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

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) ===
Adds support for migrating BTRFS containers that have subvolumes.

WIP.
From 62ae28941db7a0674692581b420f58fc5580df3d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 4 May 2020 10:23:08 +0100
Subject: [PATCH 01/15] lxd/storage/driver/driver/btrfs/utils: Updates
 snapshotSubvolume to handle sub volumes

Marks snapshotted sub volumes and the parent volume as readonly when in 
recursive mode and readonly argument passed.
Returns list of snapshot volumes.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_utils.go | 42 +++++++++++++++--------
 1 file changed, 28 insertions(+), 14 deletions(-)

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index 5b30f043c4..126832c026 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -86,10 +86,12 @@ func (d *btrfs) getSubvolumes(path string) ([]string, 
error) {
        return result, nil
 }
 
+// snapshotSubvolume creates a snapshot of the specified path at the dest 
supplied. If recursion is true and
+// sub volumes are found below the path then they are created at the relative 
location in dest.
 func (d *btrfs) snapshotSubvolume(path string, dest string, readonly bool, 
recursion bool) error {
        // Single subvolume deletion.
-       snapshot := func(path string, dest string) error {
-               if readonly && !d.state.OS.RunningInUserNS {
+       snapshot := func(path string, dest string, ro bool) error {
+               if ro && !d.state.OS.RunningInUserNS {
                        _, err := shared.RunCommand("btrfs", "subvolume", 
"snapshot", "-r", path, dest)
                        if err != nil {
                                return err
@@ -109,28 +111,40 @@ func (d *btrfs) snapshotSubvolume(path string, dest 
string, readonly bool, recur
        // Now snapshot all subvolumes of the root.
        if recursion {
                // Get the subvolumes list.
-               subsubvols, err := d.getSubvolumes(path)
+               subSubVols, err := d.getSubvolumes(path)
                if err != nil {
                        return err
                }
-               sort.Sort(sort.StringSlice(subsubvols))
-
-               if len(subsubvols) > 0 && readonly {
-                       // Creating subvolumes requires the parent to be 
writable.
-                       readonly = false
+               sort.Sort(sort.StringSlice(subSubVols))
+
+               if len(subSubVols) > 0 && readonly {
+                       // First snapshot the root volume. Creating subvolumes 
requires the parent to be writable.
+                       err = snapshot(path, dest, false)
+               } else {
+                       // First snapshot the root volume.
+                       err = snapshot(path, dest, readonly)
                }
 
-               // First snapshot the root.
-               err = snapshot(path, dest)
+               // Check errors from snapshot.
                if err != nil {
                        return err
                }
 
-               for _, subsubvol := range subsubvols {
+               for _, subSubVol := range subSubVols {
+                       subSubVolSnapPath := filepath.Join(dest, subSubVol)
+
                        // Clear the target for the subvol to use.
-                       os.Remove(filepath.Join(dest, subsubvol))
+                       os.Remove(subSubVolSnapPath)
+
+                       err := snapshot(filepath.Join(path, subSubVol), 
subSubVolSnapPath, readonly)
+                       if err != nil {
+                               return err
+                       }
+               }
 
-                       err := snapshot(filepath.Join(path, subsubvol), 
filepath.Join(dest, subsubvol))
+               // Now set the root snapshot we created earlier as writable 
read only.
+               if len(subSubVols) > 0 && readonly {
+                       _, err = shared.RunCommand("btrfs", "property", "set", 
"-ts", dest, "ro", "true")
                        if err != nil {
                                return err
                        }
@@ -140,7 +154,7 @@ func (d *btrfs) snapshotSubvolume(path string, dest string, 
readonly bool, recur
        }
 
        // Handle standalone volume.
-       err := snapshot(path, dest)
+       err := snapshot(path, dest, readonly)
        if err != nil {
                return err
        }

From e36d81e9949f31d9817a58182c3a623cb07be4e3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 4 May 2020 10:27:01 +0100
Subject: [PATCH 02/15] lxd/storag/drivers/driver/btrfs/volumes: Updates
 MigrateVolume to send subvolumes

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_volumes.go | 68 +++++++++++++++++----
 1 file changed, 56 insertions(+), 12 deletions(-)

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
index 9048a01f4c..4d72b61b4e 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -2,6 +2,7 @@ package drivers
 
 import (
        "context"
+       "encoding/json"
        "fmt"
        "io"
        "io/ioutil"
@@ -606,6 +607,40 @@ func (d *btrfs) MigrateVolume(vol Volume, conn 
io.ReadWriteCloser, volSrcArgs *m
                return nil
        }
 
+       // Generate migration header, containing subvolume info.
+       migrationHeader, err := d.migrationHeader(vol, volSrcArgs.Snapshots)
+       if err != nil {
+               return err
+       }
+
+       // If we haven't negotiated subvolume support, check if we have any 
subvolumes in source and fail,
+       // otherwise we would end up not materialising all of the source's 
files on the target.
+       if !shared.StringInSlice(migration.BTRFSFeatureMigrationHeader, 
volSrcArgs.MigrationType.Features) || 
!shared.StringInSlice(migration.BTRFSFeatureSubvolumes, 
volSrcArgs.MigrationType.Features) {
+               for _, subVol := range migrationHeader.Subvolumes {
+                       if subVol.Path != string(filepath.Separator) {
+                               return fmt.Errorf("Subvolumes detected in 
source but target does not support receiving subvolumes")
+                       }
+               }
+       }
+
+       // Send metadata migration header frame with subvolume info if we have 
negotiated that feature.
+       if shared.StringInSlice(migration.BTRFSFeatureMigrationHeader, 
volSrcArgs.MigrationType.Features) {
+               headerJSON, err := json.Marshal(migrationHeader)
+               if err != nil {
+                       return err
+               }
+
+               _, err = conn.Write(headerJSON)
+               if err != nil {
+                       return err
+               }
+
+               err = conn.Close() //End the frame.
+               if err != nil {
+                       return err
+               }
+       }
+
        // Transfer the snapshots first.
        for i, snapName := range volSrcArgs.Snapshots {
                snapshot, _ := vol.NewSnapshot(snapName)
@@ -645,12 +680,12 @@ func (d *btrfs) MigrateVolume(vol Volume, conn 
io.ReadWriteCloser, volSrcArgs *m
        }
 
        // Make read-only snapshot of the subvolume as writable subvolumes 
cannot be sent.
-       migrationSendSnapshot := filepath.Join(tmpVolumesMountPoint, 
".migration-send")
-       err = d.snapshotSubvolume(vol.MountPath(), migrationSendSnapshot, true, 
false)
+       migrationSendSnapshotPrefix := filepath.Join(tmpVolumesMountPoint, 
".migration-send")
+       err = d.snapshotSubvolume(vol.MountPath(), migrationSendSnapshotPrefix, 
true, true)
        if err != nil {
                return err
        }
-       defer d.deleteSubvolume(migrationSendSnapshot, true)
+       defer d.deleteSubvolume(migrationSendSnapshotPrefix, true)
 
        // Setup progress tracking.
        var wrapper *ioprogress.ProgressTracker
@@ -658,16 +693,25 @@ func (d *btrfs) MigrateVolume(vol Volume, conn 
io.ReadWriteCloser, volSrcArgs *m
                wrapper = migration.ProgressTracker(op, "fs_progress", vol.name)
        }
 
-       // Compare to latest snapshot.
-       btrfsParent := ""
-       if len(volSrcArgs.Snapshots) > 0 {
-               btrfsParent = GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, 
volSrcArgs.Snapshots[len(volSrcArgs.Snapshots)-1]))
-       }
+       // Send main volume (and any subvolumes) to target.
+       for _, subVolume := range migrationHeader.Subvolumes {
+               if subVolume.Snapshot != "" {
+                       continue // Only sending main volume now (not 
snapshots).
+               }
 
-       // Send the volume itself.
-       err = d.sendSubvolume(migrationSendSnapshot, btrfsParent, conn, wrapper)
-       if err != nil {
-               return err
+               parent := ""
+
+               if subVolume.Path == string(filepath.Separator) && 
len(volSrcArgs.Snapshots) > 0 {
+                       // Compare to latest snapshot.
+                       parent = GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, 
volSrcArgs.Snapshots[len(volSrcArgs.Snapshots)-1]))
+               }
+
+               source := filepath.Join(migrationSendSnapshotPrefix, 
subVolume.Path)
+               d.logger.Debug("Sending subvolume snapshot", log.Ctx{"name": 
vol.name, "source": source, "parent": parent, "path": subVolume.Path})
+               err = d.sendSubvolume(source, parent, conn, wrapper)
+               if err != nil {
+                       return err
+               }
        }
 
        return nil

From cd4c96d970d61a42f73e28fef1028beb27d13b64 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 4 May 2020 12:21:13 +0100
Subject: [PATCH 03/15] lxd/storage/drivers/driver/btrfs/volumes: Fail backup
 when cleanup fails in BackupVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_volumes.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
index 4d72b61b4e..34beb638e4 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -874,6 +874,12 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter 
*instancewriter.InstanceTarWr
                return err
        }
 
+       // Ensure snapshot sub volumes are removed.
+       err = d.deleteSubvolume(targetVolume, true)
+       if err != nil {
+               return err
+       }
+
        return nil
 }
 

From 43f2ab1e5a6b0b8bbe4d9f746a934d2b1e319ba5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 4 May 2020 12:24:07 +0100
Subject: [PATCH 04/15] lxd/storage/drivers/driver/btrfs/volumes: Better naming
 of variables in unpackVolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_volumes.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
index 34beb638e4..0d5da1c297 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -135,8 +135,8 @@ func (d *btrfs) CreateVolumeFromBackup(vol Volume, 
snapshots []string, srcData i
        revert.Add(revertHook)
 
        // Define function to unpack a volume from a backup tarball file.
-       unpackVolume := func(r io.ReadSeeker, unpacker []string, srcFile 
string, mountPath string) error {
-               d.Logger().Debug("Unpacking optimized volume", 
log.Ctx{"source": srcFile, "target": mountPath})
+       unpackVolume := func(r io.ReadSeeker, unpacker []string, srcFile 
string, targetPath string) error {
+               d.Logger().Debug("Unpacking optimized volume", 
log.Ctx{"source": srcFile, "target": targetPath})
                tr, cancelFunc, err := 
shared.CompressedTarReader(context.Background(), r, unpacker)
                if err != nil {
                        return err
@@ -154,7 +154,7 @@ func (d *btrfs) CreateVolumeFromBackup(vol Volume, 
snapshots []string, srcData i
 
                        if hdr.Name == srcFile {
                                // Extract the backup.
-                               err = shared.RunCommandWithFds(tr, nil, 
"btrfs", "receive", "-e", mountPath)
+                               err = shared.RunCommandWithFds(tr, nil, 
"btrfs", "receive", "-e", targetPath)
                                if err != nil {
                                        return err
                                }

From 550ac94a193a5eaa5dc16b1343de238c3cadfcb0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Mon, 4 May 2020 12:24:34 +0100
Subject: [PATCH 05/15] lxd/storage/driversr/driver/btrfs/utils: Allow ro
 subvolumes to be deleted in deleteSubvolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_utils.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index 126832c026..bd48d3fc5a 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -193,6 +193,11 @@ func (d *btrfs) deleteSubvolume(path string, recursion 
bool) error {
                }
                sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
 
+               if len(subsubvols) > 0 {
+                       // Attempt to make the root subvolume writable so any 
subvolumes can be removed.
+                       shared.RunCommand("btrfs", "property", "set", path, 
"ro", "false")
+               }
+
                for _, subsubvol := range subsubvols {
                        err := destroy(filepath.Join(path, subsubvol))
                        if err != nil {

From a315873333767f69c70a394dc8ecc1e50993b69c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 11:44:02 +0100
Subject: [PATCH 06/15] lxd/migration/migrate/proto: Adds BTRFS Features to
 offer header

Adds following features:

 migration_header - indicates a migration header will be sent/recv in data 
channel first.
 header_subvolumes - indicates migration can send/recv subvolumes.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/migration/migrate.pb.go | 209 +++++++++++++++++++++++-------------
 lxd/migration/migrate.proto |   6 ++
 2 files changed, 140 insertions(+), 75 deletions(-)

diff --git a/lxd/migration/migrate.pb.go b/lxd/migration/migrate.pb.go
index cf1b5fbe4c..0e824a7be0 100644
--- a/lxd/migration/migrate.pb.go
+++ b/lxd/migration/migrate.pb.go
@@ -482,6 +482,53 @@ func (m *ZfsFeatures) GetCompress() bool {
        return false
 }
 
+type BtrfsFeatures struct {
+       MigrationHeader      *bool    
`protobuf:"varint,1,opt,name=migration_header,json=migrationHeader" 
json:"migration_header,omitempty"`
+       HeaderSubvolumes     *bool    
`protobuf:"varint,2,opt,name=header_subvolumes,json=headerSubvolumes" 
json:"header_subvolumes,omitempty"`
+       XXX_NoUnkeyedLiteral struct{} `json:"-"`
+       XXX_unrecognized     []byte   `json:"-"`
+       XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *BtrfsFeatures) Reset()         { *m = BtrfsFeatures{} }
+func (m *BtrfsFeatures) String() string { return proto.CompactTextString(m) }
+func (*BtrfsFeatures) ProtoMessage()    {}
+func (*BtrfsFeatures) Descriptor() ([]byte, []int) {
+       return fileDescriptor_fe8772548dc4b615, []int{6}
+}
+
+func (m *BtrfsFeatures) XXX_Unmarshal(b []byte) error {
+       return xxx_messageInfo_BtrfsFeatures.Unmarshal(m, b)
+}
+func (m *BtrfsFeatures) XXX_Marshal(b []byte, deterministic bool) ([]byte, 
error) {
+       return xxx_messageInfo_BtrfsFeatures.Marshal(b, m, deterministic)
+}
+func (m *BtrfsFeatures) XXX_Merge(src proto.Message) {
+       xxx_messageInfo_BtrfsFeatures.Merge(m, src)
+}
+func (m *BtrfsFeatures) XXX_Size() int {
+       return xxx_messageInfo_BtrfsFeatures.Size(m)
+}
+func (m *BtrfsFeatures) XXX_DiscardUnknown() {
+       xxx_messageInfo_BtrfsFeatures.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BtrfsFeatures proto.InternalMessageInfo
+
+func (m *BtrfsFeatures) GetMigrationHeader() bool {
+       if m != nil && m.MigrationHeader != nil {
+               return *m.MigrationHeader
+       }
+       return false
+}
+
+func (m *BtrfsFeatures) GetHeaderSubvolumes() bool {
+       if m != nil && m.HeaderSubvolumes != nil {
+               return *m.HeaderSubvolumes
+       }
+       return false
+}
+
 type MigrationHeader struct {
        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"`
@@ -493,6 +540,7 @@ type MigrationHeader struct {
        Refresh              *bool            
`protobuf:"varint,9,opt,name=refresh" json:"refresh,omitempty"`
        ZfsFeatures          *ZfsFeatures     
`protobuf:"bytes,10,opt,name=zfsFeatures" json:"zfsFeatures,omitempty"`
        VolumeSize           *int64           
`protobuf:"varint,11,opt,name=volumeSize" json:"volumeSize,omitempty"`
+       BtrfsFeatures        *BtrfsFeatures   
`protobuf:"bytes,12,opt,name=btrfsFeatures" json:"btrfsFeatures,omitempty"`
        XXX_NoUnkeyedLiteral struct{}         `json:"-"`
        XXX_unrecognized     []byte           `json:"-"`
        XXX_sizecache        int32            `json:"-"`
@@ -502,7 +550,7 @@ func (m *MigrationHeader) Reset()         { *m = 
MigrationHeader{} }
 func (m *MigrationHeader) String() string { return proto.CompactTextString(m) }
 func (*MigrationHeader) ProtoMessage()    {}
 func (*MigrationHeader) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{6}
+       return fileDescriptor_fe8772548dc4b615, []int{7}
 }
 
 func (m *MigrationHeader) XXX_Unmarshal(b []byte) error {
@@ -593,6 +641,13 @@ func (m *MigrationHeader) GetVolumeSize() int64 {
        return 0
 }
 
+func (m *MigrationHeader) GetBtrfsFeatures() *BtrfsFeatures {
+       if m != nil {
+               return m.BtrfsFeatures
+       }
+       return nil
+}
+
 type MigrationControl struct {
        Success *bool `protobuf:"varint,1,req,name=success" 
json:"success,omitempty"`
        // optional failure message if sending a failure
@@ -606,7 +661,7 @@ func (m *MigrationControl) Reset()         { *m = 
MigrationControl{} }
 func (m *MigrationControl) String() string { return proto.CompactTextString(m) 
}
 func (*MigrationControl) ProtoMessage()    {}
 func (*MigrationControl) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{7}
+       return fileDescriptor_fe8772548dc4b615, []int{8}
 }
 
 func (m *MigrationControl) XXX_Unmarshal(b []byte) error {
@@ -652,7 +707,7 @@ func (m *MigrationSync) Reset()         { *m = 
MigrationSync{} }
 func (m *MigrationSync) String() string { return proto.CompactTextString(m) }
 func (*MigrationSync) ProtoMessage()    {}
 func (*MigrationSync) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{8}
+       return fileDescriptor_fe8772548dc4b615, []int{9}
 }
 
 func (m *MigrationSync) XXX_Unmarshal(b []byte) error {
@@ -702,7 +757,7 @@ func (m *DumpStatsEntry) Reset()         { *m = 
DumpStatsEntry{} }
 func (m *DumpStatsEntry) String() string { return proto.CompactTextString(m) }
 func (*DumpStatsEntry) ProtoMessage()    {}
 func (*DumpStatsEntry) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{9}
+       return fileDescriptor_fe8772548dc4b615, []int{10}
 }
 
 func (m *DumpStatsEntry) XXX_Unmarshal(b []byte) error {
@@ -815,7 +870,7 @@ func (m *RestoreStatsEntry) Reset()         { *m = 
RestoreStatsEntry{} }
 func (m *RestoreStatsEntry) String() string { return 
proto.CompactTextString(m) }
 func (*RestoreStatsEntry) ProtoMessage()    {}
 func (*RestoreStatsEntry) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{10}
+       return fileDescriptor_fe8772548dc4b615, []int{11}
 }
 
 func (m *RestoreStatsEntry) XXX_Unmarshal(b []byte) error {
@@ -883,7 +938,7 @@ func (m *StatsEntry) Reset()         { *m = StatsEntry{} }
 func (m *StatsEntry) String() string { return proto.CompactTextString(m) }
 func (*StatsEntry) ProtoMessage()    {}
 func (*StatsEntry) Descriptor() ([]byte, []int) {
-       return fileDescriptor_fe8772548dc4b615, []int{11}
+       return fileDescriptor_fe8772548dc4b615, []int{12}
 }
 
 func (m *StatsEntry) XXX_Unmarshal(b []byte) error {
@@ -927,6 +982,7 @@ func init() {
        proto.RegisterType((*Snapshot)(nil), "migration.Snapshot")
        proto.RegisterType((*RsyncFeatures)(nil), "migration.rsyncFeatures")
        proto.RegisterType((*ZfsFeatures)(nil), "migration.zfsFeatures")
+       proto.RegisterType((*BtrfsFeatures)(nil), "migration.btrfsFeatures")
        proto.RegisterType((*MigrationHeader)(nil), "migration.MigrationHeader")
        proto.RegisterType((*MigrationControl)(nil), 
"migration.MigrationControl")
        proto.RegisterType((*MigrationSync)(nil), "migration.MigrationSync")
@@ -938,73 +994,76 @@ func init() {
 func init() { proto.RegisterFile("lxd/migration/migrate.proto", 
fileDescriptor_fe8772548dc4b615) }
 
 var fileDescriptor_fe8772548dc4b615 = []byte{
-       // 1080 bytes of a gzipped FileDescriptorProto
-       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 
0xcd, 0x6e, 0xdb, 0x46,
-       0x17, 0xfd, 0x24, 0x51, 0xb6, 0x78, 0x25, 0x39, 0xca, 0xc4, 0x08, 0x88, 
0xe4, 0x6b, 0xaa, 0x32,
-       0x29, 0xaa, 0x78, 0x61, 0xa7, 0x0a, 0x0a, 0x64, 0x55, 0x20, 0x96, 0xea, 
0x26, 0xa8, 0xa3, 0x18,
-       0x23, 0x1b, 0x45, 0xbb, 0x21, 0x26, 0xe4, 0xa5, 0x3c, 0x30, 0xff, 0x30, 
0x43, 0xd9, 0x96, 0x36,
-       0x45, 0x1f, 0xa6, 0x0f, 0xd1, 0xa7, 0xe8, 0xaa, 0xef, 0x53, 0xcc, 0x0c, 
0x49, 0x93, 0x4e, 0x81,
-       0xee, 0xe6, 0x9e, 0x7b, 0x78, 0xee, 0xcc, 0xfd, 0x23, 0x3c, 0x8d, 0x6e, 
0x83, 0xa3, 0x98, 0xaf,
-       0x04, 0xcb, 0x79, 0x9a, 0x14, 0x27, 0x3c, 0xcc, 0x44, 0x9a, 0xa7, 0xc4, 
0xae, 0x1c, 0xee, 0x6f,
-       0x60, 0xbf, 0x9f, 0x7f, 0x60, 0xd9, 0xf9, 0x26, 0x43, 0xb2, 0x0f, 0x5d, 
0x2e, 0xd7, 0x3c, 0x70,
-       0x5a, 0xe3, 0xf6, 0xa4, 0x47, 0x8d, 0x61, 0xd0, 0x15, 0x0f, 0x9c, 0x76, 
0x89, 0xae, 0x78, 0x40,
-       0x1e, 0xc3, 0xce, 0x65, 0x2a, 0x73, 0x1e, 0x38, 0x9d, 0x71, 0x7b, 0xd2, 
0xa5, 0x85, 0x45, 0x08,
-       0x58, 0x89, 0xe4, 0x81, 0x63, 0x69, 0x54, 0x9f, 0xc9, 0x13, 0xe8, 0xc5, 
0x2c, 0x13, 0x2c, 0x59,
-       0xa1, 0xd3, 0xd5, 0x78, 0x65, 0xbb, 0xaf, 0x60, 0x67, 0x96, 0x26, 0x21, 
0x5f, 0x91, 0x11, 0x74,
-       0xae, 0x70, 0xa3, 0x63, 0xdb, 0x54, 0x1d, 0x55, 0xe4, 0x6b, 0x16, 0xad, 
0x51, 0x47, 0xb6, 0xa9,
-       0x31, 0xdc, 0x1f, 0x61, 0x67, 0x8e, 0xd7, 0xdc, 0x47, 0x1d, 0x8b, 0xc5, 
0x58, 0x7c, 0xa2, 0xcf,
-       0xe4, 0x25, 0xec, 0xf8, 0x5a, 0xcf, 0x69, 0x8f, 0x3b, 0x93, 0xfe, 0xf4, 
0xe1, 0x61, 0xf5, 0xd8,
-       0x43, 0x13, 0x88, 0x16, 0x04, 0xf7, 0xaf, 0x36, 0xf4, 0x96, 0x09, 0xcb, 
0xe4, 0x65, 0x9a, 0xff,
-       0xab, 0xd6, 0x6b, 0xe8, 0x47, 0xa9, 0xcf, 0xa2, 0xd9, 0x7f, 0x08, 0xd6, 
0x59, 0xea, 0xb1, 0x99,
-       0x48, 0x43, 0x1e, 0xa1, 0x74, 0x3a, 0xe3, 0xce, 0xc4, 0xa6, 0x95, 0x4d, 
0xfe, 0x0f, 0x36, 0x66,
-       0x97, 0x18, 0xa3, 0x60, 0x91, 0xce, 0x50, 0x8f, 0xde, 0x01, 0xe4, 0x3b, 
0x18, 0x68, 0x21, 0xf3,
-       0x3a, 0xe9, 0x74, 0x3f, 0x8b, 0x67, 0x3c, 0xb4, 0x41, 0x23, 0x2e, 0x0c, 
0x98, 0xf0, 0x2f, 0x79,
-       0x8e, 0x7e, 0xbe, 0x16, 0xe8, 0xec, 0xe8, 0x0c, 0x37, 0x30, 0x75, 0x29, 
0x99, 0xb3, 0x1c, 0xc3,
-       0x75, 0xe4, 0xec, 0xea, 0xb8, 0x95, 0x4d, 0x9e, 0xc3, 0xd0, 0x17, 0xa8, 
0x03, 0x78, 0x01, 0xcb,
-       0xd1, 0xe9, 0x8d, 0x5b, 0x93, 0x0e, 0x1d, 0x94, 0xe0, 0x9c, 0xe5, 0x48, 
0x5e, 0xc0, 0x5e, 0xc4,
-       0x64, 0xee, 0xad, 0x25, 0x06, 0x86, 0x65, 0x1b, 0x96, 0x42, 0x2f, 0x24, 
0x06, 0x8a, 0xe5, 0xfe,
-       0xde, 0x82, 0xa1, 0x90, 0x9b, 0xc4, 0x3f, 0x41, 0xa6, 0xe2, 0x4a, 0xd5, 
0x26, 0xb7, 0x2c, 0xcf,
-       0x85, 0x74, 0x5a, 0xe3, 0xd6, 0xa4, 0x47, 0x0b, 0x4b, 0xe1, 0x01, 0x46, 
0x98, 0xab, 0xda, 0x6a,
-       0xdc, 0x58, 0xea, 0xa2, 0x7e, 0x1a, 0x67, 0x02, 0xa5, 0xca, 0x9e, 0xf2, 
0x54, 0x36, 0x79, 0x01,
-       0xc3, 0x4f, 0x3c, 0xe0, 0x02, 0x7d, 0x75, 0x2d, 0x9d, 0x41, 0x45, 0x68, 
0x82, 0xee, 0x4b, 0xe8,
-       0x6f, 0x43, 0x59, 0x5d, 0xa0, 0x2e, 0xd8, 0x6a, 0x0a, 0xba, 0x7f, 0x76, 
0xe0, 0xc1, 0x87, 0x32,
-       0xb9, 0xef, 0x90, 0x05, 0x28, 0xc8, 0x01, 0xb4, 0x43, 0xa9, 0xbb, 0x60, 
0x6f, 0xfa, 0xa4, 0x96,
-       0xfa, 0x8a, 0x77, 0xb2, 0x54, 0xb3, 0x42, 0xdb, 0xa1, 0x24, 0xdf, 0x80, 
0xe5, 0x0b, 0xbe, 0xd6,
-       0x4f, 0xd8, 0x9b, 0x3e, 0xaa, 0x37, 0x06, 0x7d, 0x7f, 0xa1, 0x69, 0x9a, 
0x40, 0x0e, 0xa0, 0xcb,
-       0x83, 0x98, 0x65, 0xba, 0x21, 0xfa, 0xd3, 0xfd, 0x1a, 0xb3, 0x9a, 0x3e, 
0x6a, 0x28, 0xea, 0x95,
-       0xb2, 0x68, 0xca, 0x05, 0x8b, 0x51, 0x3a, 0x96, 0x6e, 0xa2, 0x26, 0x48, 
0xbe, 0x05, 0xbb, 0x04,
-       0xca, 0x46, 0xa9, 0xc7, 0x2f, 0xdb, 0x9a, 0xde, 0xb1, 0x88, 0x03, 0xbb, 
0x99, 0xc0, 0x60, 0x1d,
-       0x67, 0xce, 0xae, 0x4e, 0x44, 0x69, 0x92, 0xef, 0xef, 0x55, 0x4d, 0x77, 
0x40, 0x7f, 0xea, 0xd4,
-       0x04, 0x1b, 0x7e, 0x7a, 0xaf, 0xc8, 0x0e, 0xec, 0x0a, 0x0c, 0x05, 0xca, 
0x4b, 0xdd, 0x15, 0x3d,
-       0x5a, 0x9a, 0xe4, 0x4d, 0xa3, 0x18, 0x0e, 0x68, 0xdd, 0xc7, 0x35, 0xdd, 
0x9a, 0x97, 0x36, 0xea,
-       0xf6, 0x0c, 0xe0, 0x3a, 0x8d, 0xd6, 0x31, 0x2e, 0xf9, 0x16, 0x9d, 0xbe, 
0x6e, 0xb6, 0x1a, 0xe2,
-       0x9e, 0xc0, 0xa8, 0x2a, 0xc9, 0x2c, 0x4d, 0x72, 0x91, 0x46, 0xea, 0x1e, 
0x72, 0xed, 0xfb, 0xa6,
-       0xd4, 0xaa, 0xc9, 0x4b, 0x53, 0x79, 0x62, 0x94, 0x92, 0xad, 0x4c, 0xbf, 
0xd9, 0xb4, 0x34, 0xdd,
-       0xd7, 0x30, 0xac, 0x74, 0x96, 0x9b, 0xc4, 0x57, 0xe3, 0x14, 0xf2, 0x84, 
0x45, 0x67, 0x02, 0xe7,
-       0x2a, 0x57, 0x46, 0xa9, 0x81, 0xb9, 0x7f, 0x74, 0x60, 0xa4, 0x32, 0xe7, 
0xa9, 0x21, 0x92, 0x1e,
-       0x26, 0xb9, 0xd8, 0xa8, 0x39, 0x0a, 0x05, 0xe2, 0x96, 0x27, 0x2b, 0x2f, 
0xe7, 0xc5, 0x2a, 0x19,
-       0xd2, 0x41, 0x09, 0x9e, 0xf3, 0x18, 0xc9, 0x97, 0xd0, 0x0f, 0x45, 0xba, 
0xc5, 0xc4, 0x50, 0xda,
-       0x9a, 0x02, 0x06, 0xd2, 0x84, 0xaf, 0x60, 0x10, 0x63, 0xac, 0xc5, 0x35, 
0xa3, 0xa3, 0x19, 0xfd,
-       0x02, 0xd3, 0x94, 0xe7, 0x30, 0x8c, 0x31, 0xbe, 0x11, 0x3c, 0x47, 0xc3, 
0xb1, 0x4c, 0xa0, 0x12,
-       0x2c, 0x49, 0x19, 0x5b, 0xa1, 0xf4, 0xa4, 0xcf, 0x92, 0x04, 0x03, 0xbd, 
0x78, 0x2d, 0x3a, 0xd0,
-       0xe0, 0xd2, 0x60, 0xe4, 0x15, 0xec, 0x17, 0xa4, 0x2b, 0x9e, 0x65, 0x18, 
0x78, 0x19, 0x13, 0x98,
-       0xe4, 0x7a, 0x85, 0x58, 0x94, 0x18, 0xae, 0x71, 0x9d, 0x69, 0xcf, 0x9d, 
0xac, 0x8a, 0x94, 0x63,
-       0xa2, 0xb7, 0x49, 0x29, 0xfb, 0xb3, 0xc1, 0x14, 0x89, 0x8b, 0x98, 0x65, 
0x9e, 0x40, 0x99, 0x46,
-       0xd7, 0x66, 0xa3, 0x0c, 0xe9, 0x40, 0x83, 0xd4, 0x60, 0xe4, 0x0b, 0x00, 
0xa3, 0x14, 0xb1, 0xed,
-       0xc6, 0xb1, 0xb5, 0x8c, 0xad, 0x91, 0x53, 0xb6, 0xdd, 0x94, 0x6e, 0x2f, 
0xe3, 0x59, 0xd1, 0x38,
-       0x85, 0xfb, 0x4c, 0x01, 0x6a, 0x1f, 0x55, 0x6e, 0xef, 0xd3, 0x3a, 0x94, 
0xba, 0x45, 0x8a, 0x8b,
-       0x28, 0xca, 0xf1, 0x3a, 0x94, 0xee, 0xdf, 0x2d, 0x78, 0x24, 0x50, 0xe6, 
0xa9, 0xc0, 0x46, 0xa9,
-       0xbe, 0x36, 0x5f, 0x4b, 0x4f, 0xad, 0x02, 0x26, 0xd0, 0xfc, 0xf1, 0x2c, 
0x6a, 0xde, 0x36, 0x2b,
-       0x40, 0x72, 0x00, 0x0f, 0x9b, 0xe9, 0xf1, 0xd3, 0x1b, 0x5d, 0x32, 0x8b, 
0x3e, 0xa8, 0xe7, 0x66,
-       0x96, 0xde, 0xa8, 0xba, 0x85, 0xa9, 0xb8, 0xaa, 0x8a, 0x5f, 0xd4, 0xad, 
0xc0, 0xca, 0xd2, 0x96,
-       0x97, 0xa9, 0x95, 0xad, 0x5f, 0x60, 0x9a, 0x52, 0x5d, 0xac, 0x00, 0x55, 
0xd9, 0x5a, 0xd5, 0xc5,
-       0x68, 0x01, 0xba, 0xb7, 0xd0, 0xaf, 0x3f, 0xe7, 0x08, 0xac, 0xc0, 0xb4, 
0xaa, 0x1a, 0xaf, 0xa7,
-       0xb5, 0xf1, 0xba, 0xdf, 0xa4, 0x54, 0x13, 0xc9, 0x1b, 0x35, 0xb0, 0x5a, 
0x4b, 0x8f, 0x43, 0x7f,
-       0xfa, 0xac, 0x3e, 0xea, 0x9f, 0x27, 0x8c, 0x96, 0xf4, 0x83, 0x45, 0x6d, 
0x63, 0x9a, 0x4d, 0x48,
-       0x6c, 0xe8, 0xd2, 0xe5, 0x2f, 0x8b, 0xd9, 0xe8, 0x7f, 0xea, 0x78, 0x7c, 
0x4e, 0x4f, 0x96, 0xa3,
-       0x16, 0xd9, 0x85, 0xce, 0xaf, 0x27, 0xcb, 0x51, 0x5b, 0x1d, 0xe8, 0xf1, 
0x7c, 0xd4, 0x21, 0x8f,
-       0xe0, 0xc1, 0xf1, 0xe9, 0xc7, 0xd9, 0x4f, 0xde, 0xdb, 0xc5, 0xdc, 0x33, 
0x5f, 0x58, 0x07, 0x47,
-       0xd0, 0x2b, 0x77, 0x25, 0xd9, 0x03, 0x50, 0x67, 0xaf, 0xa6, 0x76, 0xf6, 
0xee, 0xed, 0xc5, 0xe9,
-       0xa8, 0x45, 0x7a, 0x60, 0x2d, 0x3e, 0x2e, 0x7e, 0x18, 0xb5, 0xff, 0x09, 
0x00, 0x00, 0xff, 0xff,
-       0x6c, 0x4d, 0xa0, 0xee, 0xd9, 0x08, 0x00, 0x00,
+       // 1128 bytes of a gzipped FileDescriptorProto
+       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 
0xdd, 0x8e, 0xd3, 0x46,
+       0x14, 0x6e, 0x12, 0x67, 0x37, 0x3e, 0x49, 0x76, 0xc3, 0x80, 0x90, 0x05, 
0x2d, 0x4d, 0x0d, 0x55,
+       0xc3, 0x56, 0x02, 0x1a, 0x54, 0x89, 0xab, 0x4a, 0x6c, 0xd2, 0x2d, 0xa8, 
0x10, 0x56, 0x13, 0x50,
+       0xd5, 0xde, 0x58, 0xb3, 0xf6, 0x71, 0x32, 0xc2, 0x7f, 0x9a, 0xb1, 0x17, 
0x92, 0x9b, 0xaa, 0x0f,
+       0xd3, 0xe7, 0xe9, 0x55, 0x1f, 0xa6, 0x77, 0xd5, 0xcc, 0xd8, 0xc6, 0xde, 
0xad, 0xd4, 0xbb, 0x39,
+       0xdf, 0xf9, 0xfc, 0x9d, 0x33, 0xe7, 0x67, 0x0c, 0x77, 0xa3, 0x8f, 0xc1, 
0xe3, 0x98, 0x6f, 0x04,
+       0xcb, 0x79, 0x9a, 0x94, 0x27, 0x7c, 0x94, 0x89, 0x34, 0x4f, 0x89, 0x5d, 
0x3b, 0xdc, 0xdf, 0xc1,
+       0x7e, 0xb9, 0x7c, 0xcd, 0xb2, 0xb7, 0xbb, 0x0c, 0xc9, 0x2d, 0xe8, 0x73, 
0x59, 0xf0, 0xc0, 0xe9,
+       0x4c, 0xbb, 0xb3, 0x01, 0x35, 0x86, 0x41, 0x37, 0x3c, 0x70, 0xba, 0x15, 
0xba, 0xe1, 0x01, 0xb9,
+       0x0d, 0x07, 0xdb, 0x54, 0xe6, 0x3c, 0x70, 0x7a, 0xd3, 0xee, 0xac, 0x4f, 
0x4b, 0x8b, 0x10, 0xb0,
+       0x12, 0xc9, 0x03, 0xc7, 0xd2, 0xa8, 0x3e, 0x93, 0x3b, 0x30, 0x88, 0x59, 
0x26, 0x58, 0xb2, 0x41,
+       0xa7, 0xaf, 0xf1, 0xda, 0x76, 0x9f, 0xc0, 0xc1, 0x22, 0x4d, 0x42, 0xbe, 
0x21, 0x13, 0xe8, 0xbd,
+       0xc7, 0x9d, 0x8e, 0x6d, 0x53, 0x75, 0x54, 0x91, 0x2f, 0x59, 0x54, 0xa0, 
0x8e, 0x6c, 0x53, 0x63,
+       0xb8, 0x3f, 0xc1, 0xc1, 0x12, 0x2f, 0xb9, 0x8f, 0x3a, 0x16, 0x8b, 0xb1, 
0xfc, 0x44, 0x9f, 0xc9,
+       0x43, 0x38, 0xf0, 0xb5, 0x9e, 0xd3, 0x9d, 0xf6, 0x66, 0xc3, 0xf9, 0x8d, 
0x47, 0xf5, 0x65, 0x1f,
+       0x99, 0x40, 0xb4, 0x24, 0xb8, 0x7f, 0x75, 0x61, 0xb0, 0x4e, 0x58, 0x26, 
0xb7, 0x69, 0xfe, 0x9f,
+       0x5a, 0x4f, 0x61, 0x18, 0xa5, 0x3e, 0x8b, 0x16, 0xff, 0x23, 0xd8, 0x64, 
0xa9, 0xcb, 0x66, 0x22,
+       0x0d, 0x79, 0x84, 0xd2, 0xe9, 0x4d, 0x7b, 0x33, 0x9b, 0xd6, 0x36, 0xf9, 
0x1c, 0x6c, 0xcc, 0xb6,
+       0x18, 0xa3, 0x60, 0x91, 0xae, 0xd0, 0x80, 0x7e, 0x02, 0xc8, 0xf7, 0x30, 
0xd2, 0x42, 0xe6, 0x76,
+       0xd2, 0xe9, 0x5f, 0x8b, 0x67, 0x3c, 0xb4, 0x45, 0x23, 0x2e, 0x8c, 0x98, 
0xf0, 0xb7, 0x3c, 0x47,
+       0x3f, 0x2f, 0x04, 0x3a, 0x07, 0xba, 0xc2, 0x2d, 0x4c, 0x25, 0x25, 0x73, 
0x96, 0x63, 0x58, 0x44,
+       0xce, 0xa1, 0x8e, 0x5b, 0xdb, 0xe4, 0x3e, 0x8c, 0x7d, 0x81, 0x3a, 0x80, 
0x17, 0xb0, 0x1c, 0x9d,
+       0xc1, 0xb4, 0x33, 0xeb, 0xd1, 0x51, 0x05, 0x2e, 0x59, 0x8e, 0xe4, 0x01, 
0x1c, 0x45, 0x4c, 0xe6,
+       0x5e, 0x21, 0x31, 0x30, 0x2c, 0xdb, 0xb0, 0x14, 0xfa, 0x4e, 0x62, 0xa0, 
0x58, 0xee, 0x1f, 0x1d,
+       0x18, 0x0b, 0xb9, 0x4b, 0xfc, 0x33, 0x64, 0x2a, 0xae, 0x54, 0x63, 0xf2, 
0x91, 0xe5, 0xb9, 0x90,
+       0x4e, 0x67, 0xda, 0x99, 0x0d, 0x68, 0x69, 0x29, 0x3c, 0xc0, 0x08, 0x73, 
0xd5, 0x5b, 0x8d, 0x1b,
+       0x4b, 0x25, 0xea, 0xa7, 0x71, 0x26, 0x50, 0xaa, 0xea, 0x29, 0x4f, 0x6d, 
0x93, 0x07, 0x30, 0xbe,
+       0xe0, 0x01, 0x17, 0xe8, 0xab, 0xb4, 0x74, 0x05, 0x15, 0xa1, 0x0d, 0xba, 
0x0f, 0x61, 0xb8, 0x0f,
+       0x65, 0x9d, 0x40, 0x53, 0xb0, 0xd3, 0x16, 0x74, 0x37, 0x30, 0xbe, 0xc8, 
0x45, 0x83, 0xfc, 0x10,
+       0x26, 0x75, 0xb1, 0xbd, 0x2d, 0xb2, 0x00, 0x45, 0xf9, 0xd1, 0x71, 0x8d, 
0xbf, 0xd0, 0x30, 0xf9,
+       0x16, 0x6e, 0x18, 0x82, 0x27, 0x8b, 0x8b, 0xcb, 0x34, 0x2a, 0x62, 0x94, 
0xe5, 0x5d, 0x26, 0xc6,
+       0xb1, 0xae, 0x71, 0xf7, 0x9f, 0x1e, 0x1c, 0xbf, 0xbe, 0x22, 0x70, 0x02, 
0xdd, 0x50, 0xea, 0x71,
+       0x3b, 0x9a, 0xdf, 0x69, 0xf4, 0xb8, 0xe6, 0x9d, 0xad, 0xd5, 0x52, 0xd2, 
0x6e, 0x28, 0xc9, 0x37,
+       0x60, 0xf9, 0x82, 0x17, 0x5a, 0xff, 0x68, 0x7e, 0xb3, 0x39, 0x81, 0xf4, 
0xe5, 0x3b, 0x4d, 0xd3,
+       0x04, 0x72, 0x02, 0x7d, 0x1e, 0xc4, 0x2c, 0xd3, 0x93, 0x37, 0x9c, 0xdf, 
0x6a, 0x30, 0xeb, 0x35,
+       0xa7, 0x86, 0xa2, 0xca, 0x29, 0xcb, 0xe9, 0x5f, 0x31, 0x95, 0xbd, 0xa5, 
0xa7, 0xb5, 0x0d, 0x92,
+       0xef, 0xc0, 0xae, 0x80, 0x6a, 0x22, 0x9b, 0xf1, 0xab, 0xfd, 0xa1, 0x9f, 
0x58, 0xc4, 0x81, 0xc3,
+       0x4c, 0x60, 0x50, 0xc4, 0x99, 0x73, 0xa8, 0x0b, 0x52, 0x99, 0xe4, 0x87, 
0x2b, 0xe3, 0xa1, 0x47,
+       0x6d, 0x38, 0x77, 0x1a, 0x82, 0x2d, 0x3f, 0xbd, 0x32, 0x4d, 0x0e, 0x1c, 
0x0a, 0x0c, 0x05, 0xca,
+       0xad, 0x1e, 0xbf, 0x01, 0xad, 0x4c, 0xf2, 0xac, 0xd5, 0x75, 0x07, 0xb4, 
0xee, 0xed, 0x86, 0x6e,
+       0xc3, 0x4b, 0x5b, 0x03, 0x72, 0x0f, 0xc0, 0xb4, 0x69, 0xcd, 0xf7, 0xe8, 
0x0c, 0xf5, 0x54, 0x37,
+       0x10, 0x95, 0x73, 0x6b, 0x48, 0x9c, 0xd1, 0xb5, 0x9c, 0x5b, 0x7e, 0xda, 
0xa6, 0xbb, 0x67, 0x30,
+       0xa9, 0x5b, 0xba, 0x48, 0x93, 0x5c, 0xa4, 0x91, 0xba, 0x87, 0x2c, 0x7c, 
0xdf, 0xcc, 0xa4, 0xda,
+       0xc6, 0xca, 0x54, 0x9e, 0x18, 0xa5, 0x64, 0x1b, 0xb3, 0x18, 0x36, 0xad, 
0x4c, 0xf7, 0x29, 0x8c,
+       0x6b, 0x9d, 0xf5, 0x2e, 0xf1, 0xd5, 0xde, 0x87, 0x3c, 0x61, 0xd1, 0xb9, 
0xc0, 0xa5, 0xaa, 0xb5,
+       0x51, 0x6a, 0x61, 0xee, 0x9f, 0x3d, 0x98, 0xa8, 0xca, 0x7b, 0x6a, 0xdb, 
0xa5, 0x87, 0x49, 0x2e,
+       0x76, 0x6a, 0xe1, 0x43, 0x81, 0xb8, 0xe7, 0xc9, 0xc6, 0xcb, 0x79, 0xf9, 
0xe6, 0x8d, 0xe9, 0xa8,
+       0x02, 0xdf, 0xf2, 0x18, 0xc9, 0x97, 0x30, 0x0c, 0x45, 0xba, 0xc7, 0xc4, 
0x50, 0xba, 0x9a, 0x02,
+       0x06, 0xd2, 0x84, 0xaf, 0x60, 0x14, 0x63, 0xac, 0xc5, 0x35, 0xa3, 0xa7, 
0x19, 0xc3, 0x12, 0xd3,
+       0x94, 0xfb, 0x30, 0x8e, 0x31, 0xfe, 0x20, 0x78, 0x8e, 0x86, 0x63, 0x99, 
0x40, 0x15, 0x58, 0x91,
+       0x32, 0xb6, 0x41, 0xe9, 0x49, 0x9f, 0x25, 0x09, 0x06, 0xfa, 0x0f, 0x61, 
0xd1, 0x91, 0x06, 0xd7,
+       0x06, 0x23, 0x4f, 0xe0, 0x56, 0x49, 0x7a, 0xcf, 0xb3, 0x0c, 0x03, 0x2f, 
0x63, 0x02, 0x93, 0x5c,
+       0xbf, 0x75, 0x16, 0x25, 0x86, 0x6b, 0x5c, 0xe7, 0xda, 0xf3, 0x49, 0x56, 
0x45, 0xca, 0x31, 0xd1,
+       0xcf, 0x5e, 0x25, 0xfb, 0x8b, 0xc1, 0x14, 0x89, 0x8b, 0x98, 0x65, 0x9e, 
0x40, 0x99, 0x46, 0x97,
+       0xe6, 0xe9, 0x1b, 0xd3, 0x91, 0x06, 0xa9, 0xc1, 0xc8, 0x17, 0x00, 0x46, 
0x29, 0x62, 0xfb, 0x9d,
+       0x63, 0x6b, 0x19, 0x5b, 0x23, 0xaf, 0xd8, 0x7e, 0x57, 0xb9, 0xbd, 0x8c, 
0x67, 0xe5, 0xe0, 0x95,
+       0xee, 0x73, 0x05, 0xa8, 0x87, 0xb3, 0x76, 0x7b, 0x17, 0x45, 0x28, 0xf5, 
0x88, 0x95, 0x89, 0x28,
+       0xca, 0x69, 0x11, 0x4a, 0xf7, 0xef, 0x0e, 0xdc, 0x14, 0x28, 0xf3, 0x54, 
0x60, 0xab, 0x55, 0x5f,
+       0x9b, 0xaf, 0xa5, 0xa7, 0xde, 0x2c, 0x26, 0xd0, 0xfc, 0x9a, 0x2d, 0x6a, 
0xee, 0xb6, 0x28, 0x41,
+       0x72, 0x02, 0x37, 0xda, 0xe5, 0xf1, 0xd3, 0x0f, 0xba, 0x65, 0x16, 0x3d, 
0x6e, 0xd6, 0x66, 0x91,
+       0x7e, 0x50, 0x7d, 0x0b, 0x53, 0xf1, 0xbe, 0x6e, 0x7e, 0xd9, 0xb7, 0x12, 
0xab, 0x5a, 0x5b, 0x25,
+       0xd3, 0x68, 0xdb, 0xb0, 0xc4, 0x34, 0xa5, 0x4e, 0xac, 0x04, 0x55, 0xdb, 
0x3a, 0x75, 0x62, 0xb4,
+       0x04, 0xdd, 0x8f, 0x30, 0x6c, 0x5e, 0xe7, 0x31, 0x58, 0x81, 0x19, 0x55, 
0xb5, 0x42, 0x77, 0x1b,
+       0x2b, 0x74, 0x75, 0x48, 0xa9, 0x26, 0x92, 0x67, 0x6a, 0xe1, 0xb5, 0x96, 
0x5e, 0x87, 0xe1, 0xfc,
+       0x5e, 0xf3, 0xa9, 0xb8, 0x5e, 0x30, 0x5a, 0xd1, 0x4f, 0x56, 0x8d, 0x17, 
0xd7, 0xbc, 0xa4, 0xc4,
+       0x86, 0x3e, 0x5d, 0xff, 0xba, 0x5a, 0x4c, 0x3e, 0x53, 0xc7, 0xd3, 0xb7, 
0xf4, 0x6c, 0x3d, 0xe9,
+       0x90, 0x43, 0xe8, 0xfd, 0x76, 0xb6, 0x9e, 0x74, 0xd5, 0x81, 0x9e, 0x2e, 
0x27, 0x3d, 0x72, 0x13,
+       0x8e, 0x4f, 0x5f, 0xbd, 0x59, 0xfc, 0xec, 0x3d, 0x5f, 0x2d, 0x3d, 0xf3, 
0x85, 0x75, 0xf2, 0x18,
+       0x06, 0xd5, 0x5b, 0x4b, 0x8e, 0x00, 0xd4, 0xd9, 0x6b, 0xa8, 0x9d, 0xbf, 
0x78, 0xfe, 0xee, 0xd5,
+       0xa4, 0x43, 0x06, 0x60, 0xad, 0xde, 0xac, 0x7e, 0x9c, 0x74, 0xff, 0x0d, 
0x00, 0x00, 0xff, 0xff,
+       0x51, 0xb3, 0x06, 0x24, 0x82, 0x09, 0x00, 0x00,
 }
diff --git a/lxd/migration/migrate.proto b/lxd/migration/migrate.proto
index 721662e424..257f038e21 100644
--- a/lxd/migration/migrate.proto
+++ b/lxd/migration/migrate.proto
@@ -58,6 +58,11 @@ message zfsFeatures {
        optional bool           compress = 1;
 }
 
+message btrfsFeatures {
+       optional bool           migration_header = 1;
+       optional bool           header_subvolumes = 2;
+}
+
 message MigrationHeader {
        required MigrationFSType                fs              = 1;
        optional CRIUType                       criu            = 2;
@@ -69,6 +74,7 @@ message MigrationHeader {
        optional bool                           refresh         = 9;
        optional zfsFeatures                    zfsFeatures     = 10;
        optional int64                          volumeSize      = 11;
+       optional btrfsFeatures                  btrfsFeatures   = 12;
 }
 
 message MigrationControl {

From 7217506b510fab252968072ab20bfedfd827f196 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 11:46:09 +0100
Subject: [PATCH 07/15] lxd/migration/utils: Adds GetBtrfsFeaturesSlice
 function

And constants for the BTRFS features.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/migration/utils.go | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/lxd/migration/utils.go b/lxd/migration/utils.go
index 92265c2ace..9d3762dc2a 100644
--- a/lxd/migration/utils.go
+++ b/lxd/migration/utils.go
@@ -1,5 +1,11 @@
 package migration
 
+// BTRFSFeatureMigrationHeader indicates a migration header will be sent/recv 
in data channel first.
+const BTRFSFeatureMigrationHeader = "migration_header"
+
+// BTRFSFeatureSubvolumes indicates migration can send/recv subvolumes.
+const BTRFSFeatureSubvolumes = "header_subvolumes"
+
 // GetRsyncFeaturesSlice returns a slice of strings representing the supported 
RSYNC features
 func (m *MigrationHeader) GetRsyncFeaturesSlice() []string {
        features := []string{}
@@ -42,3 +48,23 @@ func (m *MigrationHeader) GetZfsFeaturesSlice() []string {
 
        return features
 }
+
+// GetBtrfsFeaturesSlice returns a slice of strings representing the supported 
BTRFS features.
+func (m *MigrationHeader) GetBtrfsFeaturesSlice() []string {
+       features := []string{}
+       if m == nil {
+               return features
+       }
+
+       if m.BtrfsFeatures != nil {
+               if m.BtrfsFeatures.MigrationHeader != nil && 
*m.BtrfsFeatures.MigrationHeader == true {
+                       features = append(features, BTRFSFeatureMigrationHeader)
+               }
+
+               if m.BtrfsFeatures.HeaderSubvolumes != nil && 
*m.BtrfsFeatures.HeaderSubvolumes == true {
+                       features = append(features, BTRFSFeatureSubvolumes)
+               }
+       }
+
+       return features
+}

From 58172c554abec5a8671131872e1119986536a529 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 11:46:45 +0100
Subject: [PATCH 08/15] lxd/migration/migration/volumes: Adds BTRFS feature
 support to TypesToHeader

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/migration/migration_volumes.go | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lxd/migration/migration_volumes.go 
b/lxd/migration/migration_volumes.go
index 331b003c22..314ea99bbe 100644
--- a/lxd/migration/migration_volumes.go
+++ b/lxd/migration/migration_volumes.go
@@ -72,6 +72,23 @@ func TypesToHeader(types ...Type) MigrationHeader {
                header.ZfsFeatures = &features
        }
 
+       // Add BTRFS features if preferred type is BTRFS.
+       if preferredType.FSType == MigrationFSType_BTRFS {
+               features := BtrfsFeatures{
+                       MigrationHeader:  &missingFeature,
+                       HeaderSubvolumes: &missingFeature,
+               }
+               for _, feature := range preferredType.Features {
+                       if feature == BTRFSFeatureMigrationHeader {
+                               features.MigrationHeader = &hasFeature
+                       } else if feature == BTRFSFeatureSubvolumes {
+                               features.HeaderSubvolumes = &hasFeature
+                       }
+               }
+
+               header.BtrfsFeatures = &features
+       }
+
        // Check all the types for an Rsync method, if found add its features 
to the header's RsyncFeatures list.
        for _, t := range types {
                if t.FSType != MigrationFSType_RSYNC && t.FSType != 
MigrationFSType_BLOCK_AND_RSYNC {

From d3e78fc65202a5c60a8ce228f4310a587b34cde0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 11:47:03 +0100
Subject: [PATCH 09/15] lxd/migration/migration/volumes: Adds BTRFS feature
 support to MatchTypes

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/migration/migration_volumes.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/migration/migration_volumes.go 
b/lxd/migration/migration_volumes.go
index 314ea99bbe..261f7eaab0 100644
--- a/lxd/migration/migration_volumes.go
+++ b/lxd/migration/migration_volumes.go
@@ -145,6 +145,8 @@ func MatchTypes(offer MigrationHeader, fallbackType 
MigrationFSType, ourTypes []
                        var offeredFeatures []string
                        if offerFSType == MigrationFSType_ZFS {
                                offeredFeatures = offer.GetZfsFeaturesSlice()
+                       } else if offerFSType == MigrationFSType_BTRFS {
+                               offeredFeatures = offer.GetBtrfsFeaturesSlice()
                        } else if offerFSType == MigrationFSType_RSYNC {
                                offeredFeatures = offer.GetRsyncFeaturesSlice()
                                if !shared.StringInSlice("bidirectional", 
offeredFeatures) {

From 1fd33760969d4254ae3722471dac320a3383a763 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 11:47:31 +0100
Subject: [PATCH 10/15] lxd/storage/drivers/driver/btrfs: Adds BTRFS features
 to MigrationTypes

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs.go | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lxd/storage/drivers/driver_btrfs.go 
b/lxd/storage/drivers/driver_btrfs.go
index 4c4e569b81..e69d0ff18b 100644
--- a/lxd/storage/drivers/driver_btrfs.go
+++ b/lxd/storage/drivers/driver_btrfs.go
@@ -370,6 +370,7 @@ func (d *btrfs) GetResources() (*api.ResourcesStoragePool, 
error) {
 // MigrationType returns the type of transfer methods to be used when doing 
migrations between pools in preference order.
 func (d *btrfs) MigrationTypes(contentType ContentType, refresh bool) 
[]migration.Type {
        rsyncFeatures := []string{"xattrs", "delete", "compress", 
"bidirectional"}
+       btrfsFeatures := []string{migration.BTRFSFeatureMigrationHeader, 
migration.BTRFSFeatureSubvolumes}
 
        // Only offer rsync for refreshes or if running in an unprivileged 
container.
        if refresh || d.state.OS.RunningInUserNS {
@@ -392,7 +393,8 @@ func (d *btrfs) MigrationTypes(contentType ContentType, 
refresh bool) []migratio
        if contentType == ContentTypeBlock {
                return []migration.Type{
                        {
-                               FSType: migration.MigrationFSType_BTRFS,
+                               FSType:   migration.MigrationFSType_BTRFS,
+                               Features: btrfsFeatures,
                        },
                        {
                                FSType:   
migration.MigrationFSType_BLOCK_AND_RSYNC,
@@ -403,7 +405,8 @@ func (d *btrfs) MigrationTypes(contentType ContentType, 
refresh bool) []migratio
 
        return []migration.Type{
                {
-                       FSType: migration.MigrationFSType_BTRFS,
+                       FSType:   migration.MigrationFSType_BTRFS,
+                       Features: btrfsFeatures,
                },
                {
                        FSType:   migration.MigrationFSType_RSYNC,

From 6fb47c0e094e23e6cf54362e38ed6a161590aa7c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 16:51:18 +0100
Subject: [PATCH 11/15] lxd/storage/memorypipe: Dont make ioutil.ReadAll panic
 on cancel

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/memorypipe/memory_pipe.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage/memorypipe/memory_pipe.go 
b/lxd/storage/memorypipe/memory_pipe.go
index 28744d1c7b..4db48f874f 100644
--- a/lxd/storage/memorypipe/memory_pipe.go
+++ b/lxd/storage/memorypipe/memory_pipe.go
@@ -31,12 +31,12 @@ func (p *pipe) Read(b []byte) (int, error) {
        select {
        case msg := <-p.ch:
                if msg.err == io.EOF {
-                       return -1, msg.err
+                       return 0, msg.err
                }
                n := copy(b, msg.data)
                return n, msg.err
        case <-p.ctx.Done():
-               return -1, p.ctx.Err()
+               return 0, p.ctx.Err()
        }
 }
 
@@ -51,7 +51,7 @@ func (p *pipe) Write(b []byte) (int, error) {
        case p.otherEnd.ch <- msg: // Sent msg to the other side's Read 
function.
                return len(msg.data), msg.err
        case <-p.ctx.Done():
-               return -1, p.ctx.Err()
+               return 0, p.ctx.Err()
        }
 }
 

From 7e16de785564ade6f6e9d2100b15e53330eac55f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 16:57:10 +0100
Subject: [PATCH 12/15] lxd/storage/drivers/driver/btrfs/utils: Kill btrfs send
 on error in sendSubvolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_utils.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index bd48d3fc5a..bf2fd9097b 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -286,6 +286,7 @@ func (d *btrfs) sendSubvolume(path string, parent string, 
conn io.ReadWriteClose
                _, err := io.Copy(conn, stdoutPipe)
                chStdoutPipe <- err
                conn.Close()
+               cmd.Process.Kill() // This closes stderr.
        }()
 
        // Run the command.

From 10d380152044cdfbf38c143ab9ad094670023b9a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 16:57:29 +0100
Subject: [PATCH 13/15] lxd/storage/drivers/driver/btrfs/utils: Support
 subvolumes in receiveSubvolume

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_utils.go | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index bf2fd9097b..12df54820a 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -378,8 +378,15 @@ func (d *btrfs) receiveSubvolume(path string, targetPath 
string, conn io.ReadWri
                return nil
        }
 
-       // Handle older LXD versions.
+       // Location we would expect to receive the root subvolume.
        receivedSnapshot := fmt.Sprintf("%s/.migration-send", path)
+
+       // Handle non-root subvolumes.
+       if !shared.PathExists(receivedSnapshot) {
+               receivedSnapshot = fmt.Sprintf("%s/%s", path, 
filepath.Base(targetPath))
+       }
+
+       // Handle older LXD versions.
        if !shared.PathExists(receivedSnapshot) {
                receivedSnapshot = fmt.Sprintf("%s/.root", path)
        }

From a1b54501a15a9a52efc99c9fd17251d3e045b636 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 17:17:00 +0100
Subject: [PATCH 14/15] lxd/storage/drivers/driver/btrfs/utils: Adds
 migrationHeader function returning BTRFSMigrationHeader struct

Scans a volume and its snapshots for BTRFS subvolumes ready for sending to 
migration target.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_utils.go | 69 +++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index 12df54820a..e5744b7690 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -417,3 +417,72 @@ func (d *btrfs) volumeSize(vol Volume) string {
 
        return size
 }
+
+// BTRFSSubVolume is the structure used to store information about a subvolume.
+type BTRFSSubVolume struct {
+       Path     string
+       Snapshot string
+       Readonly bool
+}
+
+// BTRFSMigrationHeader is the meta data header sent before each volume during 
a migration.
+type BTRFSMigrationHeader struct {
+       Subvolumes []BTRFSSubVolume
+}
+
+// migrationHeader scans the volume and any specified snapshots, returning a 
migration header containing subvolume
+// information.
+func (d *btrfs) migrationHeader(vol Volume, snapshots []string) 
(*BTRFSMigrationHeader, error) {
+       var migrationHeader BTRFSMigrationHeader
+
+       // Add snapshots to volumes list.
+       for _, snapName := range snapshots {
+               snapVol, _ := vol.NewSnapshot(snapName)
+
+               // Add snapshot root volume to volumes list.
+               migrationHeader.Subvolumes = append(migrationHeader.Subvolumes, 
BTRFSSubVolume{
+                       Snapshot: snapVol.name,
+                       Path:     string(filepath.Separator),
+                       Readonly: false,
+               })
+
+               // Find any subvolumes in snapshot volume
+               subVolPaths, err := d.getSubvolumes(snapVol.MountPath())
+               if err != nil {
+                       return nil, err
+               }
+               sort.Sort(sort.StringSlice(subVolPaths))
+
+               // Add any subvolumes under the same snapshot volume name, but 
with a different path.
+               for _, subVolPath := range subVolPaths {
+                       migrationHeader.Subvolumes = 
append(migrationHeader.Subvolumes, BTRFSSubVolume{
+                               Snapshot: snapVol.name,
+                               Path:     fmt.Sprintf("%s%s", 
string(filepath.Separator), subVolPath),
+                               Readonly: false,
+                       })
+               }
+       }
+
+       // Add main root volume to volumes list.
+       migrationHeader.Subvolumes = append(migrationHeader.Subvolumes, 
BTRFSSubVolume{
+               Path:     string(filepath.Separator),
+               Readonly: false,
+       })
+
+       // Find any subvolumes in main volume.
+       subVolPaths, err := d.getSubvolumes(vol.MountPath())
+       if err != nil {
+               return nil, err
+       }
+       sort.Sort(sort.StringSlice(subVolPaths))
+
+       // Add any subvolumes under the main volume name, but with a different 
path.
+       for _, subVolPath := range subVolPaths {
+               migrationHeader.Subvolumes = append(migrationHeader.Subvolumes, 
BTRFSSubVolume{
+                       Path:     fmt.Sprintf("%s%s", 
string(filepath.Separator), subVolPath),
+                       Readonly: false,
+               })
+       }
+
+       return &migrationHeader, nil
+}

From e08e5688ac8fa967038d005bd211286e739abe54 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 6 May 2020 17:24:43 +0100
Subject: [PATCH 15/15] lxd/storage/drivers/driver/btrfs/volumes: Updates
 CreateVolumeFromMigration to receive subvolumes

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs_volumes.go | 29 +++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
index 0d5da1c297..b87666d238 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -318,6 +318,21 @@ func (d *btrfs) CreateVolumeFromMigration(vol Volume, conn 
io.ReadWriteCloser, v
                return ErrNotSupported
        }
 
+       var migrationHeader BTRFSMigrationHeader
+
+       // Inspect negotiated features to see if we are expecting to get a 
metadata migration header frame.
+       if shared.StringInSlice(migration.BTRFSFeatureMigrationHeader, 
volTargetArgs.MigrationType.Features) {
+               buf, err := ioutil.ReadAll(conn)
+               if err != nil {
+                       return errors.Wrapf(err, "Failed reading migration 
header")
+               }
+
+               err = json.Unmarshal(buf, &migrationHeader)
+               if err != nil {
+                       return errors.Wrapf(err, "Failed decoding migration 
header")
+               }
+       }
+
        // Handle btrfs send/receive migration.
        if len(volTargetArgs.Snapshots) > 0 {
                snapshotsDir := GetVolumeSnapshotDir(d.name, vol.volType, 
vol.name)
@@ -356,11 +371,25 @@ func (d *btrfs) CreateVolumeFromMigration(vol Volume, 
conn io.ReadWriteCloser, v
        }
 
        wrapper := migration.ProgressWriter(op, "fs_progress", vol.name)
+       d.logger.Debug("Receiving volume", log.Ctx{"name": vol.name, "tmpPath": 
tmpVolumesMountPoint, "path": vol.MountPath()})
        err = d.receiveSubvolume(tmpVolumesMountPoint, vol.MountPath(), conn, 
wrapper)
        if err != nil {
                return err
        }
 
+       for _, subVol := range migrationHeader.Subvolumes {
+               if subVol.Snapshot != "" || subVol.Path == 
string(filepath.Separator) {
+                       continue // We're only looking for subvolumes of main 
volume.
+               }
+
+               path := filepath.Join(vol.MountPath(), subVol.Path)
+               d.logger.Debug("Receiving volume", log.Ctx{"name": vol.name, 
"tmpPath": tmpVolumesMountPoint, "path": path})
+               err = d.receiveSubvolume(tmpVolumesMountPoint, 
filepath.Join(vol.MountPath(), subVol.Path), conn, wrapper)
+               if err != nil {
+                       return err
+               }
+       }
+
        return nil
 }
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to