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

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) ===
WIP - just running tests with only backup side changed.
From 1b15e36f20e46a399e28647e2839d1d6da4a3c43 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:23:56 +0100
Subject: [PATCH 1/9] lxd/device/device/utils/generic: Removes deviceNameEncode
 and deviceNameDecode

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/device_utils_generic.go | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/lxd/device/device_utils_generic.go 
b/lxd/device/device_utils_generic.go
index 8828403a68..3b1c051f6a 100644
--- a/lxd/device/device_utils_generic.go
+++ b/lxd/device/device_utils_generic.go
@@ -4,20 +4,6 @@ import (
        "strings"
 )
 
-// deviceNameEncode encodes a string to be used as part of a file name in the 
LXD devices path.
-// The encoding scheme replaces "-" with "--" and then "/" with "-".
-func deviceNameEncode(text string) string {
-       return strings.Replace(strings.Replace(text, "-", "--", -1), "/", "-", 
-1)
-}
-
-// deviceNameDecode decodes a string used in the LXD devices path back to its 
original form.
-// The decoding scheme converts "-" back to "/" and "--" back to "-".
-func deviceNameDecode(text string) string {
-       // This converts "--" to the null character "\0" first, to allow 
remaining "-" chars to be
-       // converted back to "/" before making a final pass to convert "\0" 
back to original "-".
-       return strings.Replace(strings.Replace(strings.Replace(text, "--", 
"\000", -1), "-", "/", -1), "\000", "-", -1)
-}
-
 // deviceJoinPath joins together prefix and text delimited by a "." for device 
path generation.
 func deviceJoinPath(parts ...string) string {
        return strings.Join(parts, ".")

From b0f658ca41406ab74b3615f2099baa9f371bd559 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:24:26 +0100
Subject: [PATCH 2/9] lxd/storage/drivers/utils: Adds PathNameEncode and
 PathNameDecode

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

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index 8093fa1d7a..a2cf4dfa7f 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -753,3 +753,17 @@ func BlockDevSizeBytes(blockDevPath string) (int64, error) 
{
 
        return int64(res), nil
 }
+
+// PathNameEncode encodes a path string to be used as part of a file name.
+// The encoding scheme replaces "-" with "--" and then "/" with "-".
+func PathNameEncode(text string) string {
+       return strings.Replace(strings.Replace(text, "-", "--", -1), "/", "-", 
-1)
+}
+
+// PathNameDecode decodes a string containing an encoded path back to its 
original form.
+// The decoding scheme converts "-" back to "/" and "--" back to "-".
+func PathNameDecode(text string) string {
+       // This converts "--" to the null character "\0" first, to allow 
remaining "-" chars to be
+       // converted back to "/" before making a final pass to convert "\0" 
back to original "-".
+       return strings.Replace(strings.Replace(strings.Replace(text, "--", 
"\000", -1), "-", "/", -1), "\000", "-", -1)
+}

From 66c0bd5ee9d93389102862dadf42c570c60f89d0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:27:29 +0100
Subject: [PATCH 3/9] lxd/device/device: PathNameEncode and PathNameDecode
 usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/device_utils_unix.go | 17 +++++++++--------
 lxd/device/disk.go              |  4 ++--
 lxd/device/unix_common.go       |  3 ++-
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index da711e6f01..810e77dc99 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -12,6 +12,7 @@ import (
 
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
        "github.com/lxc/lxd/lxd/state"
+       storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/idmap"
        "github.com/lxc/lxd/shared/logger"
@@ -190,7 +191,7 @@ func UnixDeviceCreate(s *state.State, idmapSet 
*idmap.IdmapSet, devicesPath stri
 
        destPath := unixDeviceDestPath(m)
        relativeDestPath := strings.TrimPrefix(destPath, "/")
-       devName := deviceNameEncode(deviceJoinPath(prefix, relativeDestPath))
+       devName := storageDrivers.PathNameEncode(deviceJoinPath(prefix, 
relativeDestPath))
        devPath := filepath.Join(devicesPath, devName)
 
        // Create the new entry.
@@ -253,7 +254,7 @@ func unixDeviceSetup(s *state.State, devicesPath string, 
typePrefix string, devi
 
        // Convert the requested dest path inside the instance to an encoded 
relative one.
        ourDestPath := unixDeviceDestPath(m)
-       ourEncRelDestFile := deviceNameEncode(strings.TrimPrefix(ourDestPath, 
"/"))
+       ourEncRelDestFile := 
storageDrivers.PathNameEncode(strings.TrimPrefix(ourDestPath, "/"))
 
        // Load all existing host devices.
        dents, err := ioutil.ReadDir(devicesPath)
@@ -359,7 +360,7 @@ func unixDeviceSetupBlockNum(s *state.State, devicesPath 
string, typePrefix stri
 // UnixDeviceExists checks if the unix device already exists in devices path.
 func UnixDeviceExists(devicesPath string, prefix string, path string) bool {
        relativeDestPath := strings.TrimPrefix(path, "/")
-       devName := fmt.Sprintf("%s.%s", deviceNameEncode(prefix), 
deviceNameEncode(relativeDestPath))
+       devName := fmt.Sprintf("%s.%s", storageDrivers.PathNameEncode(prefix), 
storageDrivers.PathNameEncode(relativeDestPath))
        devPath := filepath.Join(devicesPath, devName)
 
        return shared.PathExists(devPath)
@@ -384,9 +385,9 @@ func unixDeviceRemove(devicesPath string, typePrefix 
string, deviceName string,
        var ourPrefix string
        // If a prefix override has been supplied, use that for filtering the 
devices to remove.
        if optPrefix != "" {
-               ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, 
deviceName, optPrefix))
+               ourPrefix = 
storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
        } else {
-               ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, 
deviceName))
+               ourPrefix = 
storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName))
        }
 
        ourDevs := []string{}
@@ -448,7 +449,7 @@ func unixDeviceRemove(devicesPath string, typePrefix 
string, deviceName string,
 
                // Append this device to the mount rules (these will be 
unmounted).
                runConf.Mounts = append(runConf.Mounts, 
deviceConfig.MountEntryItem{
-                       TargetPath: deviceNameDecode(ourEncRelDestFile),
+                       TargetPath: 
storageDrivers.PathNameDecode(ourEncRelDestFile),
                })
 
                absDevPath := filepath.Join(devicesPath, ourDev)
@@ -474,9 +475,9 @@ func unixDeviceDeleteFiles(s *state.State, devicesPath 
string, typePrefix string
        var ourPrefix string
        // If a prefix override has been supplied, use that for filtering the 
devices to remove.
        if optPrefix != "" {
-               ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, 
deviceName, optPrefix))
+               ourPrefix = 
storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName, optPrefix))
        } else {
-               ourPrefix = deviceNameEncode(deviceJoinPath(typePrefix, 
deviceName))
+               ourPrefix = 
storageDrivers.PathNameEncode(deviceJoinPath(typePrefix, deviceName))
        }
 
        // Load all devices.
diff --git a/lxd/device/disk.go b/lxd/device/disk.go
index a0104be82d..10ab81fde0 100644
--- a/lxd/device/disk.go
+++ b/lxd/device/disk.go
@@ -189,7 +189,7 @@ func (d *disk) validateConfig(instConf 
instance.ConfigReader) error {
 // getDevicePath returns the absolute path on the host for this instance and 
supplied device config.
 func (d *disk) getDevicePath(devName string, devConfig deviceConfig.Device) 
string {
        relativeDestPath := strings.TrimPrefix(devConfig["path"], "/")
-       devPath := deviceNameEncode(deviceJoinPath("disk", devName, 
relativeDestPath))
+       devPath := storageDrivers.PathNameEncode(deviceJoinPath("disk", 
devName, relativeDestPath))
        return filepath.Join(d.inst.DevicesPath(), devPath)
 }
 
@@ -1514,7 +1514,7 @@ func (d *disk) getParentBlocks(path string) ([]string, 
error) {
 // generateVMConfigDrive generates an ISO containing the cloud init config for 
a VM.
 // Returns the path to the ISO.
 func (d *disk) generateVMConfigDrive() (string, error) {
-       scratchDir := filepath.Join(d.inst.DevicesPath(), 
deviceNameEncode(d.name))
+       scratchDir := filepath.Join(d.inst.DevicesPath(), 
storageDrivers.PathNameEncode(d.name))
 
        // Check we have the mkisofs tool available.
        mkisofsPath, err := exec.LookPath("mkisofs")
diff --git a/lxd/device/unix_common.go b/lxd/device/unix_common.go
index 87de76f724..21e273afe1 100644
--- a/lxd/device/unix_common.go
+++ b/lxd/device/unix_common.go
@@ -8,6 +8,7 @@ import (
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
        "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/instance/instancetype"
+       storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
        "github.com/lxc/lxd/shared"
 )
 
@@ -91,7 +92,7 @@ func (d *unixCommon) Register() error {
                // Derive the host side path for the instance device file.
                ourPrefix := deviceJoinPath("unix", deviceName)
                relativeDestPath := 
strings.TrimPrefix(unixDeviceDestPath(devConfig), "/")
-               devName := deviceNameEncode(deviceJoinPath(ourPrefix, 
relativeDestPath))
+               devName := 
storageDrivers.PathNameEncode(deviceJoinPath(ourPrefix, relativeDestPath))
                devPath := filepath.Join(devicesPath, devName)
 
                runConf := deviceConfig.RunConfig{}

From 36c681e2814af736d0719d258d5a3ca19719b12c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:28:44 +0100
Subject: [PATCH 4/9] lxd/storage/drivers/driver/types: Adds
 OptimizedBackupHeader field to Info

Used to indicate if driver will generate a backup header file when generating 
an optimized backup file.

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

diff --git a/lxd/storage/drivers/driver_types.go 
b/lxd/storage/drivers/driver_types.go
index c7547211ed..700eca1cd6 100644
--- a/lxd/storage/drivers/driver_types.go
+++ b/lxd/storage/drivers/driver_types.go
@@ -8,6 +8,7 @@ type Info struct {
        Remote                bool         // Whether the driver uses a remote 
backing store.
        OptimizedImages       bool         // Whether driver stores images as 
separate volume.
        OptimizedBackups      bool         // Whether driver supports optimized 
volume backups.
+       OptimizedBackupHeader bool         // Whether driver generates an 
optimised backup header file in backup.
        PreservesInodes       bool         // Whether driver preserves inodes 
when volumes are moved hosts.
        BlockBacking          bool         // Whether driver uses block devices 
as backing store.
        RunningQuotaResize    bool         // Whether quota resize is supported 
whilst instance running.

From ca9ad71f6984235fd34ca1e258d77ba3e673e538 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:29:56 +0100
Subject: [PATCH 5/9] lxd/backup/backup: Adds OptimizedHeader field to Info
 struct

Used to indicate if an optimized backup file will contain a driver specific 
optimized header file.

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

diff --git a/lxd/backup/backup.go b/lxd/backup/backup.go
index 3648f2d375..5b9b2b571a 100644
--- a/lxd/backup/backup.go
+++ b/lxd/backup/backup.go
@@ -31,7 +31,8 @@ type Info struct {
        Backend          string           `json:"backend" yaml:"backend"`
        Pool             string           `json:"pool" yaml:"pool"`
        Snapshots        []string         `json:"snapshots,omitempty" 
yaml:"snapshots,omitempty"`
-       OptimizedStorage *bool            `json:"optimized,omitempty" 
yaml:"optimized,omitempty"` // Optional field to handle older optimized backups 
that don't have this field.
+       OptimizedStorage *bool            `json:"optimized,omitempty" 
yaml:"optimized,omitempty"`               // Optional field to handle older 
optimized backups that don't have this field.
+       OptimizedHeader  *bool            `json:"optimized_header,omitempty" 
yaml:"optimized_header,omitempty"` // Optional field to handle older optimized 
backups that don't have this field.
        Type             api.InstanceType `json:"type" yaml:"type"`
 }
 

From 44da0769461216ff792e4e5cc86acab1aa4e556c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:30:35 +0100
Subject: [PATCH 6/9] lxd/backup: Updates backupWriteIndex to populate the
 OptimizedHeader field

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

diff --git a/lxd/backup.go b/lxd/backup.go
index a1bef75103..f61c119920 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -167,6 +167,12 @@ func backupCreate(s *state.State, args 
db.InstanceBackupArgs, sourceInst instanc
 
 // backupWriteIndex generates an index.yaml file and then writes it to the 
root of the backup tarball.
 func backupWriteIndex(sourceInst instance.Instance, pool storagePools.Pool, 
optimized bool, snapshots bool, tarWriter *instancewriter.InstanceTarWriter) 
error {
+       // Indicate whether the driver will include a driver-specific optimized 
header.
+       poolDriverOptimizedHeader := false
+       if optimized {
+               poolDriverOptimizedHeader = 
pool.Driver().Info().OptimizedBackupHeader
+       }
+
        indexInfo := backup.Info{
                Name:             sourceInst.Name(),
                Pool:             pool.Name(),
@@ -174,6 +180,7 @@ func backupWriteIndex(sourceInst instance.Instance, pool 
storagePools.Pool, opti
                Backend:          pool.Driver().Info().Name,
                Type:             api.InstanceType(sourceInst.Type().String()),
                OptimizedStorage: &optimized,
+               OptimizedHeader:  &poolDriverOptimizedHeader,
        }
 
        if snapshots {
@@ -188,7 +195,7 @@ func backupWriteIndex(sourceInst instance.Instance, pool 
storagePools.Pool, opti
                }
        }
 
-       // Convert to JSON.
+       // Convert to YAML.
        indexData, err := yaml.Marshal(&indexInfo)
        if err != nil {
                return err

From da3f45b2f656bdb41eb30f70c956100bd05a5316 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:31:06 +0100
Subject: [PATCH 7/9] lxd/storage/drivers/driver/btrfs: Sets
 OptimizedBackupHeader to true in Info struct response

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

diff --git a/lxd/storage/drivers/driver_btrfs.go 
b/lxd/storage/drivers/driver_btrfs.go
index e69d0ff18b..2ec544339f 100644
--- a/lxd/storage/drivers/driver_btrfs.go
+++ b/lxd/storage/drivers/driver_btrfs.go
@@ -73,6 +73,7 @@ func (d *btrfs) Info() Info {
                Version:               btrfsVersion,
                OptimizedImages:       true,
                OptimizedBackups:      true,
+               OptimizedBackupHeader: true,
                PreservesInodes:       !d.state.OS.RunningInUserNS,
                Remote:                false,
                VolumeTypes:           []VolumeType{VolumeTypeCustom, 
VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},

From eec84caf71e1c3c86a2a8af5a150203e53905c3e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:31:36 +0100
Subject: [PATCH 8/9] lxd/storage/drivers/driver/btrfs/utils: Adds warning to
 BTRFSSubVolume and BTRFSMetaDataHeader about shared usage

Modifying these structs in the future will impact both migration and backup 
subsystems which may be unwanted.

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

diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
index ef1aa554a0..6f3e795212 100644
--- a/lxd/storage/drivers/driver_btrfs_utils.go
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -423,6 +423,7 @@ func (d *btrfs) setSubvolumeReadonlyProperty(path string, 
readonly bool) error {
 }
 
 // BTRFSSubVolume is the structure used to store information about a subvolume.
+// Note: This is used by both migration and backup subsystems so do not modify 
without considering both!
 type BTRFSSubVolume struct {
        Path     string `json:"path" yaml:"path"`         // Path inside the 
volume where the subvolume belongs (so / is the top of the volume tree).
        Snapshot string `json:"snapshot" yaml:"snapshot"` // Snapshot name the 
subvolume belongs to.
@@ -466,6 +467,7 @@ func (d *btrfs) getSubvolumesMetaData(vol Volume) 
([]BTRFSSubVolume, error) {
 }
 
 // BTRFSMetaDataHeader is the meta data header about the volumes being 
sent/stored.
+// Note: This is used by both migration and backup subsystems so do not modify 
without considering both!
 type BTRFSMetaDataHeader struct {
        Subvolumes []BTRFSSubVolume `json:"subvolumes" yaml:"subvolumes"` // 
Sub volumes inside the volume (including the top level ones).
 }

From 34a1cd6fc2a206a63a849816e8eb378a40f08253 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 13 May 2020 14:33:16 +0100
Subject: [PATCH 9/9] lxd/storage/drivers/driver/btrfs/volumes: Updates
 BackupVolume to add subvolumes to optimized backup file

 Adds an optimized_header.yaml file containing subvolume metadata.
 Adds each subvolume as a separate .bin file, the name of which encodes the 
relative path of the subvolume so it can be found from the 
optimized_header.yaml file data.

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

diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go 
b/lxd/storage/drivers/driver_btrfs_volumes.go
index fcd3c8bd15..398b0f24d4 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -1,6 +1,7 @@
 package drivers
 
 import (
+       "bytes"
        "context"
        "encoding/json"
        "fmt"
@@ -9,8 +10,10 @@ import (
        "os"
        "path/filepath"
        "strings"
+       "time"
 
        "github.com/pkg/errors"
+       "gopkg.in/yaml.v2"
 
        "github.com/lxc/lxd/lxd/migration"
        "github.com/lxc/lxd/lxd/operations"
@@ -910,7 +913,45 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter 
*instancewriter.InstanceTarWr
                return genericVFSBackupVolume(d, vol, tarWriter, snapshots, op)
        }
 
-       // Handle the optimized tarballs.
+       // Optimized backup.
+       var err error
+       var volSnapshots []string
+
+       // Retrieve the snapshots if requested.
+       if snapshots {
+               volSnapshots, err = d.VolumeSnapshots(vol, op)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Generate driver restoration header.
+       optimizedHeader, err := d.restorationHeader(vol, volSnapshots)
+       if err != nil {
+               return err
+       }
+
+       // Convert to YAML.
+       optimizedHeaderYAML, err := yaml.Marshal(&optimizedHeader)
+       if err != nil {
+               return err
+       }
+       r := bytes.NewReader(optimizedHeaderYAML)
+
+       indexFileInfo := instancewriter.FileInfo{
+               FileName:    "backup/optimized_header.yaml",
+               FileSize:    int64(len(optimizedHeaderYAML)),
+               FileMode:    0644,
+               FileModTime: time.Now(),
+       }
+
+       // Write to tarball.
+       err = tarWriter.WriteFileFromReader(r, &indexFileInfo)
+       if err != nil {
+               return err
+       }
+
+       // sendToFile sends a subvolume to backup file.
        sendToFile := func(path string, parent string, fileName string) error {
                // Prepare btrfs send arguments.
                args := []string{"send"}
@@ -929,7 +970,7 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter 
*instancewriter.InstanceTarWr
                defer os.Remove(tmpFile.Name())
 
                // Write the subvolume to the file.
-               d.logger.Debug("Generating optimized volume file", 
log.Ctx{"sourcePath": path, "file": tmpFile.Name(), "name": fileName})
+               d.logger.Debug("Generating optimized volume file", 
log.Ctx{"sourcePath": path, "parent": parent, "file": tmpFile.Name(), "name": 
fileName})
                err = shared.RunCommandWithFds(nil, tmpFile, "btrfs", args...)
                if err != nil {
                        return err
@@ -949,44 +990,104 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter 
*instancewriter.InstanceTarWr
                return tmpFile.Close()
        }
 
-       // Handle snapshots.
-       finalParent := ""
-       if snapshots {
-               // Retrieve the snapshots.
-               volSnapshots, err := d.VolumeSnapshots(vol, op)
-               if err != nil {
-                       return err
+       // addVolume adds a volume and its subvolumes to backup file.
+       addVolume := func(v Volume, sourcePrefix string, parentPrefix string, 
fileNamePrefix string) error {
+               snapName := "" // Default to empty (sending main volume) from 
migrationHeader.Subvolumes.
+
+               // Detect if we are adding a snapshot by comparing to main 
volume name.
+               // We can't only use IsSnapshot() as the main vol may itself be 
a snapshot.
+               if v.IsSnapshot() && v.name != vol.name {
+                       _, snapName, _ = 
shared.InstanceGetParentAndSnapshotName(v.name)
                }
 
-               for i, snap := range volSnapshots {
-                       fullSnapshotName := GetSnapshotVolumeName(vol.name, 
snap)
+               sentVols := 0
 
-                       // Figure out parent and current subvolumes.
-                       parent := ""
-                       if i > 0 {
-                               parent = GetVolumeMountPath(d.name, 
vol.volType, GetSnapshotVolumeName(vol.name, volSnapshots[i-1]))
+               // Add volume (and any subvolumes if supported) to backup file.
+               for _, subVolume := range optimizedHeader.Subvolumes {
+                       if subVolume.Snapshot != snapName {
+                               continue // Only add subvolumes related to 
snapshot name (empty for main vol).
                        }
 
-                       cur := GetVolumeMountPath(d.name, vol.volType, 
fullSnapshotName)
+                       // Detect if parent subvolume exists, and if so use it 
for differential.
+                       parentPath := ""
+                       if parentPrefix != "" && 
btrfsIsSubVolume(filepath.Join(parentPrefix, subVolume.Path)) {
+                               parentPath = filepath.Join(parentPrefix, 
subVolume.Path)
+
+                               // Set parent subvolume readonly if needed so 
we can add the subvolume.
+                               if !BTRFSSubVolumeIsRo(parentPath) {
+                                       err = 
d.setSubvolumeReadonlyProperty(parentPath, true)
+                                       if err != nil {
+                                               return err
+                                       }
+                                       defer 
d.setSubvolumeReadonlyProperty(parentPath, false)
+                               }
+                       }
 
-                       // Make a binary btrfs backup.
-                       prefix := "snapshots"
-                       fileName := fmt.Sprintf("%s.bin", snap)
-                       if vol.volType == VolumeTypeVM {
-                               prefix = "virtual-machine-snapshots"
-                               if vol.contentType == ContentTypeFS {
-                                       fileName = fmt.Sprintf("%s-config.bin", 
snap)
+                       // Set subvolume readonly if needed so we can add it.
+                       sourcePath := filepath.Join(sourcePrefix, 
subVolume.Path)
+                       if !BTRFSSubVolumeIsRo(sourcePath) {
+                               err = 
d.setSubvolumeReadonlyProperty(sourcePath, true)
+                               if err != nil {
+                                       return err
                                }
+                               defer 
d.setSubvolumeReadonlyProperty(sourcePath, false)
+                       }
+
+                       // Default to no subvolume name for root subvolume to 
maintain backwards compatibility
+                       // with earlier optimized dump format. Although 
restoring this backup file on an earlier
+                       // system will not restore the subvolumes stored inside 
the backup.
+                       subVolName := ""
+                       if subVolume.Path != string(filepath.Separator) {
+                               // Encode the path of the subvolume (without 
the leading /) into the filename so
+                               // that we find the file from the optimized 
header's Path field on restore.
+                               subVolName = fmt.Sprintf("_%s", 
PathNameEncode(strings.TrimPrefix(subVolume.Path, string(filepath.Separator))))
                        }
 
-                       target := fmt.Sprintf("backup/%s/%s", prefix, fileName)
-                       err := sendToFile(cur, parent, target)
+                       fileName := fmt.Sprintf("%s%s.bin", fileNamePrefix, 
subVolName)
+                       err = sendToFile(sourcePath, parentPath, 
filepath.Join("backup", fileName))
                        if err != nil {
-                               return err
+                               return errors.Wrapf(err, "Failed adding volume 
%v:%s", v.name, subVolume.Path)
+                       }
+
+                       sentVols++
+               }
+
+               // Ensure we found and sent at least root subvolume of the 
volume requested.
+               if sentVols < 1 {
+                       return fmt.Errorf("No matching subvolume(s) for %q 
found in subvolumes list", v.name)
+               }
+
+               return nil
+       }
+
+       // Backup snapshots if populated.
+       finalParent := ""
+       for i, snapName := range volSnapshots {
+               snapVol, _ := vol.NewSnapshot(snapName)
+
+               // Locate the parent snapshot.
+               parentSnapshotPath := ""
+               if i > 0 {
+                       parentSnapshotPath = GetVolumeMountPath(d.name, 
vol.volType, GetSnapshotVolumeName(vol.name, volSnapshots[i-1]))
+               }
+
+               // Make a binary btrfs backup.
+               snapDir := "snapshots"
+               fileName := snapName
+               if vol.volType == VolumeTypeVM {
+                       snapDir = "virtual-machine-snapshots"
+                       if vol.contentType == ContentTypeFS {
+                               fileName = fmt.Sprintf("%s-config", snapName)
                        }
+               }
 
-                       finalParent = cur
+               fileNamePrefix := filepath.Join(snapDir, fileName)
+               err := addVolume(snapVol, snapVol.MountPath(), 
parentSnapshotPath, fileNamePrefix)
+               if err != nil {
+                       return err
                }
+
+               finalParent = snapVol.MountPath()
        }
 
        // Make a temporary copy of the instance.
@@ -1017,18 +1118,17 @@ func (d *btrfs) BackupVolume(vol Volume, tarWriter 
*instancewriter.InstanceTarWr
                return err
        }
 
-       // Dump the container to a file.
-       fileName := "container.bin"
+       // Dump the instance to a file.
+       fileNamePrefix := "container"
        if vol.volType == VolumeTypeVM {
                if vol.contentType == ContentTypeFS {
-                       fileName = "virtual-machine-config.bin"
+                       fileNamePrefix = "virtual-machine-config"
                } else {
-                       fileName = "virtual-machine.bin"
+                       fileNamePrefix = "virtual-machine"
                }
        }
 
-       // Dump the container to a file.
-       err = sendToFile(targetVolume, finalParent, fmt.Sprintf("backup/%s", 
fileName))
+       err = addVolume(vol, targetVolume, finalParent, fileNamePrefix)
        if err != nil {
                return err
        }
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to