The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4272

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From 1551e180ab6d1808ab3cff15b4ac56088a4e9bbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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@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@lists.linuxcontainers.org --keyword=i18n.G 
--keyword-plural=i18n.NG lxc/*.go lxc/*/*.go
 
 build-mo: $(MOFILES)
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to