[lxc-devel] [lxd/master] Add the lxd-p2c tool

stgraber on Github lxc-bot at linuxcontainers.org
Thu Feb 22 08:16:42 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180222/871229b8/attachment.bin>
-------------- next part --------------
From 1551e180ab6d1808ab3cff15b4ac56088a4e9bbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Feb 2018 18:21:09 -0500
Subject: [PATCH 1/7] shared/eagain: Make our EAGAIN code a new 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>
---
 shared/eagain/file.go          | 52 ++++++++++++++++++++++++++++++++++++++++++
 test/suites/static_analysis.sh |  1 +
 2 files changed, 53 insertions(+)
 create mode 100644 shared/eagain/file.go

diff --git a/shared/eagain/file.go b/shared/eagain/file.go
new file mode 100644
index 000000000..9e3eac9c0
--- /dev/null
+++ b/shared/eagain/file.go
@@ -0,0 +1,52 @@
+package eagain
+
+import (
+	"io"
+	"syscall"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// Reader represents an io.Reader that handles EAGAIN
+type Reader struct {
+	Reader io.Reader
+}
+
+// Read behaves like io.Reader.Read but will retry on EAGAIN
+func (er Reader) Read(p []byte) (int, error) {
+again:
+	n, err := er.Reader.Read(p)
+	if err == nil {
+		return n, nil
+	}
+
+	// keep retrying on EAGAIN
+	errno, ok := shared.GetErrno(err)
+	if ok && errno == syscall.EAGAIN {
+		goto again
+	}
+
+	return n, err
+}
+
+// Writer represents an io.Writer that handles EAGAIN
+type Writer struct {
+	Writer io.Writer
+}
+
+// Write behaves like io.Writer.Write but will retry on EAGAIN
+func (ew Writer) Write(p []byte) (int, error) {
+again:
+	n, err := ew.Writer.Write(p)
+	if err == nil {
+		return n, nil
+	}
+
+	// keep retrying on EAGAIN
+	errno, ok := shared.GetErrno(err)
+	if ok && errno == syscall.EAGAIN {
+		goto again
+	}
+
+	return n, err
+}
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index ce311c401..598d77113 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -83,6 +83,7 @@ test_static_analysis() {
       golint -set_exit_status shared/api/
       golint -set_exit_status shared/cancel/
       golint -set_exit_status shared/cmd/
+      golint -set_exit_status shared/eagain/
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
       golint -set_exit_status shared/ioprogress/

From f4dfb99569dd6ca434c66732e00a4a593c8e2abf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Feb 2018 18:22:30 -0500
Subject: [PATCH 2/7] lxd/netcat: Port to using shared/eagain
MIME-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_netcat.go | 46 +++-------------------------------------------
 1 file changed, 3 insertions(+), 43 deletions(-)

diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index 2fac5b338..26e2e8b0c 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -6,9 +6,9 @@ import (
 	"net"
 	"os"
 	"sync"
-	"syscall"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/eagain"
 )
 
 // Netcat is called with:
@@ -50,7 +50,7 @@ func cmdNetcat(args *Args) error {
 	wg.Add(1)
 
 	go func() {
-		_, err := io.Copy(eagainWriter{os.Stdout}, eagainReader{conn})
+		_, err := io.Copy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: conn})
 		if err != nil {
 			logFile.WriteString(fmt.Sprintf("Error while copying from stdout to unix domain socket \"%s\": %s.\n", args.Params[0], err))
 		}
@@ -59,7 +59,7 @@ func cmdNetcat(args *Args) error {
 	}()
 
 	go func() {
-		_, err := io.Copy(eagainWriter{conn}, eagainReader{os.Stdin})
+		_, err := io.Copy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: os.Stdin})
 		if err != nil {
 			logFile.WriteString(fmt.Sprintf("Error while copying from unix domain socket \"%s\" to stdin: %s.\n", args.Params[0], err))
 		}
@@ -69,43 +69,3 @@ func cmdNetcat(args *Args) error {
 
 	return nil
 }
-
-type eagainReader struct {
-	r io.Reader
-}
-
-func (er eagainReader) Read(p []byte) (int, error) {
-again:
-	n, err := er.r.Read(p)
-	if err == nil {
-		return n, nil
-	}
-
-	// keep retrying on EAGAIN
-	errno, ok := shared.GetErrno(err)
-	if ok && errno == syscall.EAGAIN {
-		goto again
-	}
-
-	return n, err
-}
-
-type eagainWriter struct {
-	w io.Writer
-}
-
-func (ew eagainWriter) Write(p []byte) (int, error) {
-again:
-	n, err := ew.w.Write(p)
-	if err == nil {
-		return n, nil
-	}
-
-	// keep retrying on EAGAIN
-	errno, ok := shared.GetErrno(err)
-	if ok && errno == syscall.EAGAIN {
-		goto again
-	}
-
-	return n, err
-}

From 4de1243fe80fc9421d60c60c6ba176169195d68f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Feb 2018 18:50:35 -0500
Subject: [PATCH 3/7] lxd: Move migration code to 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 +-
 lxd/containers_post.go            |   3 +-
 lxd/migrate.go                    | 104 ++++++++++---------------
 lxd/{ => migration}/migrate.pb.go | 159 +++++++++++++++++++-------------------
 lxd/{ => migration}/migrate.proto |   2 +-
 lxd/migration/wsproto.go          |  71 +++++++++++++++++
 lxd/storage.go                    |   5 +-
 lxd/storage_btrfs.go              |   9 ++-
 lxd/storage_ceph_migration.go     |   7 +-
 lxd/storage_dir.go                |   7 +-
 lxd/storage_lvm.go                |   7 +-
 lxd/storage_migration.go          |   5 +-
 lxd/storage_mock.go               |   8 +-
 lxd/storage_zfs.go                |   7 +-
 test/suites/static_analysis.sh    |   1 +
 15 files changed, 227 insertions(+), 170 deletions(-)
 rename lxd/{ => migration}/migrate.pb.go (70%)
 rename lxd/{ => migration}/migrate.proto (99%)
 create mode 100644 lxd/migration/wsproto.go

diff --git a/Makefile b/Makefile
index 891088074..9d7a37226 100644
--- a/Makefile
+++ b/Makefile
@@ -44,7 +44,7 @@ debug:
 # it's not a default build step.
 .PHONY: protobuf
 protobuf:
-	protoc --go_out=. ./lxd/migrate.proto
+	protoc --go_out=. ./lxd/migration/migrate.proto
 
 .PHONY: check
 check: default
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 76d2a615a..55875277f 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -12,6 +12,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -316,7 +317,7 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 			return InternalError(err)
 		}
 
-		if ps.MigrationType() == MigrationFSType_RSYNC {
+		if ps.MigrationType() == migration.MigrationFSType_RSYNC {
 			c, err = containerCreateFromImage(d.State(), args, req.Source.BaseImage)
 			if err != nil {
 				return InternalError(err)
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6bedadcb1..62078766e 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -24,6 +24,7 @@ import (
 	"github.com/gorilla/websocket"
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -63,36 +64,17 @@ func (c *migrationFields) send(m proto.Message) error {
 	 */
 	c.controlLock.Lock()
 	defer c.controlLock.Unlock()
-	w, err := c.controlConn.NextWriter(websocket.BinaryMessage)
-	if err != nil {
-		return err
-	}
-	defer w.Close()
 
-	data, err := proto.Marshal(m)
+	err := migration.ProtoSend(c.controlConn, m)
 	if err != nil {
 		return err
 	}
 
-	return shared.WriteAll(w, data)
+	return nil
 }
 
 func (c *migrationFields) recv(m proto.Message) error {
-	mt, r, err := c.controlConn.NextReader()
-	if err != nil {
-		return err
-	}
-
-	if mt != websocket.BinaryMessage {
-		return fmt.Errorf("Only binary messages allowed")
-	}
-
-	buf, err := ioutil.ReadAll(r)
-	if err != nil {
-		return err
-	}
-
-	return proto.Unmarshal(buf, m)
+	return migration.ProtoRecv(c.controlConn, m)
 }
 
 func (c *migrationFields) disconnect() {
@@ -123,26 +105,20 @@ func (c *migrationFields) disconnect() {
 }
 
 func (c *migrationFields) sendControl(err error) {
-	message := ""
-	if err != nil {
-		message = err.Error()
-	}
+	c.controlLock.Lock()
+	defer c.controlLock.Unlock()
 
-	msg := MigrationControl{
-		Success: proto.Bool(err == nil),
-		Message: proto.String(message),
-	}
-	c.send(&msg)
+	migration.ProtoSendControl(c.controlConn, err)
 
 	if err != nil {
 		c.disconnect()
 	}
 }
 
-func (c *migrationFields) controlChannel() <-chan MigrationControl {
-	ch := make(chan MigrationControl)
+func (c *migrationFields) controlChannel() <-chan migration.MigrationControl {
+	ch := make(chan migration.MigrationControl)
 	go func() {
-		msg := MigrationControl{}
+		msg := migration.MigrationControl{}
 		err := c.recv(&msg)
 		if err != nil {
 			logger.Debugf("Got error reading migration control socket %s", err)
@@ -321,24 +297,24 @@ fi
 	return err
 }
 
-func snapshotToProtobuf(c container) *Snapshot {
-	config := []*Config{}
+func snapshotToProtobuf(c container) *migration.Snapshot {
+	config := []*migration.Config{}
 	for k, v := range c.LocalConfig() {
 		kCopy := string(k)
 		vCopy := string(v)
-		config = append(config, &Config{Key: &kCopy, Value: &vCopy})
+		config = append(config, &migration.Config{Key: &kCopy, Value: &vCopy})
 	}
 
-	devices := []*Device{}
+	devices := []*migration.Device{}
 	for name, d := range c.LocalDevices() {
-		props := []*Config{}
+		props := []*migration.Config{}
 		for k, v := range d {
 			kCopy := string(k)
 			vCopy := string(v)
-			props = append(props, &Config{Key: &kCopy, Value: &vCopy})
+			props = append(props, &migration.Config{Key: &kCopy, Value: &vCopy})
 		}
 
-		devices = append(devices, &Device{Name: &name, Config: props})
+		devices = append(devices, &migration.Device{Name: &name, Config: props})
 	}
 
 	parts := strings.SplitN(c.Name(), shared.SnapshotDelimiter, 2)
@@ -346,7 +322,7 @@ func snapshotToProtobuf(c container) *Snapshot {
 	arch := int32(c.Architecture())
 	stateful := c.IsStateful()
 
-	return &Snapshot{
+	return &migration.Snapshot{
 		Name:         &parts[len(parts)-1],
 		LocalConfig:  config,
 		Profiles:     c.Profiles(),
@@ -444,7 +420,7 @@ func readCriuStatsDump(path string) (uint64, uint64, error) {
 	size := binary.LittleEndian.Uint32(in[8:12])
 	logger.Debugf("stats-dump payload size %d", size)
 
-	statsEntry := &StatsEntry{}
+	statsEntry := &migration.StatsEntry{}
 	if err = proto.Unmarshal(in[12:12+size], statsEntry); err != nil {
 		logger.Errorf("Failed to parse CRIU's 'stats-dump' file: %s", err.Error())
 		return 0, 0, err
@@ -534,7 +510,7 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) {
 	// expects a message to know if this was the
 	// last pre-dump
 	logger.Debugf("Sending another header")
-	sync := MigrationSync{
+	sync := migration.MigrationSync{
 		FinalPreDump: proto.Bool(final),
 	}
 
@@ -557,11 +533,11 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) {
 func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	<-s.allConnected
 
-	criuType := CRIUType_CRIU_RSYNC.Enum()
+	criuType := migration.CRIUType_CRIU_RSYNC.Enum()
 	if !s.live {
 		criuType = nil
 		if s.container.IsRunning() {
-			criuType = CRIUType_NONE.Enum()
+			criuType = migration.CRIUType_NONE.Enum()
 		}
 	}
 
@@ -575,7 +551,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		defer s.container.StorageStop()
 	}
 
-	idmaps := make([]*IDMapType, 0)
+	idmaps := make([]*migration.IDMapType, 0)
 
 	idmapset, err := s.container.IdmapSet()
 	if err != nil {
@@ -584,7 +560,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 
 	if idmapset != nil {
 		for _, ctnIdmap := range idmapset.Idmap {
-			idmap := IDMapType{
+			idmap := migration.IDMapType{
 				Isuid:    proto.Bool(ctnIdmap.Isuid),
 				Isgid:    proto.Bool(ctnIdmap.Isgid),
 				Hostid:   proto.Int32(int32(ctnIdmap.Hostid)),
@@ -598,7 +574,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 
 	driver, fsErr := s.container.Storage().MigrationSource(s.container, s.containerOnly)
 
-	snapshots := []*Snapshot{}
+	snapshots := []*migration.Snapshot{}
 	snapshotNames := []string{}
 	// Only send snapshots when requested.
 	if !s.containerOnly {
@@ -620,7 +596,7 @@ 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{
+	header := migration.MigrationHeader{
 		Fs:            &myType,
 		Criu:          criuType,
 		Idmap:         idmaps,
@@ -648,7 +624,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 
 	bwlimit := ""
 	if *header.Fs != myType {
-		myType = MigrationFSType_RSYNC
+		myType = migration.MigrationFSType_RSYNC
 		header.Fs = &myType
 
 		driver, _ = rsyncMigrationSource(s.container, s.containerOnly)
@@ -693,7 +669,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	if s.live {
 		if header.Criu == nil {
 			return abort(fmt.Errorf("Got no CRIU socket type for live migration"))
-		} else if *header.Criu != CRIUType_CRIU_RSYNC {
+		} else if *header.Criu != migration.CRIUType_CRIU_RSYNC {
 			return abort(fmt.Errorf("Formats other than criu rsync not understood"))
 		}
 
@@ -865,7 +841,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		}
 	}
 
-	if s.live || (header.Criu != nil && *header.Criu == CRIUType_NONE) {
+	if s.live || (header.Criu != nil && *header.Criu == migration.CRIUType_NONE) {
 		err = driver.SendAfterCheckpoint(s.fsConn, bwlimit)
 		if err != nil {
 			return abort(err)
@@ -874,7 +850,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 
 	driver.Cleanup()
 
-	msg := MigrationControl{}
+	msg := migration.MigrationControl{}
 	err = s.recv(&msg)
 	if err != nil {
 		s.disconnect()
@@ -1091,7 +1067,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		controller = c.dest.sendControl
 	}
 
-	header := MigrationHeader{}
+	header := migration.MigrationHeader{}
 	if err := receiver(&header); err != nil {
 		controller(err)
 		return err
@@ -1102,9 +1078,9 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		live = c.dest.live
 	}
 
-	criuType := CRIUType_CRIU_RSYNC.Enum()
-	if header.Criu != nil && *header.Criu == CRIUType_NONE {
-		criuType = CRIUType_NONE.Enum()
+	criuType := migration.CRIUType_CRIU_RSYNC.Enum()
+	if header.Criu != nil && *header.Criu == migration.CRIUType_NONE {
+		criuType = migration.CRIUType_NONE.Enum()
 	} else {
 		if !live {
 			criuType = nil
@@ -1113,7 +1089,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 	mySink := c.src.container.Storage().MigrationSink
 	myType := c.src.container.Storage().MigrationType()
-	resp := MigrationHeader{
+	resp := migration.MigrationHeader{
 		Fs:   &myType,
 		Criu: criuType,
 	}
@@ -1122,7 +1098,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	// we have to use rsync.
 	if *header.Fs != *resp.Fs {
 		mySink = rsyncMigrationSink
-		myType = MigrationFSType_RSYNC
+		myType = migration.MigrationFSType_RSYNC
 		resp.Fs = &myType
 	}
 
@@ -1163,7 +1139,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		 */
 		fsTransfer := make(chan error)
 		go func() {
-			snapshots := []*Snapshot{}
+			snapshots := []*migration.Snapshot{}
 
 			/* Legacy: we only sent the snapshot names, so we just
 			 * copy the container's config over, same as we used to
@@ -1191,7 +1167,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				sendFinalFsDelta = true
 			}
 
-			if criuType != nil && *criuType == CRIUType_NONE {
+			if criuType != nil && *criuType == migration.CRIUType_NONE {
 				sendFinalFsDelta = true
 			}
 
@@ -1229,7 +1205,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				criuConn = c.src.criuConn
 			}
 
-			sync := &MigrationSync{
+			sync := &migration.MigrationSync{
 				FinalPreDump: proto.Bool(false),
 			}
 
@@ -1307,7 +1283,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		restore <- nil
 	}(c)
 
-	var source <-chan MigrationControl
+	var source <-chan migration.MigrationControl
 	if c.push {
 		source = c.dest.controlChannel()
 	} else {
diff --git a/lxd/migrate.pb.go b/lxd/migration/migrate.pb.go
similarity index 70%
rename from lxd/migrate.pb.go
rename to lxd/migration/migrate.pb.go
index 85368b02d..9e9f4a465 100644
--- a/lxd/migrate.pb.go
+++ b/lxd/migration/migrate.pb.go
@@ -1,12 +1,11 @@
-// Code generated by protoc-gen-go.
-// source: lxd/migrate.proto
-// DO NOT EDIT!
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: lxd/migration/migrate.proto
 
 /*
-Package main is a generated protocol buffer package.
+Package migration is a generated protocol buffer package.
 
 It is generated from these files:
-	lxd/migrate.proto
+	lxd/migration/migrate.proto
 
 It has these top-level messages:
 	IDMapType
@@ -20,7 +19,7 @@ It has these top-level messages:
 	RestoreStatsEntry
 	StatsEntry
 */
-package main
+package migration
 
 import proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
@@ -279,8 +278,8 @@ func (m *Snapshot) GetStateful() bool {
 }
 
 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"`
+	Fs               *MigrationFSType `protobuf:"varint,1,req,name=fs,enum=migration.MigrationFSType" json:"fs,omitempty"`
+	Criu             *CRIUType        `protobuf:"varint,2,opt,name=criu,enum=migration.CRIUType" json:"criu,omitempty"`
 	Idmap            []*IDMapType     `protobuf:"bytes,3,rep,name=idmap" json:"idmap,omitempty"`
 	SnapshotNames    []string         `protobuf:"bytes,4,rep,name=snapshotNames" json:"snapshotNames,omitempty"`
 	Snapshots        []*Snapshot      `protobuf:"bytes,5,rep,name=snapshots" json:"snapshots,omitempty"`
@@ -551,78 +550,78 @@ func (m *StatsEntry) GetRestore() *RestoreStatsEntry {
 }
 
 func init() {
-	proto.RegisterType((*IDMapType)(nil), "main.IDMapType")
-	proto.RegisterType((*Config)(nil), "main.Config")
-	proto.RegisterType((*Device)(nil), "main.Device")
-	proto.RegisterType((*Snapshot)(nil), "main.Snapshot")
-	proto.RegisterType((*MigrationHeader)(nil), "main.MigrationHeader")
-	proto.RegisterType((*MigrationControl)(nil), "main.MigrationControl")
-	proto.RegisterType((*MigrationSync)(nil), "main.MigrationSync")
-	proto.RegisterType((*DumpStatsEntry)(nil), "main.dump_stats_entry")
-	proto.RegisterType((*RestoreStatsEntry)(nil), "main.restore_stats_entry")
-	proto.RegisterType((*StatsEntry)(nil), "main.stats_entry")
-	proto.RegisterEnum("main.MigrationFSType", MigrationFSType_name, MigrationFSType_value)
-	proto.RegisterEnum("main.CRIUType", CRIUType_name, CRIUType_value)
-}
-
-func init() { proto.RegisterFile("lxd/migrate.proto", fileDescriptor0) }
+	proto.RegisterType((*IDMapType)(nil), "migration.IDMapType")
+	proto.RegisterType((*Config)(nil), "migration.Config")
+	proto.RegisterType((*Device)(nil), "migration.Device")
+	proto.RegisterType((*Snapshot)(nil), "migration.Snapshot")
+	proto.RegisterType((*MigrationHeader)(nil), "migration.MigrationHeader")
+	proto.RegisterType((*MigrationControl)(nil), "migration.MigrationControl")
+	proto.RegisterType((*MigrationSync)(nil), "migration.MigrationSync")
+	proto.RegisterType((*DumpStatsEntry)(nil), "migration.dump_stats_entry")
+	proto.RegisterType((*RestoreStatsEntry)(nil), "migration.restore_stats_entry")
+	proto.RegisterType((*StatsEntry)(nil), "migration.stats_entry")
+	proto.RegisterEnum("migration.MigrationFSType", MigrationFSType_name, MigrationFSType_value)
+	proto.RegisterEnum("migration.CRIUType", CRIUType_name, CRIUType_value)
+}
+
+func init() { proto.RegisterFile("lxd/migration/migrate.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 888 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x94, 0xcd, 0x6e, 0xdb, 0x46,
-	0x10, 0xc7, 0x4b, 0x89, 0xb2, 0xc5, 0xa1, 0x2c, 0x2b, 0xdb, 0x34, 0x60, 0x8b, 0x16, 0x55, 0x19,
-	0x1b, 0x10, 0x8c, 0xc2, 0x31, 0x94, 0x53, 0x8f, 0xb5, 0x5c, 0x23, 0x01, 0x12, 0xd7, 0x58, 0x39,
-	0x28, 0xda, 0x0b, 0xb1, 0x25, 0x87, 0xf2, 0xc2, 0xfc, 0xc2, 0x2e, 0x65, 0x57, 0xbe, 0xf4, 0x69,
-	0xfa, 0x48, 0x7d, 0x90, 0x5e, 0x7b, 0x2a, 0x76, 0x96, 0xa4, 0xa5, 0x22, 0xb9, 0xed, 0xfc, 0xe6,
-	0xcf, 0x99, 0x9d, 0x0f, 0x2e, 0x3c, 0xcb, 0xfe, 0x48, 0x5e, 0xe5, 0x72, 0xa5, 0x44, 0x8d, 0xa7,
-	0x95, 0x2a, 0xeb, 0x92, 0xb9, 0xb9, 0x90, 0x45, 0xf8, 0x27, 0x78, 0x6f, 0x2f, 0xde, 0x8b, 0xea,
-	0x66, 0x53, 0x21, 0x7b, 0x0e, 0x03, 0xa9, 0xd7, 0x32, 0x09, 0x9c, 0x69, 0x6f, 0x36, 0xe4, 0xd6,
-	0xb0, 0x74, 0x25, 0x93, 0xa0, 0xd7, 0xd2, 0x95, 0x4c, 0xd8, 0x0b, 0xd8, 0xbb, 0x2d, 0x75, 0x2d,
-	0x93, 0xa0, 0x3f, 0xed, 0xcd, 0x06, 0xbc, 0xb1, 0x18, 0x03, 0xb7, 0xd0, 0x32, 0x09, 0x5c, 0xa2,
-	0x74, 0x66, 0x5f, 0xc1, 0x30, 0x17, 0x95, 0x12, 0xc5, 0x0a, 0x83, 0x01, 0xf1, 0xce, 0x0e, 0xcf,
-	0x60, 0x6f, 0x51, 0x16, 0xa9, 0x5c, 0xb1, 0x09, 0xf4, 0xef, 0x70, 0x43, 0xb9, 0x3d, 0x6e, 0x8e,
-	0x26, 0xf3, 0xbd, 0xc8, 0xd6, 0x48, 0x99, 0x3d, 0x6e, 0x8d, 0xf0, 0x1c, 0xf6, 0x2e, 0xf0, 0x5e,
-	0xc6, 0x48, 0xb9, 0x44, 0x8e, 0xcd, 0x27, 0x74, 0x66, 0x47, 0xb0, 0x17, 0x53, 0xbc, 0xa0, 0x37,
-	0xed, 0xcf, 0xfc, 0xf9, 0xe8, 0xd4, 0xd4, 0x79, 0x6a, 0x73, 0xf0, 0xc6, 0x17, 0xfe, 0xeb, 0xc0,
-	0x70, 0x59, 0x88, 0x4a, 0xdf, 0x96, 0xf5, 0x47, 0xc3, 0x9c, 0x82, 0x9f, 0x95, 0xb1, 0xc8, 0x16,
-	0x9f, 0x8e, 0xb5, 0x2d, 0x30, 0x25, 0x56, 0xaa, 0x4c, 0x65, 0x86, 0x3a, 0xe8, 0x4f, 0xfb, 0x33,
-	0x8f, 0x77, 0x36, 0xfb, 0x1a, 0x3c, 0xac, 0x6e, 0x31, 0x47, 0x25, 0x32, 0xea, 0xcb, 0x90, 0x3f,
-	0x01, 0x76, 0x06, 0x23, 0x0a, 0x64, 0x6b, 0xd2, 0xc1, 0x60, 0x3b, 0x95, 0x85, 0x7c, 0x47, 0xc1,
-	0x42, 0x18, 0x09, 0x15, 0xdf, 0xca, 0x1a, 0xe3, 0x7a, 0xad, 0x30, 0xd8, 0xa3, 0x96, 0xee, 0x30,
-	0x73, 0x1f, 0x5d, 0x8b, 0x1a, 0xd3, 0x75, 0x16, 0xec, 0x53, 0xca, 0xce, 0x0e, 0xff, 0x71, 0xe0,
-	0xf0, 0x3d, 0xed, 0x82, 0x2c, 0x8b, 0x37, 0x28, 0x12, 0x54, 0xec, 0x18, 0x7a, 0xa9, 0xa6, 0x0e,
-	0x8c, 0xe7, 0x5f, 0xd8, 0xdc, 0x9d, 0xe4, 0x72, 0x69, 0xb6, 0x83, 0xf7, 0x52, 0x93, 0xda, 0x8d,
-	0x95, 0x5c, 0x07, 0xbd, 0xa9, 0x33, 0x1b, 0xcf, 0xc7, 0x4d, 0x3f, 0xf8, 0xdb, 0x0f, 0xa4, 0x20,
-	0x1f, 0x3b, 0x86, 0x81, 0x4c, 0x72, 0x51, 0x51, 0x1f, 0xfc, 0xf9, 0xa1, 0x15, 0x75, 0x5b, 0xc6,
-	0xad, 0x97, 0x1d, 0xc1, 0x81, 0x6e, 0x26, 0x70, 0x25, 0x72, 0xd4, 0x81, 0x4b, 0x6d, 0xdb, 0x85,
-	0xec, 0x7b, 0xf0, 0x5a, 0xd0, 0xb6, 0xa6, 0xc9, 0xda, 0x8e, 0x8f, 0x3f, 0x09, 0x58, 0x00, 0xfb,
-	0x95, 0xc2, 0x64, 0x9d, 0x57, 0xc1, 0xfe, 0xd4, 0x99, 0x0d, 0x79, 0x6b, 0x86, 0x97, 0x30, 0xe9,
-	0xea, 0x59, 0x94, 0x45, 0xad, 0xca, 0xcc, 0xa8, 0xf5, 0x3a, 0x8e, 0x51, 0xeb, 0x66, 0xe1, 0x5b,
-	0xd3, 0x78, 0x72, 0xd4, 0x5a, 0xac, 0x90, 0x2a, 0xf5, 0x78, 0x6b, 0x86, 0xaf, 0xe1, 0xa0, 0x8b,
-	0xb3, 0xdc, 0x14, 0xb1, 0x19, 0x46, 0x2a, 0x0b, 0x91, 0x5d, 0x2b, 0xbc, 0x30, 0x79, 0x6d, 0xa4,
-	0x1d, 0x16, 0xfe, 0xd5, 0x87, 0x89, 0xb9, 0x45, 0x64, 0x46, 0xa0, 0x23, 0x2c, 0x6a, 0xb5, 0x61,
-	0x2f, 0xe1, 0x20, 0x55, 0x88, 0x8f, 0xb2, 0x58, 0x45, 0xb5, 0x6c, 0xd6, 0xef, 0x80, 0x8f, 0x5a,
-	0x78, 0x23, 0x73, 0x64, 0xdf, 0x82, 0x9f, 0xaa, 0xf2, 0x11, 0x0b, 0x2b, 0xe9, 0x91, 0x04, 0x2c,
-	0x22, 0xc1, 0x77, 0x30, 0xca, 0x31, 0xa7, 0xe0, 0xa4, 0xe8, 0x93, 0xc2, 0x6f, 0x18, 0x49, 0x5e,
-	0xc2, 0x41, 0x8e, 0xf9, 0x83, 0x92, 0x35, 0x5a, 0x8d, 0x6b, 0x13, 0xb5, 0xb0, 0x15, 0x55, 0x62,
-	0x85, 0x3a, 0xd2, 0xb1, 0x28, 0x0a, 0x4c, 0xe8, 0x3f, 0x75, 0xf9, 0x88, 0xe0, 0xd2, 0x32, 0x76,
-	0x06, 0xcf, 0x1b, 0xd1, 0x9d, 0xac, 0x2a, 0x4c, 0xa2, 0x4a, 0x28, 0x2c, 0x6a, 0x5a, 0x40, 0x97,
-	0x33, 0xab, 0xb5, 0xae, 0x6b, 0xf2, 0x3c, 0x85, 0x35, 0x99, 0x6a, 0x2c, 0x68, 0x17, 0xdb, 0xb0,
-	0xbf, 0x58, 0x66, 0x44, 0x52, 0xe5, 0xa2, 0x8a, 0x14, 0xea, 0x32, 0xbb, 0xc7, 0x60, 0x38, 0x75,
-	0xcc, 0x05, 0x09, 0x72, 0xcb, 0xd8, 0x37, 0x00, 0x36, 0x52, 0x26, 0x1e, 0x37, 0x81, 0x47, 0x61,
-	0x3c, 0x22, 0xef, 0xc4, 0xe3, 0xa6, 0x75, 0x47, 0x95, 0xac, 0x50, 0x07, 0x30, 0x75, 0x5a, 0xf7,
-	0xb5, 0x01, 0xec, 0x08, 0xc6, 0x9d, 0x3b, 0xfa, 0x7d, 0x9d, 0xea, 0xc0, 0x27, 0xc9, 0xa8, 0x95,
-	0x9c, 0xaf, 0x53, 0x1d, 0xfe, 0xed, 0xc0, 0xe7, 0x0a, 0x75, 0x5d, 0x2a, 0xdc, 0x19, 0xd5, 0xb1,
-	0xfd, 0x5a, 0x47, 0x71, 0x99, 0x9b, 0x92, 0xed, 0x03, 0xe9, 0x72, 0x5b, 0xdb, 0xa2, 0x81, 0xec,
-	0x04, 0x9e, 0xed, 0xb6, 0x27, 0x2e, 0x1f, 0x68, 0x64, 0x2e, 0x3f, 0xdc, 0xee, 0xcd, 0xa2, 0x7c,
-	0x30, 0x73, 0x4b, 0x4b, 0x75, 0xd7, 0x0d, 0xbf, 0x99, 0x5b, 0xc3, 0xda, 0xd1, 0xb6, 0x97, 0xd9,
-	0x1a, 0x9b, 0xdf, 0x30, 0x92, 0x74, 0x17, 0x6b, 0xa0, 0x19, 0x9b, 0xd3, 0x5d, 0x8c, 0x37, 0x30,
-	0x2c, 0xc0, 0xdf, 0x2e, 0xe7, 0x04, 0xdc, 0xc4, 0xae, 0xaa, 0x33, 0xf3, 0xe7, 0x2f, 0xec, 0xef,
-	0xf4, 0xff, 0xfd, 0xe4, 0xa4, 0x61, 0xaf, 0x61, 0xbf, 0x89, 0x4d, 0x7f, 0x82, 0x3f, 0xff, 0xd2,
-	0xca, 0x3f, 0xd2, 0x26, 0xde, 0x2a, 0x4f, 0x7e, 0xd8, 0x7a, 0x5f, 0xec, 0xe3, 0xc1, 0x3c, 0x18,
-	0xf0, 0xe5, 0xaf, 0x57, 0x8b, 0xc9, 0x67, 0xe6, 0x78, 0x7e, 0xc3, 0x2f, 0x97, 0x13, 0x87, 0xed,
-	0x43, 0xff, 0xb7, 0xcb, 0xe5, 0xa4, 0x67, 0x0e, 0xfc, 0xfc, 0x62, 0xd2, 0x3f, 0x79, 0x05, 0xc3,
-	0xf6, 0x39, 0x61, 0x63, 0x00, 0x73, 0x8e, 0xb6, 0x3e, 0xbc, 0x7e, 0xf3, 0xe3, 0x87, 0x77, 0x13,
-	0x87, 0x0d, 0xc1, 0xbd, 0xfa, 0xf9, 0xea, 0xa7, 0x49, 0xef, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff,
-	0x0d, 0x9a, 0xe8, 0xf0, 0xda, 0x06, 0x00, 0x00,
+	// 894 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0x5b, 0x6f, 0xdb, 0x36,
+	0x14, 0x9e, 0x64, 0xf9, 0xa2, 0x23, 0x3b, 0x75, 0xd9, 0x60, 0x10, 0xda, 0x5d, 0x3c, 0xb5, 0xc3,
+	0x3c, 0x3f, 0x24, 0x9d, 0x8b, 0x01, 0xdb, 0xe3, 0xe2, 0x2c, 0x6b, 0x81, 0x36, 0x0b, 0xe8, 0x14,
+	0xc3, 0xf6, 0x22, 0x70, 0xd2, 0x91, 0x43, 0x44, 0x37, 0x90, 0x72, 0x52, 0xe7, 0x65, 0x2f, 0xdb,
+	0x4f, 0xd9, 0x4f, 0xda, 0xff, 0x19, 0x48, 0x4a, 0x8a, 0xbc, 0x15, 0xe8, 0x1b, 0xcf, 0x77, 0x3e,
+	0x9e, 0xef, 0xdc, 0x48, 0x78, 0x92, 0xbe, 0x8b, 0x8f, 0x33, 0xbe, 0x11, 0xac, 0xe2, 0x45, 0x5e,
+	0x9f, 0xf0, 0xa8, 0x14, 0x45, 0x55, 0x10, 0xb7, 0x75, 0x04, 0x7f, 0x80, 0xfb, 0xea, 0xf4, 0x0d,
+	0x2b, 0x2f, 0x77, 0x25, 0x92, 0x43, 0xe8, 0x73, 0xb9, 0xe5, 0xb1, 0x6f, 0xcd, 0xec, 0xf9, 0x88,
+	0x1a, 0xc3, 0xa0, 0x1b, 0x1e, 0xfb, 0x76, 0x83, 0x6e, 0x78, 0x4c, 0x3e, 0x86, 0xc1, 0x55, 0x21,
+	0x2b, 0x1e, 0xfb, 0xbd, 0x99, 0x3d, 0xef, 0xd3, 0xda, 0x22, 0x04, 0x9c, 0x5c, 0xf2, 0xd8, 0x77,
+	0x34, 0xaa, 0xcf, 0xe4, 0x31, 0x8c, 0x32, 0x56, 0x0a, 0x96, 0x6f, 0xd0, 0xef, 0x6b, 0xbc, 0xb5,
+	0x83, 0xe7, 0x30, 0x58, 0x15, 0x79, 0xc2, 0x37, 0x64, 0x0a, 0xbd, 0x6b, 0xdc, 0x69, 0x6d, 0x97,
+	0xaa, 0xa3, 0x52, 0xbe, 0x61, 0xe9, 0x16, 0xb5, 0xb2, 0x4b, 0x8d, 0x11, 0xfc, 0x04, 0x83, 0x53,
+	0xbc, 0xe1, 0x11, 0x6a, 0x2d, 0x96, 0x61, 0x7d, 0x45, 0x9f, 0xc9, 0xd7, 0x30, 0x88, 0x74, 0x3c,
+	0xdf, 0x9e, 0xf5, 0xe6, 0xde, 0xf2, 0xe1, 0x51, 0x5b, 0xec, 0x91, 0x11, 0xa2, 0x35, 0x21, 0xf8,
+	0xd3, 0x86, 0xd1, 0x3a, 0x67, 0xa5, 0xbc, 0x2a, 0xaa, 0xf7, 0xc6, 0x7a, 0x01, 0x5e, 0x5a, 0x44,
+	0x2c, 0x5d, 0x7d, 0x20, 0x60, 0x97, 0xa5, 0x8a, 0x2d, 0x45, 0x91, 0xf0, 0x14, 0xa5, 0xdf, 0x9b,
+	0xf5, 0xe6, 0x2e, 0x6d, 0x6d, 0xf2, 0x09, 0xb8, 0x58, 0x5e, 0x61, 0x86, 0x82, 0xa5, 0xba, 0x43,
+	0x23, 0x7a, 0x0f, 0x90, 0x6f, 0x61, 0xac, 0x03, 0x99, 0xea, 0xa4, 0xdf, 0xff, 0x9f, 0x9e, 0xf1,
+	0xd0, 0x3d, 0x1a, 0x09, 0x60, 0xcc, 0x44, 0x74, 0xc5, 0x2b, 0x8c, 0xaa, 0xad, 0x40, 0x7f, 0xa0,
+	0x3b, 0xbc, 0x87, 0xa9, 0xa4, 0x64, 0xc5, 0x2a, 0x4c, 0xb6, 0xa9, 0x3f, 0xd4, 0xba, 0xad, 0x1d,
+	0xfc, 0x65, 0xc3, 0x83, 0x37, 0x8d, 0xc4, 0x4b, 0x64, 0x31, 0x0a, 0xb2, 0x00, 0x3b, 0x91, 0xba,
+	0x17, 0x07, 0xcb, 0xc7, 0x9d, 0x04, 0x5a, 0xde, 0xd9, 0x5a, 0x6d, 0x0c, 0xb5, 0x13, 0x49, 0xbe,
+	0x02, 0x27, 0x12, 0x7c, 0xeb, 0xdb, 0x33, 0x6b, 0x7e, 0xb0, 0x7c, 0xd4, 0x6d, 0x0f, 0x7d, 0xf5,
+	0x56, 0xd3, 0x34, 0x81, 0x2c, 0xa0, 0xcf, 0xe3, 0x8c, 0x95, 0xba, 0x2d, 0xde, 0xf2, 0xb0, 0xc3,
+	0x6c, 0x77, 0x90, 0x1a, 0x0a, 0x79, 0x06, 0x13, 0x59, 0x8f, 0xe6, 0x9c, 0x65, 0x28, 0x7d, 0x47,
+	0xb7, 0x72, 0x1f, 0x24, 0xdf, 0x80, 0xdb, 0x00, 0x4d, 0xbb, 0xba, 0xfa, 0xcd, 0x70, 0xe9, 0x3d,
+	0x8b, 0xf8, 0x30, 0x2c, 0x05, 0xc6, 0xdb, 0xac, 0xf4, 0x87, 0x33, 0x6b, 0x3e, 0xa2, 0x8d, 0x19,
+	0x9c, 0xc1, 0xb4, 0x2d, 0x6f, 0x55, 0xe4, 0x95, 0x28, 0x52, 0xc5, 0x96, 0xdb, 0x28, 0x42, 0x29,
+	0xeb, 0x37, 0xd1, 0x98, 0xca, 0x93, 0xa1, 0x94, 0x6c, 0x83, 0xba, 0x70, 0x97, 0x36, 0x66, 0xf0,
+	0x02, 0x26, 0x6d, 0x9c, 0xf5, 0x2e, 0x8f, 0xd4, 0x80, 0x12, 0x9e, 0xb3, 0xf4, 0x42, 0xe0, 0xa9,
+	0xd2, 0x35, 0x91, 0xf6, 0xb0, 0xe0, 0xef, 0x1e, 0x4c, 0x55, 0x16, 0xa1, 0x1a, 0x8b, 0x0c, 0x31,
+	0xaf, 0xc4, 0x8e, 0x3c, 0x85, 0x49, 0x22, 0x10, 0xef, 0x78, 0xbe, 0x09, 0x2b, 0x5e, 0x2f, 0xe7,
+	0x84, 0x8e, 0x1b, 0xf0, 0x92, 0x67, 0x48, 0x3e, 0x07, 0x2f, 0x11, 0xc5, 0x1d, 0xe6, 0x86, 0x62,
+	0x6b, 0x0a, 0x18, 0x48, 0x13, 0xbe, 0x80, 0x71, 0x86, 0x99, 0x0e, 0xae, 0x19, 0x3d, 0xcd, 0xf0,
+	0x6a, 0x4c, 0x53, 0x9e, 0xc2, 0x24, 0xc3, 0xec, 0x56, 0xf0, 0x0a, 0x0d, 0xc7, 0x31, 0x42, 0x0d,
+	0xd8, 0x90, 0x4a, 0xb6, 0x41, 0x19, 0xca, 0x88, 0xe5, 0x39, 0xc6, 0xfa, 0x29, 0x3b, 0x74, 0xac,
+	0xc1, 0xb5, 0xc1, 0xc8, 0x73, 0x38, 0xac, 0x49, 0xd7, 0xbc, 0x2c, 0x31, 0x0e, 0x4b, 0x26, 0x30,
+	0xaf, 0xf4, 0x52, 0x3a, 0x94, 0x18, 0xae, 0x71, 0x5d, 0x68, 0xcf, 0x7d, 0x58, 0xa5, 0x54, 0x61,
+	0xae, 0xf7, 0xb3, 0x09, 0xfb, 0x8b, 0xc1, 0x14, 0x89, 0x8b, 0x8c, 0x95, 0xa1, 0x40, 0x59, 0xa4,
+	0x37, 0xe8, 0x8f, 0x66, 0x96, 0x4a, 0x50, 0x83, 0xd4, 0x60, 0xe4, 0x53, 0x00, 0x13, 0x29, 0x65,
+	0x77, 0x3b, 0xdf, 0xd5, 0x61, 0x5c, 0x8d, 0xbc, 0x66, 0x77, 0xbb, 0xc6, 0x1d, 0x96, 0xbc, 0x44,
+	0xe9, 0xc3, 0xcc, 0x6a, 0xdc, 0x17, 0x0a, 0x20, 0xcf, 0xe0, 0xa0, 0x75, 0x87, 0xbf, 0x6f, 0x13,
+	0xe9, 0x7b, 0x9a, 0x32, 0x6e, 0x28, 0x27, 0xdb, 0x44, 0x06, 0xff, 0x58, 0xf0, 0x48, 0xa0, 0xac,
+	0x0a, 0x81, 0x7b, 0xa3, 0xfa, 0xd2, 0xdc, 0x96, 0x61, 0x54, 0x64, 0xaa, 0x64, 0xf3, 0x87, 0x3a,
+	0xd4, 0xd4, 0xb6, 0xaa, 0x41, 0xb2, 0x80, 0x87, 0xfb, 0xed, 0x89, 0x8a, 0x5b, 0x3d, 0x32, 0x87,
+	0x3e, 0xe8, 0xf6, 0x66, 0x55, 0xdc, 0xaa, 0xb9, 0x25, 0x85, 0xb8, 0x6e, 0x87, 0x5f, 0xcf, 0xad,
+	0xc6, 0x9a, 0xd1, 0x36, 0xc9, 0x74, 0xc6, 0xe6, 0xd5, 0x98, 0xa6, 0xb4, 0x89, 0xd5, 0xa0, 0x1a,
+	0x9b, 0xd5, 0x26, 0x46, 0x6b, 0x30, 0x78, 0x07, 0x5e, 0xb7, 0x9c, 0x63, 0x70, 0x62, 0xb3, 0xaa,
+	0xd6, 0xdc, 0x5b, 0x3e, 0xe9, 0xbc, 0xa9, 0xff, 0x2e, 0x29, 0xd5, 0x44, 0xf2, 0x1d, 0x0c, 0x6b,
+	0x01, 0xfd, 0x1c, 0xbc, 0xe5, 0x67, 0x9d, 0x3b, 0xef, 0x69, 0x18, 0x6d, 0xe8, 0x8b, 0xef, 0x3b,
+	0xbf, 0x8f, 0xf9, 0x55, 0x88, 0x0b, 0x7d, 0xba, 0xfe, 0xf5, 0x7c, 0x35, 0xfd, 0x48, 0x1d, 0x4f,
+	0x2e, 0xe9, 0xd9, 0x7a, 0x6a, 0x91, 0x21, 0xf4, 0x7e, 0x3b, 0x5b, 0x4f, 0x6d, 0x75, 0xa0, 0x27,
+	0xa7, 0xd3, 0xde, 0xe2, 0x18, 0x46, 0xcd, 0x17, 0x43, 0x0e, 0x00, 0xd4, 0x39, 0xec, 0x5c, 0xbc,
+	0x78, 0xf9, 0xc3, 0xdb, 0xd7, 0x53, 0x8b, 0x8c, 0xc0, 0x39, 0xff, 0xf9, 0xfc, 0xc7, 0xa9, 0xfd,
+	0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x86, 0x78, 0x5b, 0x16, 0x07, 0x00, 0x00,
 }
diff --git a/lxd/migrate.proto b/lxd/migration/migrate.proto
similarity index 99%
rename from lxd/migrate.proto
rename to lxd/migration/migrate.proto
index 8bcfdc295..bfb35eca2 100644
--- a/lxd/migrate.proto
+++ b/lxd/migration/migrate.proto
@@ -1,7 +1,7 @@
 // silence the protobuf compiler warning by setting the default
 syntax = "proto2";
 
-package main;
+package migration;
 
 enum MigrationFSType {
 	RSYNC		= 0;
diff --git a/lxd/migration/wsproto.go b/lxd/migration/wsproto.go
new file mode 100644
index 000000000..7ba37b29f
--- /dev/null
+++ b/lxd/migration/wsproto.go
@@ -0,0 +1,71 @@
+package migration
+
+import (
+	"fmt"
+	"io/ioutil"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// ProtoRecv gets a protobuf message from a websocket
+func ProtoRecv(ws *websocket.Conn, msg proto.Message) error {
+	mt, r, err := ws.NextReader()
+	if err != nil {
+		return err
+	}
+
+	if mt != websocket.BinaryMessage {
+		return fmt.Errorf("Only binary messages allowed")
+	}
+
+	buf, err := ioutil.ReadAll(r)
+	if err != nil {
+		return err
+	}
+
+	err = proto.Unmarshal(buf, msg)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ProtoSend sends a protobuf message over a websocket
+func ProtoSend(ws *websocket.Conn, msg proto.Message) error {
+	w, err := ws.NextWriter(websocket.BinaryMessage)
+	if err != nil {
+		return err
+	}
+	defer w.Close()
+
+	data, err := proto.Marshal(msg)
+	if err != nil {
+		return err
+	}
+
+	err = shared.WriteAll(w, data)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ProtoSendControl sends a migration control message over a websocket
+func ProtoSendControl(ws *websocket.Conn, err error) {
+	message := ""
+	if err != nil {
+		message = err.Error()
+	}
+
+	msg := MigrationControl{
+		Success: proto.Bool(err == nil),
+		Message: proto.String(message),
+	}
+
+	ProtoSend(ws, &msg)
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index 5df78f7c6..f7b04ebf0 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -12,6 +12,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -196,7 +197,7 @@ type storage interface {
 	StorageEntitySetQuota(volumeType int, size int64, data interface{}) error
 
 	// Functions dealing with migration.
-	MigrationType() MigrationFSType
+	MigrationType() migration.MigrationFSType
 	// Does this storage backend preserve inodes when it is moved across LXD
 	// hosts?
 	PreservesInodes() bool
@@ -222,7 +223,7 @@ type storage interface {
 	MigrationSink(
 		live bool,
 		c container,
-		objects []*Snapshot,
+		objects []*migration.Snapshot,
 		conn *websocket.Conn,
 		srcIdmap *idmap.IdmapSet,
 		op *operation,
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 2eca2c6a8..996ca3a85 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -16,6 +16,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -1972,12 +1973,12 @@ func (s *btrfsMigrationSourceDriver) Cleanup() {
 	}
 }
 
-func (s *storageBtrfs) MigrationType() MigrationFSType {
+func (s *storageBtrfs) MigrationType() migration.MigrationFSType {
 	if s.s.OS.RunningInUserNS {
-		return MigrationFSType_RSYNC
+		return migration.MigrationFSType_RSYNC
 	}
 
-	return MigrationFSType_BTRFS
+	return migration.MigrationFSType_BTRFS
 }
 
 func (s *storageBtrfs) PreservesInodes() bool {
@@ -2023,7 +2024,7 @@ func (s *storageBtrfs) MigrationSource(c container, containerOnly bool) (Migrati
 	return driver, nil
 }
 
-func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	if s.s.OS.RunningInUserNS {
 		return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly)
 	}
diff --git a/lxd/storage_ceph_migration.go b/lxd/storage_ceph_migration.go
index 000a91a9c..4e4016203 100644
--- a/lxd/storage_ceph_migration.go
+++ b/lxd/storage_ceph_migration.go
@@ -8,6 +8,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/idmap"
 	"github.com/lxc/lxd/shared/logger"
@@ -169,8 +170,8 @@ func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
 	return nil
 }
 
-func (s *storageCeph) MigrationType() MigrationFSType {
-	return MigrationFSType_RBD
+func (s *storageCeph) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RBD
 }
 
 func (s *storageCeph) PreservesInodes() bool {
@@ -240,7 +241,7 @@ func (s *storageCeph) MigrationSource(c container, containerOnly bool) (Migratio
 }
 
 func (s *storageCeph) MigrationSink(live bool, c container,
-	snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet,
+	snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet,
 	op *operation, containerOnly bool) error {
 	// Check that we received a valid root disk device with a pool property
 	// set.
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 771153d70..6e4ddd42e 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/idmap"
@@ -1030,8 +1031,8 @@ func (s *storageDir) ImageUmount(fingerprint string) (bool, error) {
 	return true, nil
 }
 
-func (s *storageDir) MigrationType() MigrationFSType {
-	return MigrationFSType_RSYNC
+func (s *storageDir) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RSYNC
 }
 
 func (s *storageDir) PreservesInodes() bool {
@@ -1042,7 +1043,7 @@ func (s *storageDir) MigrationSource(container container, containerOnly bool) (M
 	return rsyncMigrationSource(container, containerOnly)
 }
 
-func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+func (s *storageDir) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly)
 }
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index d74b24d2f..3eedf4fa0 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/idmap"
@@ -1724,8 +1725,8 @@ func (s *storageLvm) ImageUmount(fingerprint string) (bool, error) {
 	return true, nil
 }
 
-func (s *storageLvm) MigrationType() MigrationFSType {
-	return MigrationFSType_RSYNC
+func (s *storageLvm) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RSYNC
 }
 
 func (s *storageLvm) PreservesInodes() bool {
@@ -1736,7 +1737,7 @@ func (s *storageLvm) MigrationSource(container container, containerOnly bool) (M
 	return rsyncMigrationSource(container, containerOnly)
 }
 
-func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly)
 }
 
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 76308e96d..e8a966319 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -6,6 +6,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/idmap"
@@ -96,7 +97,7 @@ func rsyncMigrationSource(c container, containerOnly bool) (MigrationStorageSour
 	return rsyncStorageSourceDriver{c, snapshots}, nil
 }
 
-func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) db.ContainerArgs {
+func snapshotProtobufToContainerArgs(containerName string, snap *migration.Snapshot) db.ContainerArgs {
 	config := map[string]string{}
 
 	for _, ent := range snap.LocalConfig {
@@ -126,7 +127,7 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) db.Co
 	}
 }
 
-func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+func rsyncMigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	ourStart, err := container.StorageStart()
 	if err != nil {
 		return err
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 5019237f7..1fa402f87 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/idmap"
 	"github.com/lxc/lxd/shared/logger"
@@ -204,8 +205,8 @@ func (s *storageMock) ImageUmount(fingerprint string) (bool, error) {
 	return true, nil
 }
 
-func (s *storageMock) MigrationType() MigrationFSType {
-	return MigrationFSType_RSYNC
+func (s *storageMock) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_RSYNC
 }
 
 func (s *storageMock) PreservesInodes() bool {
@@ -215,7 +216,8 @@ func (s *storageMock) PreservesInodes() bool {
 func (s *storageMock) MigrationSource(container container, containerOnly bool) (MigrationStorageSourceDriver, error) {
 	return nil, fmt.Errorf("not implemented")
 }
-func (s *storageMock) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+
+func (s *storageMock) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	return nil
 }
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 46d9b6c50..9e4dec3eb 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -2160,8 +2161,8 @@ func (s *zfsMigrationSourceDriver) Cleanup() {
 	}
 }
 
-func (s *storageZfs) MigrationType() MigrationFSType {
-	return MigrationFSType_ZFS
+func (s *storageZfs) MigrationType() migration.MigrationFSType {
+	return migration.MigrationFSType_ZFS
 }
 
 func (s *storageZfs) PreservesInodes() bool {
@@ -2219,7 +2220,7 @@ func (s *storageZfs) MigrationSource(ct container, containerOnly bool) (Migratio
 	return &driver, nil
 }
 
-func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
+func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*migration.Snapshot, conn *websocket.Conn, srcIdmap *idmap.IdmapSet, op *operation, containerOnly bool) error {
 	poolName := s.getOnDiskPoolName()
 	zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
 		zfsFsName := fmt.Sprintf("%s/%s", poolName, zfsName)
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 598d77113..4f9d0c892 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -72,6 +72,7 @@ test_static_analysis() {
       golint -set_exit_status lxd/debug
       golint -set_exit_status lxd/endpoints
       golint -set_exit_status lxd/maas
+      golint -set_exit_status lxd/migration
       golint -set_exit_status lxd/node
       golint -set_exit_status lxd/state
       golint -set_exit_status lxd/sys

From 51eb1f60363f524daa87fcbe4fdce39ffc0c3d28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Feb 2018 00:26:15 -0500
Subject: [PATCH 4/7] lxd/daemon: Cleanup startup 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/main_daemon.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index 4b0948544..eec8d9e4e 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -24,23 +24,24 @@ func cmdDaemon(args *Args) error {
 		dbg.Memory(args.MemProfile),
 		dbg.Goroutines(args.PrintGoroutinesEvery),
 	)
-	defer stop()
 	if err != nil {
-		fmt.Printf("%v\n", err)
-		return nil
+		return err
 	}
 
+	defer stop()
+
 	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
 	for _, p := range neededPrograms {
 		_, err := exec.LookPath(p)
 		if err != nil {
 			return err
 		}
-
 	}
+
 	c := &DaemonConfig{
 		Group: args.Group,
 	}
+
 	d := NewDaemon(c, sys.DefaultOS())
 	err = d.Init()
 	if err != nil {
@@ -56,7 +57,6 @@ func cmdDaemon(args *Args) error {
 	s := d.State()
 	select {
 	case sig := <-ch:
-
 		if sig == syscall.SIGPWR {
 			logger.Infof("Received '%s signal', shutting down containers.", sig)
 			containersShutdown(s)

From d3b81aab6c4abfd6540ad3a5a4434501727061d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Feb 2018 01:11:45 -0500
Subject: [PATCH 5/7] lxc: Introduce a new utils 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>
---
 lxc/copy.go                    |   3 +-
 lxc/image.go                   |  11 +--
 lxc/init.go                    |   5 +-
 lxc/storage.go                 |   3 +-
 lxc/utils.go                   | 153 -----------------------------------------
 lxc/utils/cancel.go            |  48 +++++++++++++
 lxc/utils/progress.go          | 128 ++++++++++++++++++++++++++++++++++
 test/suites/static_analysis.sh |   1 +
 8 files changed, 190 insertions(+), 162 deletions(-)
 create mode 100644 lxc/utils/cancel.go
 create mode 100644 lxc/utils/progress.go

diff --git a/lxc/copy.go b/lxc/copy.go
index 85acaeb63..8b68143ee 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/lxc/utils"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -195,7 +196,7 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string,
 	}
 
 	// Watch the background operation
-	progress := progressRenderer{Format: i18n.G("Transferring container: %s")}
+	progress := utils.ProgressRenderer{Format: i18n.G("Transferring container: %s")}
 	_, err = op.AddHandler(progress.UpdateOp)
 	if err != nil {
 		progress.Done("")
diff --git a/lxc/image.go b/lxc/image.go
index eaccbb231..eb104ec02 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -18,6 +18,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/lxc/utils"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -445,7 +446,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		// Register progress handler
-		progress := progressRenderer{Format: i18n.G("Copying the image: %s")}
+		progress := utils.ProgressRenderer{Format: i18n.G("Copying the image: %s")}
 		_, err = op.AddHandler(progress.UpdateOp)
 		if err != nil {
 			progress.Done("")
@@ -453,7 +454,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		// Wait for operation to finish
-		err = cancelableWait(op, &progress)
+		err = utils.CancelableWait(op, &progress)
 		if err != nil {
 			progress.Done("")
 			return err
@@ -525,7 +526,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			}
 
 			image := c.dereferenceAlias(d, inName)
-			progress := progressRenderer{Format: i18n.G("Refreshing the image: %s")}
+			progress := utils.ProgressRenderer{Format: i18n.G("Refreshing the image: %s")}
 			op, err := d.RefreshImage(image)
 			if err != nil {
 				return err
@@ -713,7 +714,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			image.Properties[strings.TrimSpace(fields[0])] = strings.TrimSpace(fields[1])
 		}
 
-		progress := progressRenderer{Format: i18n.G("Transferring image: %s")}
+		progress := utils.ProgressRenderer{Format: i18n.G("Transferring image: %s")}
 		if strings.HasPrefix(imageFile, "https://") {
 			image.Source = &api.ImagesPostSource{}
 			image.Source.Type = "url"
@@ -918,7 +919,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		defer destRootfs.Close()
 
 		// Prepare the download request
-		progress := progressRenderer{Format: i18n.G("Exporting the image: %s")}
+		progress := utils.ProgressRenderer{Format: i18n.G("Exporting the image: %s")}
 		req := lxd.ImageFileRequest{
 			MetaFile:        io.WriteSeeker(dest),
 			RootfsFile:      io.WriteSeeker(destRootfs),
diff --git a/lxc/init.go b/lxc/init.go
index 4b8fcf3fc..611aeb2d9 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/lxc/utils"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -286,14 +287,14 @@ func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServe
 	}
 
 	// Watch the background operation
-	progress := progressRenderer{Format: i18n.G("Retrieving image: %s")}
+	progress := utils.ProgressRenderer{Format: i18n.G("Retrieving image: %s")}
 	_, err = op.AddHandler(progress.UpdateOp)
 	if err != nil {
 		progress.Done("")
 		return nil, "", err
 	}
 
-	err = cancelableWait(op, &progress)
+	err = utils.CancelableWait(op, &progress)
 	if err != nil {
 		progress.Done("")
 		return nil, "", err
diff --git a/lxc/storage.go b/lxc/storage.go
index 735160743..c59598eb9 100644
--- a/lxc/storage.go
+++ b/lxc/storage.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/lxc/utils"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -1071,7 +1072,7 @@ func (c *storageCmd) doStoragePoolVolumeCopy(client lxd.ContainerServer, src str
 		return err
 	}
 	// Register progress handler
-	progress := progressRenderer{Format: opMsg}
+	progress := utils.ProgressRenderer{Format: opMsg}
 	_, err = op.AddHandler(progress.UpdateOp)
 	if err != nil {
 		progress.Done("")
diff --git a/lxc/utils.go b/lxc/utils.go
index 97d367249..47d0573ae 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -5,17 +5,13 @@ import (
 	"net"
 	"net/url"
 	"os"
-	"os/signal"
 	"sort"
 	"strings"
-	"sync"
 	"syscall"
-	"time"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
-	"github.com/lxc/lxd/shared/ioprogress"
 )
 
 // Lists
@@ -26,118 +22,6 @@ const (
 	listFormatYAML  = "yaml"
 )
 
-// Progress tracking
-type progressRenderer struct {
-	Format string
-
-	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"
-	}
-
-	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) {
-	// 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
-	}
-
-	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)
-}
-
-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)
-
-	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 ioprogress.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
-		}
-	}
-}
-
 type stringList [][]string
 
 func (a stringList) Len() int {
@@ -343,43 +227,6 @@ func profileDeviceAdd(client lxd.ContainerServer, name string, devName string, d
 	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"))
-			}
-
-			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)
-			}
-		}
-	}
-}
-
 // 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 {
diff --git a/lxc/utils/cancel.go b/lxc/utils/cancel.go
new file mode 100644
index 000000000..f1b065cbd
--- /dev/null
+++ b/lxc/utils/cancel.go
@@ -0,0 +1,48 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"time"
+
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/shared/i18n"
+)
+
+// CancelableWait waits 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"))
+			}
+
+			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)
+			}
+		}
+	}
+}
diff --git a/lxc/utils/progress.go b/lxc/utils/progress.go
new file mode 100644
index 000000000..1a781895a
--- /dev/null
+++ b/lxc/utils/progress.go
@@ -0,0 +1,128 @@
+package utils
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/ioprogress"
+)
+
+// ProgressRenderer tracks the progress information
+type ProgressRenderer struct {
+	Format string
+
+	maxLength int
+	wait      time.Time
+	done      bool
+	lock      sync.Mutex
+}
+
+// Done prints the final status and prevents any update
+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"
+	}
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print("\r")
+	fmt.Print(msg)
+}
+
+// Update changes the status message to the provided string
+func (p *ProgressRenderer) Update(status string) {
+	// 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
+	}
+
+	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)
+}
+
+// Warn shows a temporary message instead of the status
+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)
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
+
+// UpdateProgress is a helper to update the status using an iopgress instance
+func (p *ProgressRenderer) UpdateProgress(progress ioprogress.ProgressData) {
+	p.Update(progress.Text)
+}
+
+// UpdateOp is a helper to update the status using a LXD API operation
+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
+		}
+	}
+}
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 4f9d0c892..a3cbb96ff 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -61,6 +61,7 @@ test_static_analysis() {
 
       golint -set_exit_status lxc/
       golint -set_exit_status lxc/config/
+      golint -set_exit_status lxc/utils/
 
       golint -set_exit_status lxd-benchmark
       golint -set_exit_status lxd-benchmark/benchmark

From 6a3c2cc0e262149c8747a086804ac8191688a940 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 9 Feb 2018 21:27:00 -0500
Subject: [PATCH 6/7] lxd-p2c: Add new tool
MIME-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-p2c/main.go                |  69 ++++++++++++++++
 lxd-p2c/main_migrate.go        | 172 +++++++++++++++++++++++++++++++++++++++
 lxd-p2c/main_netcat.go         |  61 ++++++++++++++
 lxd-p2c/setns.go               |  33 ++++++++
 lxd-p2c/transfer.go            | 107 ++++++++++++++++++++++++
 lxd-p2c/utils.go               | 180 +++++++++++++++++++++++++++++++++++++++++
 test/suites/static_analysis.sh |   2 +
 7 files changed, 624 insertions(+)
 create mode 100644 lxd-p2c/main.go
 create mode 100644 lxd-p2c/main_migrate.go
 create mode 100644 lxd-p2c/main_netcat.go
 create mode 100644 lxd-p2c/setns.go
 create mode 100644 lxd-p2c/transfer.go
 create mode 100644 lxd-p2c/utils.go

diff --git a/lxd-p2c/main.go b/lxd-p2c/main.go
new file mode 100644
index 000000000..5a9d71340
--- /dev/null
+++ b/lxd-p2c/main.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+
+	"github.com/lxc/lxd/shared/version"
+)
+
+type cmdGlobal struct {
+	flagVersion bool
+	flagHelp    bool
+}
+
+func main() {
+	app := &cobra.Command{}
+	app.Use = "lxd-p2c"
+	app.Short = "Physical to container migration tool"
+	app.Long = `Description:
+  Physical to container migration tool
+
+  This tool lets you turn any Linux filesystem (including your current one)
+  into a LXD container on a remote LXD host.
+
+  It will setup a clean mount tree made of the root filesystem and any
+  additional mount you list, then transfer this through LXD's migration
+  API to create a new container from it.
+
+  The same set of options as ` + "`lxc launch`" + ` are also supported.
+`
+	app.SilenceUsage = true
+
+	// Global flags
+	globalCmd := cmdGlobal{}
+	app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, "Print version number")
+	app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help")
+
+	// Version handling
+	app.SetVersionTemplate("{{.Version}}\n")
+	app.Version = version.Version
+
+	// migrate command (main)
+	migrateCmd := cmdMigrate{global: &globalCmd}
+	app.Flags().StringArrayVarP(&migrateCmd.flagConfig, "config", "c", nil, "Configuration key and value to set on the container"+"``")
+	app.Flags().StringVarP(&migrateCmd.flagNetwork, "network", "n", "", "Network to use for the container"+"``")
+	app.Flags().StringArrayVarP(&migrateCmd.flagProfile, "profile", "p", nil, "Profile to apply to the container"+"``")
+	app.Flags().StringVarP(&migrateCmd.flagStorage, "storage", "s", "", "Storage pool to use for the container"+"``")
+	app.Flags().StringVarP(&migrateCmd.flagType, "type", "t", "", "Instance type to use for the container"+"``")
+	app.Flags().BoolVar(&migrateCmd.flagNoProfiles, "no-profiles", false, "Create the container with no profiles applied")
+	app.Use = "lxd-p2c <target URL> <container name> <filesystem root> [<filesystem mounts>...]"
+	app.RunE = migrateCmd.Run
+	app.Args = cobra.ArbitraryArgs
+
+	// netcat sub-command
+	netcatCmd := cmdNetcat{global: &globalCmd}
+	appNetcat := &cobra.Command{}
+	appNetcat.Use = "netcat <address>"
+	appNetcat.Hidden = true
+	appNetcat.Short = "Sends stdin data to a unix socket"
+	appNetcat.RunE = netcatCmd.Run
+	app.AddCommand(appNetcat)
+
+	// Run the main command and handle errors
+	err := app.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
diff --git a/lxd-p2c/main_migrate.go b/lxd-p2c/main_migrate.go
new file mode 100644
index 000000000..660ac2437
--- /dev/null
+++ b/lxd-p2c/main_migrate.go
@@ -0,0 +1,172 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"sort"
+	"strings"
+	"syscall"
+
+	"github.com/spf13/cobra"
+
+	"github.com/lxc/lxd/lxc/utils"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/osarch"
+)
+
+type cmdMigrate struct {
+	global *cmdGlobal
+
+	flagConfig     []string
+	flagNetwork    string
+	flagProfile    []string
+	flagStorage    string
+	flagType       string
+	flagNoProfiles bool
+}
+
+func (c *cmdMigrate) Run(cmd *cobra.Command, args []string) error {
+	// Help and usage
+	if len(args) == 0 {
+		return cmd.Help()
+	}
+
+	// Sanity checks
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This tool must be run as root")
+	}
+
+	_, err := exec.LookPath("rsync")
+	if err != nil {
+		return err
+	}
+
+	if c.flagNoProfiles && len(c.flagProfile) != 0 {
+		return fmt.Errorf("no-profiles can't be specified alongside profiles")
+	}
+
+	// Handle mandatory arguments
+	if len(args) < 3 {
+		cmd.Help()
+		return fmt.Errorf("Missing required arguments")
+	}
+
+	// Get and sort the mounts
+	mounts := args[2:]
+	sort.Strings(mounts)
+
+	// Create the temporary directory to be used for the mounts
+	path, err := ioutil.TempDir("", "lxd-p2c_mount_")
+	if err != nil {
+		return err
+	}
+
+	// Automatically clean-up the temporary path on exit
+	defer func(path string) {
+		syscall.Unmount(path, syscall.MNT_DETACH)
+		os.Remove(path)
+	}(path)
+
+	// Create the rootfs directory
+	fullPath := fmt.Sprintf("%s/rootfs", path)
+	err = os.Mkdir(fullPath, 0755)
+	if err != nil {
+		return err
+	}
+
+	// Setup the source (mounts)
+	err = setupSource(fullPath, mounts)
+	if err != nil {
+		return fmt.Errorf("Failed to setup the source: %v", err)
+	}
+
+	// Connect to the target
+	dst, err := connectTarget(args[0])
+	if err != nil {
+		return err
+	}
+
+	// Container creation request
+	apiArgs := api.ContainersPost{}
+	apiArgs.Name = args[1]
+	apiArgs.Source = api.ContainerSource{
+		Type: "migration",
+		Mode: "push",
+	}
+
+	// System architecture
+	architectureName, err := osarch.ArchitectureGetLocal()
+	if err != nil {
+		return err
+	}
+	apiArgs.Architecture = architectureName
+
+	// Instance type
+	apiArgs.InstanceType = c.flagType
+
+	// Config overrides
+	apiArgs.Config = map[string]string{}
+	for _, entry := range c.flagConfig {
+		if !strings.Contains(entry, "=") {
+			return fmt.Errorf("Bad key=value configuration: %v", entry)
+		}
+
+		fields := strings.SplitN(entry, "=", 2)
+		apiArgs.Config[fields[0]] = fields[1]
+	}
+
+	// Profiles
+	if len(c.flagProfile) != 0 {
+		apiArgs.Profiles = c.flagProfile
+	}
+
+	if c.flagNoProfiles {
+		apiArgs.Profiles = []string{}
+	}
+
+	// Devices
+	apiArgs.Devices = map[string]map[string]string{}
+
+	network := c.flagNetwork
+	if network != "" {
+		apiArgs.Devices["eth0"] = map[string]string{
+			"type":    "nic",
+			"nictype": "bridged",
+			"parent":  network,
+			"name":    "eth0",
+		}
+	}
+
+	storage := c.flagStorage
+	if network != "" {
+		apiArgs.Devices["root"] = map[string]string{
+			"type": "disk",
+			"pool": storage,
+			"path": "/",
+		}
+	}
+
+	// Create the container
+	op, err := dst.CreateContainer(apiArgs)
+	if err != nil {
+		return err
+	}
+
+	progress := utils.ProgressRenderer{Format: "Transferring container: %s"}
+	_, err = op.AddHandler(progress.UpdateOp)
+	if err != nil {
+		progress.Done("")
+		return err
+	}
+
+	err = transferRootfs(dst, op, fullPath)
+	if err != nil {
+		return err
+	}
+
+	progress.Done(fmt.Sprintf("Container %s successfully created", apiArgs.Name))
+
+	return nil
+}
diff --git a/lxd-p2c/main_netcat.go b/lxd-p2c/main_netcat.go
new file mode 100644
index 000000000..20350a27d
--- /dev/null
+++ b/lxd-p2c/main_netcat.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"sync"
+
+	"github.com/spf13/cobra"
+
+	"github.com/lxc/lxd/shared/eagain"
+)
+
+type cmdNetcat struct {
+	global *cmdGlobal
+}
+
+func (c *cmdNetcat) Run(cmd *cobra.Command, args []string) error {
+	// Help and usage
+	if len(args) == 0 {
+		cmd.Help()
+		return nil
+	}
+
+	// Handle mandatory arguments
+	if len(args) != 1 {
+		cmd.Help()
+		return fmt.Errorf("Missing required argument")
+	}
+
+	// Connect to the provided address
+	uAddr, err := net.ResolveUnixAddr("unix", args[0])
+	if err != nil {
+		return err
+	}
+
+	conn, err := net.DialUnix("unix", nil, uAddr)
+	if err != nil {
+		return err
+	}
+
+	// We'll wait until we're done reading from the socket
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		io.Copy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: conn})
+		conn.Close()
+		wg.Done()
+	}()
+
+	go func() {
+		io.Copy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: os.Stdin})
+	}()
+
+	// Wait
+	wg.Wait()
+
+	return nil
+}
diff --git a/lxd-p2c/setns.go b/lxd-p2c/setns.go
new file mode 100644
index 000000000..cd9ef1dc0
--- /dev/null
+++ b/lxd-p2c/setns.go
@@ -0,0 +1,33 @@
+package main
+
+/*
+#define _GNU_SOURCE
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+__attribute__((constructor)) void init(void) {
+	if (geteuid() != 0) {
+		return;
+	}
+
+	// Unshare a new mntns so our mounts don't leak
+	if (unshare(CLONE_NEWNS) < 0) {
+		fprintf(stderr, "Failed to unshare a new mount namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	// Prevent mount propagation back to initial namespace
+	if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) {
+		fprintf(stderr, "Failed to mark / private: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	// We're done, jump back to Go
+}
+*/
+import "C"
diff --git a/lxd-p2c/transfer.go b/lxd-p2c/transfer.go
new file mode 100644
index 000000000..45a4eadcb
--- /dev/null
+++ b/lxd-p2c/transfer.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+
+	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
+
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/shared"
+)
+
+// Send an rsync stream of a path over a websocket
+func rsyncSend(conn *websocket.Conn, path string) error {
+	cmd, dataSocket, stderr, err := rsyncSendSetup(path)
+	if err != nil {
+		return err
+	}
+
+	if dataSocket != nil {
+		defer dataSocket.Close()
+	}
+
+	readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, io.ReadCloser(dataSocket), nil, nil)
+
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
+		return fmt.Errorf("Failed to rsync: %v\n%s", err, output)
+	}
+
+	err = cmd.Wait()
+	<-readDone
+	<-writeDone
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Spawn the rsync process
+func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
+	auds := fmt.Sprintf("@lxd-p2c/%s", uuid.NewRandom().String())
+	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
+	}
+
+	execPath, err := os.Readlink("/proc/self/exe")
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, auds)
+
+	cmd := exec.Command("rsync",
+		"-arvP",
+		"--devices",
+		"--numeric-ids",
+		"--partial",
+		"--sparse",
+		path,
+		"localhost:/tmp/foo",
+		"-e",
+		rsyncCmd)
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	if err := cmd.Start(); err != nil {
+		return nil, nil, nil, err
+	}
+
+	conn, err := l.Accept()
+	if err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
+		return nil, nil, nil, err
+	}
+	l.Close()
+
+	return cmd, conn, stderr, nil
+}
+
+func protoSendError(ws *websocket.Conn, err error) {
+	migration.ProtoSendControl(ws, err)
+
+	if err != nil {
+		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+		ws.WriteMessage(websocket.CloseMessage, closeMsg)
+		ws.Close()
+	}
+}
diff --git a/lxd-p2c/utils.go b/lxd-p2c/utils.go
new file mode 100644
index 000000000..dda9f2954
--- /dev/null
+++ b/lxd-p2c/utils.go
@@ -0,0 +1,180 @@
+package main
+
+import (
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"strings"
+	"syscall"
+
+	"golang.org/x/crypto/ssh/terminal"
+
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+func transferRootfs(dst lxd.ContainerServer, op *lxd.Operation, rootfs string) error {
+	// Connect to the websockets
+	wsControl, err := dst.GetOperationWebsocket(op.ID, op.Metadata["control"].(string))
+	if err != nil {
+		return err
+	}
+
+	wsFs, err := dst.GetOperationWebsocket(op.ID, op.Metadata["fs"].(string))
+	if err != nil {
+		return err
+	}
+
+	// Setup control struct
+	fs := migration.MigrationFSType_RSYNC
+	header := migration.MigrationHeader{
+		Fs: &fs,
+	}
+
+	err = migration.ProtoSend(wsControl, &header)
+	if err != nil {
+		protoSendError(wsControl, err)
+		return err
+	}
+
+	err = migration.ProtoRecv(wsControl, &header)
+	if err != nil {
+		protoSendError(wsControl, err)
+		return err
+	}
+
+	// Send the filesystem
+	abort := func(err error) error {
+		protoSendError(wsControl, err)
+		return err
+	}
+
+	err = rsyncSend(wsFs, rootfs)
+	if err != nil {
+		return abort(err)
+	}
+
+	// Check the result
+	msg := migration.MigrationControl{}
+	err = migration.ProtoRecv(wsControl, &msg)
+	if err != nil {
+		wsControl.Close()
+		return err
+	}
+
+	if !*msg.Success {
+		return fmt.Errorf(*msg.Message)
+	}
+
+	return nil
+}
+
+func connectTarget(url string) (lxd.ContainerServer, error) {
+	// Generate a new client certificate for this
+	fmt.Println("Generating a temporary client certificate. This may take a minute...")
+	clientCrt, clientKey, err := shared.GenerateMemCert(true)
+	if err != nil {
+		return nil, err
+	}
+
+	// Attempt to connect using the system CA
+	args := lxd.ConnectionArgs{}
+	args.TLSClientCert = string(clientCrt)
+	args.TLSClientKey = string(clientKey)
+	args.UserAgent = "LXD-P2C"
+	c, err := lxd.ConnectLXD(url, &args)
+
+	var certificate *x509.Certificate
+	if err != nil {
+		// Failed to connect using the system CA, so retrieve the remote certificate
+		certificate, err = shared.GetRemoteCertificate(url)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Handle certificate prompt
+	if certificate != nil {
+		digest := shared.CertFingerprint(certificate)
+
+		fmt.Printf("Certificate fingerprint: %s\n", digest)
+		fmt.Printf("ok (y/n)? ")
+		line, err := shared.ReadStdin()
+		if err != nil {
+			return nil, err
+		}
+
+		if len(line) < 1 || line[0] != 'y' && line[0] != 'Y' {
+			return nil, fmt.Errorf("Server certificate rejected by user")
+		}
+
+		serverCrt := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})
+		args.TLSServerCert = string(serverCrt)
+
+		// Setup a new connection, this time with the remote certificate
+		c, err = lxd.ConnectLXD(url, &args)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Get server information
+	srv, _, err := c.GetServer()
+	if err != nil {
+		return nil, err
+	}
+
+	// Check if our cert is already trusted
+	if srv.Auth == "trusted" {
+		return c, nil
+	}
+
+	// Prompt for trust password
+	fmt.Printf("Admin password for %s: ", url)
+	pwd, err := terminal.ReadPassword(0)
+	if err != nil {
+		return nil, err
+	}
+	fmt.Println("")
+
+	// Add client certificate to trust store
+	req := api.CertificatesPost{
+		Password: string(pwd),
+	}
+	req.Type = "client"
+
+	err = c.CreateCertificate(req)
+	if err != nil {
+		return nil, err
+	}
+
+	return c, nil
+}
+
+func setupSource(path string, mounts []string) error {
+	prefix := "/"
+	if len(mounts) > 0 {
+		prefix = mounts[0]
+	}
+
+	// Mount everything
+	for _, mount := range mounts {
+		target := fmt.Sprintf("%s/%s", path, strings.TrimPrefix(mount, prefix))
+
+		// Mount the path
+		err := syscall.Mount(mount, target, "none", syscall.MS_BIND, "")
+		if err != nil {
+			return fmt.Errorf("Failed to mount %s: %v", mount, err)
+		}
+
+		// Make it read-only
+		err = syscall.Mount("", target, "none", syscall.MS_BIND|syscall.MS_RDONLY|syscall.MS_REMOUNT, "")
+		if err != nil {
+			return fmt.Errorf("Failed to make %s read-only: %v", mount, err)
+		}
+	}
+
+	return nil
+}
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index a3cbb96ff..aff7eeafa 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -66,6 +66,8 @@ test_static_analysis() {
       golint -set_exit_status lxd-benchmark
       golint -set_exit_status lxd-benchmark/benchmark
 
+      golint -set_exit_status lxd-p2c
+
       golint -set_exit_status lxd/config
       golint -set_exit_status lxd/db/node
       golint -set_exit_status lxd/db/query

From b986027e8d3a89381eae07ae22c900fcd2b0763c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Feb 2018 03:13:28 -0500
Subject: [PATCH 7/7] i18n: Look at all lxc 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>
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 9d7a37226..297f6bfd1 100644
--- a/Makefile
+++ b/Makefile
@@ -101,7 +101,7 @@ update-po:
 
 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 shared/*.go lxc/*.go lxd/*.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 lxc/*.go lxc/*/*.go
 
 build-mo: $(MOFILES)
 


More information about the lxc-devel mailing list