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

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 52bbc8b5e62784d1f34f7fa28da374d5d66f45c5 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Thu, 14 Nov 2019 10:59:51 +0100
Subject: [PATCH 1/4] lxd/storage: Move storage_cgo.go to drivers package

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/storage/{storage_cgo.go => drivers/driver_cgo.go} | 6 +++---
 lxd/storage_btrfs.go                                  | 2 +-
 lxd/storage_lvm.go                                    | 7 ++++---
 3 files changed, 8 insertions(+), 7 deletions(-)
 rename lxd/storage/{storage_cgo.go => drivers/driver_cgo.go} (98%)

diff --git a/lxd/storage/storage_cgo.go b/lxd/storage/drivers/driver_cgo.go
similarity index 98%
rename from lxd/storage/storage_cgo.go
rename to lxd/storage/drivers/driver_cgo.go
index 879e94ff54..fd066095e1 100644
--- a/lxd/storage/storage_cgo.go
+++ b/lxd/storage/drivers/driver_cgo.go
@@ -1,7 +1,7 @@
 // +build linux
 // +build cgo
 
-package storage
+package drivers
 
 import (
        "fmt"
@@ -29,8 +29,8 @@ import (
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include "../include/macro.h"
-#include "../include/memory_utils.h"
+#include "../../include/macro.h"
+#include "../../include/memory_utils.h"
 
 #define LXD_MAXPATH 4096
 #define LXD_NUMSTRLEN64 21
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 1d31793083..335ebd5a51 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -426,7 +426,7 @@ func (s *storageBtrfs) StoragePoolMount() (bool, error) {
                        // Since we mount the loop device LO_FLAGS_AUTOCLEAR is
                        // fine since the loop device will be kept around for as
                        // long as the mount exists.
-                       loopF, loopErr := driver.PrepareLoopDev(source, 
driver.LoFlagsAutoclear)
+                       loopF, loopErr := drivers.PrepareLoopDev(source, 
drivers.LoFlagsAutoclear)
                        if loopErr != nil {
                                return false, loopErr
                        }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index ce7aa01137..62b7421946 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -18,6 +18,7 @@ import (
        "github.com/lxc/lxd/lxd/project"
        "github.com/lxc/lxd/lxd/rsync"
        driver "github.com/lxc/lxd/lxd/storage"
+       "github.com/lxc/lxd/lxd/storage/drivers"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/ioprogress"
@@ -395,7 +396,7 @@ func (s *storageLvm) StoragePoolDelete() error {
        if s.loopInfo != nil {
                // Set LO_FLAGS_AUTOCLEAR before we remove the loop file
                // otherwise we will get EBADF.
-               err = driver.SetAutoclearOnLoopDev(int(s.loopInfo.Fd()))
+               err = drivers.SetAutoclearOnLoopDev(int(s.loopInfo.Fd()))
                if err != nil {
                        logger.Warnf("Failed to set LO_FLAGS_AUTOCLEAR on loop 
device: %s, manual cleanup needed", err)
                }
@@ -467,12 +468,12 @@ func (s *storageLvm) StoragePoolMount() (bool, error) {
 
        if filepath.IsAbs(source) && !shared.IsBlockdevPath(source) {
                // Try to prepare new loop device.
-               loopF, loopErr := driver.PrepareLoopDev(source, 0)
+               loopF, loopErr := drivers.PrepareLoopDev(source, 0)
                if loopErr != nil {
                        return false, loopErr
                }
                // Make sure that LO_FLAGS_AUTOCLEAR is unset.
-               loopErr = driver.UnsetAutoclearOnLoopDev(int(loopF.Fd()))
+               loopErr = drivers.UnsetAutoclearOnLoopDev(int(loopF.Fd()))
                if loopErr != nil {
                        return false, loopErr
                }

From 5d4ced5218e338af759102ba4c8033d8184eec6a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 18 Nov 2019 15:18:18 +0100
Subject: [PATCH 2/4] lxd/storage/drivers: Move FS and mount functions

This moves FS and mount functions to the drivers package.

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

diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go
index 71a496c72a..f47b8191eb 100644
--- a/lxd/storage/drivers/utils.go
+++ b/lxd/storage/drivers/utils.go
@@ -5,6 +5,7 @@ import (
        "io/ioutil"
        "os"
        "path/filepath"
+       "strings"
        "time"
 
        "golang.org/x/sys/unix"
@@ -14,6 +15,11 @@ import (
        "github.com/lxc/lxd/shared/api"
 )
 
+// MkfsOptions represents options for filesystem creation.
+type MkfsOptions struct {
+       Label string
+}
+
 func wipeDirectory(path string) error {
        // List all entries
        entries, err := ioutil.ReadDir(path)
@@ -235,3 +241,93 @@ func createSparseFile(filePath string, sizeBytes int64) 
error {
 
        return nil
 }
+
+// MakeFSType creates the provided filesystem.
+func MakeFSType(path string, fsType string, options *MkfsOptions) (string, 
error) {
+       var err error
+       var msg string
+
+       fsOptions := options
+       if fsOptions == nil {
+               fsOptions = &MkfsOptions{}
+       }
+
+       cmd := []string{fmt.Sprintf("mkfs.%s", fsType), path}
+       if fsOptions.Label != "" {
+               cmd = append(cmd, "-L", fsOptions.Label)
+       }
+
+       if fsType == "ext4" {
+               cmd = append(cmd, "-E", 
"nodiscard,lazy_itable_init=0,lazy_journal_init=0")
+       }
+
+       msg, err = shared.TryRunCommand(cmd[0], cmd[1:]...)
+       if err != nil {
+               return msg, err
+       }
+
+       return "", nil
+}
+
+// Export the mount options map since we might find it useful in other parts of
+// LXD.
+type mountOptions struct {
+       capture bool
+       flag    uintptr
+}
+
+// MountOptions represents a list of possible mount options.
+var MountOptions = map[string]mountOptions{
+       "async":         {false, unix.MS_SYNCHRONOUS},
+       "atime":         {false, unix.MS_NOATIME},
+       "bind":          {true, unix.MS_BIND},
+       "defaults":      {true, 0},
+       "dev":           {false, unix.MS_NODEV},
+       "diratime":      {false, unix.MS_NODIRATIME},
+       "dirsync":       {true, unix.MS_DIRSYNC},
+       "exec":          {false, unix.MS_NOEXEC},
+       "lazytime":      {true, unix.MS_LAZYTIME},
+       "mand":          {true, unix.MS_MANDLOCK},
+       "noatime":       {true, unix.MS_NOATIME},
+       "nodev":         {true, unix.MS_NODEV},
+       "nodiratime":    {true, unix.MS_NODIRATIME},
+       "noexec":        {true, unix.MS_NOEXEC},
+       "nomand":        {false, unix.MS_MANDLOCK},
+       "norelatime":    {false, unix.MS_RELATIME},
+       "nostrictatime": {false, unix.MS_STRICTATIME},
+       "nosuid":        {true, unix.MS_NOSUID},
+       "rbind":         {true, unix.MS_BIND | unix.MS_REC},
+       "relatime":      {true, unix.MS_RELATIME},
+       "remount":       {true, unix.MS_REMOUNT},
+       "ro":            {true, unix.MS_RDONLY},
+       "rw":            {false, unix.MS_RDONLY},
+       "strictatime":   {true, unix.MS_STRICTATIME},
+       "suid":          {false, unix.MS_NOSUID},
+       "sync":          {true, unix.MS_SYNCHRONOUS},
+}
+
+// resolveMountOptions resolves the provided mount options.
+func resolveMountOptions(options string) (uintptr, string) {
+       mountFlags := uintptr(0)
+       tmp := strings.SplitN(options, ",", -1)
+       for i := 0; i < len(tmp); i++ {
+               opt := tmp[i]
+               do, ok := MountOptions[opt]
+               if !ok {
+                       continue
+               }
+
+               if do.capture {
+                       mountFlags |= do.flag
+               } else {
+                       mountFlags &= ^do.flag
+               }
+
+               copy(tmp[i:], tmp[i+1:])
+               tmp[len(tmp)-1] = ""
+               tmp = tmp[:len(tmp)-1]
+               i--
+       }
+
+       return mountFlags, strings.Join(tmp, ",")
+}

From dbeef7e28356b9121a8a915d24447b09a32c850b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 18 Nov 2019 15:20:30 +0100
Subject: [PATCH 3/4] lxd/storage: Mark fs/mount functions as deprecated

This marks some FS and mount functions as deprecated as they have been
moved to the drivers package.

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/storage/utils.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go
index 25e8e273fd..faa77878a6 100644
--- a/lxd/storage/utils.go
+++ b/lxd/storage/utils.go
@@ -31,6 +31,7 @@ var baseDirectories = map[drivers.VolumeType][]string{
 var VolumeUsedByInstancesWithProfiles func(s *state.State, poolName string, 
volumeName string, volumeTypeName string, runningOnly bool) ([]string, error)
 
 // MkfsOptions represents options for filesystem creation.
+// Deprecated: Use drivers.MkfsOptions.
 type MkfsOptions struct {
        Label string
 }
@@ -73,6 +74,7 @@ var MountOptions = map[string]mountOptions{
 }
 
 // LXDResolveMountoptions resolves the provided mount options.
+// Deprecated: Use drivers.LXDResolveMountoptions.
 func LXDResolveMountoptions(options string) (uintptr, string) {
        mountFlags := uintptr(0)
        tmp := strings.SplitN(options, ",", -1)
@@ -225,6 +227,7 @@ func LXDUsesPool(dbObj *db.Cluster, onDiskPoolName string, 
driver string, onDisk
 }
 
 // MakeFSType creates the provided filesystem.
+// Deprecated: Use drivers.MakeFSType.
 func MakeFSType(path string, fsType string, options *MkfsOptions) (string, 
error) {
        var err error
        var msg string

From 89052290142543986d19eeba71269c829b1aa30b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 18 Nov 2019 15:24:41 +0100
Subject: [PATCH 4/4] lxd/storage/drivers: Add btrfs

This adds the btrfs storage driver.

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/storage/drivers/driver_btrfs.go       | 740 ++++++++++++++++++++++
 lxd/storage/drivers/driver_btrfs_utils.go | 380 +++++++++++
 lxd/storage/drivers/load.go               |   1 +
 3 files changed, 1121 insertions(+)
 create mode 100644 lxd/storage/drivers/driver_btrfs.go
 create mode 100644 lxd/storage/drivers/driver_btrfs_utils.go

diff --git a/lxd/storage/drivers/driver_btrfs.go 
b/lxd/storage/drivers/driver_btrfs.go
new file mode 100644
index 0000000000..c26d7d75a9
--- /dev/null
+++ b/lxd/storage/drivers/driver_btrfs.go
@@ -0,0 +1,740 @@
+package drivers
+
+import (
+       "fmt"
+       "io"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "strings"
+
+       "golang.org/x/sys/unix"
+
+       "github.com/lxc/lxd/lxd/migration"
+       "github.com/lxc/lxd/lxd/operations"
+       "github.com/lxc/lxd/lxd/storage/quota"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/api"
+       log "github.com/lxc/lxd/shared/log15"
+       "github.com/lxc/lxd/shared/units"
+)
+
+var btrfsVersion string
+var btrfsLoaded bool
+
+type btrfs struct {
+       common
+
+       remount uintptr
+}
+
+func (d *btrfs) load() error {
+       if btrfsLoaded {
+               return nil
+       }
+
+       // Validate the required binaries.
+       for _, tool := range []string{"btrfs"} {
+               _, err := exec.LookPath(tool)
+               if err != nil {
+                       return fmt.Errorf("Required tool '%s' is missing", tool)
+               }
+       }
+
+       // Detect and record the version.
+       if btrfsVersion == "" {
+               out, err := shared.RunCommand("btrfs", "version")
+               if err != nil {
+                       return err
+               }
+
+               count, err := fmt.Sscanf(strings.SplitN(out, " ", 2)[1], 
"v%s\n", &btrfsVersion)
+               if err != nil || count != 1 {
+                       return fmt.Errorf("The 'btrfs' tool isn't working 
properly")
+               }
+       }
+
+       btrfsLoaded = true
+       return nil
+}
+
+// Info returns info about the driver and its environment.
+func (d *btrfs) Info() Info {
+       return Info{
+               Name:               "btrfs",
+               Version:            btrfsVersion,
+               OptimizedImages:    false,
+               PreservesInodes:    !d.state.OS.RunningInUserNS,
+               Remote:             false,
+               VolumeTypes:        []VolumeType{VolumeTypeCustom, 
VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
+               BlockBacking:       false,
+               RunningQuotaResize: true,
+       }
+}
+
+func (d *btrfs) Create() error {
+       // WARNING: The Create() function cannot rely on any of the struct 
attributes being set.
+
+       // Set default source if missing.
+       defaultSource := filepath.Join(shared.VarPath("disks"), 
fmt.Sprintf("%s.img", d.name))
+       source := d.config["source"]
+
+       if source == "" {
+               source = defaultSource
+               d.config["source"] = source
+       } else if strings.HasPrefix(source, "/") {
+               source = shared.HostPath(source)
+       } else {
+               return fmt.Errorf("Invalid \"source\" property")
+       }
+
+       poolMntPoint := GetPoolMountPath(d.name)
+       isBlockDev := false
+
+       if source == defaultSource {
+               size, err := units.ParseByteSizeString(d.config["size"])
+               if err != nil {
+                       return err
+               }
+
+               err = createSparseFile(source, size)
+               if err != nil {
+                       return fmt.Errorf("Failed to create sparse file %q: 
%s", source, err)
+               }
+
+               output, err := MakeFSType(source, "btrfs", &MkfsOptions{Label: 
d.name})
+               if err != nil {
+                       return fmt.Errorf("Failed to create btrfs: %v (%s)", 
err, output)
+               }
+       } else {
+               isBlockDev = shared.IsBlockdevPath(source)
+
+               if isBlockDev {
+                       output, err := MakeFSType(source, "btrfs", 
&MkfsOptions{Label: d.name})
+                       if err != nil {
+                               return fmt.Errorf("Failed to create btrfs: %v 
(%s)", err, output)
+                       }
+               } else {
+                       if isBtrfsSubvolume(source) {
+                               subvols, err := btrfsSubvolumesGet(source)
+                               if err != nil {
+                                       return fmt.Errorf("Could not determine 
if existing btrfs subvolume ist empty: %s", err)
+                               }
+                               if len(subvols) > 0 {
+                                       return fmt.Errorf("Requested btrfs 
subvolume exists but is not empty")
+                               }
+                       } else {
+                               cleanSource := filepath.Clean(source)
+                               lxdDir := shared.VarPath()
+
+                               if shared.PathExists(source) && 
!isOnBtrfs(source) {
+                                       return fmt.Errorf("Existing path is 
neither a btrfs subvolume nor does it reside on a btrfs filesystem")
+                               } else if strings.HasPrefix(cleanSource, 
lxdDir) {
+                                       if cleanSource != poolMntPoint {
+                                               return fmt.Errorf("btrfs 
subvolumes requests in LXD directory %q are only valid under %q\n(e.g. 
source=%s)", shared.VarPath(), shared.VarPath("storage-pools"), poolMntPoint)
+                                       } else if d.state.OS.BackingFS != 
"btrfs" {
+                                               return fmt.Errorf("Creation of 
btrfs subvolume requested but %q does not reside on btrfs filesystem", source)
+                                       }
+                               }
+
+                               err := btrfsSubvolumeCreate(source)
+                               if err != nil {
+                                       return err
+                               }
+                       }
+               }
+       }
+
+       var err error
+       var devUUID string
+       mountFlags, mountOptions := resolveMountOptions(d.getMountOptions())
+       mountFlags |= d.remount
+
+       if isBlockDev {
+               devUUID, _ = shared.LookupUUIDByBlockDevPath(source)
+               // The symlink might not have been created even with the delay
+               // we granted it above. So try to call btrfs filesystem show and
+               // parse it out. (I __hate__ this!)
+               if devUUID == "" {
+                       devUUID, err = btrfsLookupFsUUID(source)
+                       if err != nil {
+                               return err
+                       }
+               }
+               d.config["source"] = devUUID
+
+               // If the symlink in /dev/disk/by-uuid hasn't been created yet
+               // aka we only detected it by parsing btrfs filesystem show, we
+               // cannot call StoragePoolMount() since it will try to do the
+               // reverse operation. So instead we shamelessly mount using the
+               // block device path at the time of pool creation.
+               err = tryMount(source, GetPoolMountPath(d.name), "btrfs", 
mountFlags, mountOptions)
+       } else {
+               _, err = d.Mount()
+       }
+       if err != nil {
+               return err
+       }
+
+       // Create default subvolumes.
+       subvolumes := []string{
+               filepath.Join(poolMntPoint, "containers"),
+               filepath.Join(poolMntPoint, "containers-snapshots"),
+               filepath.Join(poolMntPoint, "custom"),
+               filepath.Join(poolMntPoint, "custom-snapshots"),
+               filepath.Join(poolMntPoint, "images"),
+               filepath.Join(poolMntPoint, "virtual-machines"),
+               filepath.Join(poolMntPoint, "virtual-machines-snapshots"),
+       }
+
+       for _, subvol := range subvolumes {
+               err := btrfsSubvolumeCreate(subvol)
+               if err != nil {
+                       return fmt.Errorf("Could not create btrfs subvolume: 
%s", subvol)
+               }
+       }
+
+       return nil
+}
+
+// Delete removes the storage pool from the storage device.
+func (d *btrfs) Delete(op *operations.Operation) error {
+       source := d.config["source"]
+
+       if source == "" {
+               return fmt.Errorf("no \"source\" property found for the storage 
pool")
+       }
+
+       if strings.HasPrefix(source, "/") {
+               source = shared.HostPath(d.config["source"])
+       }
+
+       poolMntPoint := GetPoolMountPath(d.name)
+
+       // Delete default subvolumes.
+       subvolumes := []string{
+               filepath.Join(poolMntPoint, "containers"),
+               filepath.Join(poolMntPoint, "containers-snapshots"),
+               filepath.Join(poolMntPoint, "custom"),
+               filepath.Join(poolMntPoint, "custom-snapshots"),
+               filepath.Join(poolMntPoint, "images"),
+               filepath.Join(poolMntPoint, "virtual-machines"),
+               filepath.Join(poolMntPoint, "virtual-machines-snapshots"),
+       }
+
+       for _, subvol := range subvolumes {
+               err := btrfsSubvolumesDelete(subvol)
+               if err != nil {
+                       return fmt.Errorf("Could not delete btrfs subvolume: 
%s", subvol)
+               }
+       }
+
+       _, err := d.Unmount()
+       if err != nil {
+               return err
+       }
+
+       if filepath.IsAbs(source) {
+               var err error
+               cleanSource := filepath.Clean(source)
+               sourcePath := shared.VarPath("disks", d.name)
+               loopFilePath := sourcePath + ".img"
+
+               if cleanSource == loopFilePath {
+                       // This is a loop file so simply remove it.
+                       err = os.Remove(source)
+               } else {
+                       if !isBtrfsFilesystem(source) && 
isBtrfsSubvolume(source) {
+                               err = btrfsSubvolumesDelete(source)
+                       }
+               }
+               if err != nil && !os.IsNotExist(err) {
+                       return err
+               }
+       }
+
+       return wipeDirectory(poolMntPoint)
+}
+
+// Mount mounts the storage pool.
+func (d *btrfs) Mount() (bool, error) {
+       source := d.config["source"]
+
+       if source == "" {
+               return false, fmt.Errorf("no \"source\" property found for the 
storage pool")
+       }
+
+       if strings.HasPrefix(source, "/") {
+               source = shared.HostPath(d.config["source"])
+       }
+
+       poolMntPoint := GetPoolMountPath(d.name)
+
+       // Check whether the mount poolMntPoint exits.
+       if !shared.PathExists(poolMntPoint) {
+               err := os.MkdirAll(poolMntPoint, 0711)
+               if err != nil {
+                       return false, err
+               }
+       }
+
+       if shared.IsMountPoint(poolMntPoint) && (d.remount&unix.MS_REMOUNT) == 
0 {
+               return false, nil
+       }
+
+       mountFlags, mountOptions := resolveMountOptions(d.getMountOptions())
+       mountSource := source
+       isBlockDev := shared.IsBlockdevPath(source)
+
+       if filepath.IsAbs(source) {
+               cleanSource := filepath.Clean(source)
+               loopFilePath := shared.VarPath("disks", d.name+".img")
+
+               if !isBlockDev && cleanSource == loopFilePath {
+                       // If source == "${LXD_DIR}"/disks/{pool_name} it is a
+                       // loop file we're dealing with.
+                       //
+                       // Since we mount the loop device LO_FLAGS_AUTOCLEAR is
+                       // fine since the loop device will be kept around for as
+                       // long as the mount exists.
+                       loopF, loopErr := PrepareLoopDev(source, 
LoFlagsAutoclear)
+                       if loopErr != nil {
+                               return false, loopErr
+                       }
+                       mountSource = loopF.Name()
+                       defer loopF.Close()
+               } else if !isBlockDev && cleanSource != poolMntPoint {
+                       mountSource = source
+                       mountFlags |= unix.MS_BIND
+               } else if !isBlockDev && cleanSource == poolMntPoint && 
d.state.OS.BackingFS == "btrfs" {
+                       return false, nil
+               }
+               // User is using block device path.
+       } else {
+               // Try to lookup the disk device by UUID but don't fail. If we
+               // don't find one this might just mean we have been given the
+               // UUID of a subvolume.
+               byUUID := fmt.Sprintf("/dev/disk/by-uuid/%s", source)
+               diskPath, err := os.Readlink(byUUID)
+               if err == nil {
+                       mountSource = fmt.Sprintf("/dev/%s", 
strings.Trim(diskPath, "../../"))
+               } else {
+                       // We have very likely been given a subvolume UUID. In
+                       // this case we should simply assume that the user has
+                       // mounted the parent of the subvolume or the subvolume
+                       // itself. Otherwise this becomes a really messy
+                       // detection task.
+                       return false, nil
+               }
+       }
+
+       mountFlags |= d.remount
+       err := unix.Mount(mountSource, poolMntPoint, "btrfs", mountFlags, 
mountOptions)
+       if err != nil {
+               return false, err
+       }
+
+       return true, nil
+}
+
+// Unmount unmounts the storage pool.
+func (d *btrfs) Unmount() (bool, error) {
+       poolMntPoint := GetPoolMountPath(d.name)
+       return forceUnmount(poolMntPoint)
+}
+
+func (d *btrfs) GetResources() (*api.ResourcesStoragePool, error) {
+       // Use the generic VFS resources.
+       return vfsResources(GetPoolMountPath(d.name))
+}
+
+// GetVolumeUsage returns the disk space used by the volume.
+func (d *btrfs) GetVolumeUsage(volType VolumeType, volName string) (int64, 
error) {
+       return btrfsPoolVolumeQGroupUsage(GetVolumeMountPath(d.name, volType, 
volName))
+}
+
+// ValidateVolume validates the supplied volume config.
+func (d *btrfs) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
+       return d.validateVolume(vol, nil, removeUnknownKeys)
+}
+
+// HasVolume indicates whether a specific volume exists on the storage pool.
+func (d *btrfs) HasVolume(volType VolumeType, volName string) bool {
+       return isBtrfsSubvolume(GetVolumeMountPath(d.name, volType, volName))
+}
+
+// GetVolumeDiskPath returns the location and file format of a disk volume.
+func (d *btrfs) GetVolumeDiskPath(volType VolumeType, volName string) (string, 
error) {
+       return filepath.Join(GetVolumeMountPath(d.name, volType, volName), 
"root.img"), nil
+}
+
+// CreateVolume creates an empty volume and can optionally fill it by 
executing the supplied
+// filler function.
+func (d *btrfs) CreateVolume(vol Volume, filler func(mountPath, rootBlockPath 
string) error, op *operations.Operation) error {
+       volPath := vol.MountPath()
+
+       err := btrfsSubvolumeCreate(volPath)
+       if err != nil {
+               return err
+       }
+
+       revertSubvolume := true
+       defer func() {
+               if revertSubvolume {
+                       btrfsSubvolumeDelete(volPath)
+               }
+       }()
+
+       // Extract specified size from pool or volume config.
+       size := d.config["volume.size"]
+       if vol.config["size"] != "" {
+               size = vol.config["size"]
+       }
+
+       // Create sparse loopback file if volume is block.
+       rootBlockPath := ""
+       if vol.contentType == ContentTypeBlock {
+               // We expect the filler to copy the VM image into this path.
+               rootBlockPath, err = d.GetVolumeDiskPath(vol.volType, vol.name)
+               if err != nil {
+                       return err
+               }
+       } else {
+               // Get the volume ID for the new volume, which is used to set 
project quota.
+               volID, err := d.getVolID(vol.volType, vol.name)
+               if err != nil {
+                       return err
+               }
+
+               // Initialise the volume's quota using the volume ID.
+               err = d.initQuota(volPath, volID)
+               if err != nil {
+                       return err
+               }
+
+               defer func() {
+                       if revertSubvolume {
+                               d.deleteQuota(volPath, volID)
+                       }
+               }()
+
+               // Set the quota.
+               err = d.setQuota(volPath, volID, size)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Run the volume filler function if supplied.
+       if filler != nil {
+               err = filler(volPath, rootBlockPath)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // If we are creating a block volume, resize it to the requested size 
or 10GB.
+       // We expect the filler function to have converted the qcow2 image to 
raw into the rootBlockPath.
+       if vol.contentType == ContentTypeBlock {
+               blockSize := size
+               if blockSize == "" {
+                       blockSize = "10GB"
+               }
+
+               blockSizeBytes, err := units.ParseByteSizeString(blockSize)
+               if err != nil {
+                       return err
+               }
+
+               if shared.PathExists(rootBlockPath) {
+                       _, err = shared.RunCommand("qemu-img", "resize", "-f", 
"raw", rootBlockPath, fmt.Sprintf("%d", blockSizeBytes))
+                       if err != nil {
+                               return fmt.Errorf("Failed resizing disk image 
%s to size %s: %v", rootBlockPath, blockSize, err)
+                       }
+               } else {
+                       // If rootBlockPath doesn't exist, then there has been 
no filler function
+                       // supplied to create it from another source. So 
instead create an empty
+                       // volume (use for PXE booting a VM).
+                       _, err = shared.RunCommand("qemu-img", "create", "-f", 
"raw", rootBlockPath, fmt.Sprintf("%d", blockSizeBytes))
+                       if err != nil {
+                               return fmt.Errorf("Failed creating disk image 
%s as size %s: %v", rootBlockPath, blockSize, err)
+                       }
+               }
+       }
+
+       revertSubvolume = false
+       return nil
+}
+
+// MigrateVolume sends a volume for migration.
+func (d *btrfs) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs 
migration.VolumeSourceArgs, op *operations.Operation) error {
+       return ErrNotImplemented
+}
+
+// CreateVolumeFromMigration creates a volume being sent via a migration.
+func (d *btrfs) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, 
volTargetArgs migration.VolumeTargetArgs, op *operations.Operation) error {
+       return ErrNotImplemented
+}
+
+// CreateVolumeFromCopy provides same-pool volume copying functionality.
+func (d *btrfs) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots 
bool, op *operations.Operation) error {
+       err := btrfsSubvolumesSnapshotCreate(srcVol.MountPath(), 
vol.MountPath(), false, true, d.state.OS.RunningInUserNS)
+       if err != nil {
+               return err
+       }
+
+       if !copySnapshots || srcVol.IsSnapshot() {
+               return nil
+       }
+
+       snapshots, err := d.VolumeSnapshots(srcVol.volType, srcVol.name, op)
+       if err != nil {
+               return err
+       }
+
+       if len(snapshots) == 0 {
+               return nil
+       }
+
+       for _, snap := range snapshots {
+               srcSnapshot := GetVolumeMountPath(d.name, srcVol.volType, 
GetSnapshotVolumeName(srcVol.name, snap))
+               dstSnapshot := GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, snap))
+
+               err = btrfsSubvolumeSnapshotCreate(srcSnapshot, dstSnapshot, 
true, d.state.OS.RunningInUserNS)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// VolumeSnapshots returns a list of snapshots for the volume.
+func (d *btrfs) VolumeSnapshots(volType VolumeType, volName string, op 
*operations.Operation) ([]string, error) {
+       return btrfsSubvolumeSnapshotsGet(GetVolumeSnapshotDir(d.name, volType, 
volName))
+}
+
+// UpdateVolume applies config changes to the volume.
+func (d *btrfs) UpdateVolume(vol Volume, changedConfig map[string]string) 
error {
+       if vol.contentType != ContentTypeFS {
+               return fmt.Errorf("Content type not supported")
+       }
+
+       if vol.volType != VolumeTypeCustom {
+               return fmt.Errorf("Volume type not supported")
+       }
+
+       return d.SetVolumeQuota(vol.volType, vol.name, vol.config["size"], nil)
+}
+
+// RenameVolume renames a volume and its snapshots.
+func (d *btrfs) RenameVolume(volType VolumeType, volName string, newVolName 
string, op *operations.Operation) error {
+       srcVolumePath := GetVolumeMountPath(d.name, volType, volName)
+       dstVolumePath := GetVolumeMountPath(d.name, volType, newVolName)
+
+       err := os.Rename(srcVolumePath, dstVolumePath)
+       if err != nil {
+               return err
+       }
+
+       srcSnapshotDir := GetVolumeSnapshotDir(d.name, volType, volName)
+       dstSnapshotDir := GetVolumeSnapshotDir(d.name, volType, newVolName)
+
+       err = os.Rename(srcSnapshotDir, dstSnapshotDir)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// RestoreVolume restores a volume from a snapshot.
+func (d *btrfs) RestoreVolume(vol Volume, snapshotName string, op 
*operations.Operation) error {
+       source := GetVolumeMountPath(d.name, vol.volType, 
GetSnapshotVolumeName(vol.name, snapshotName))
+
+       return btrfsSubvolumesSnapshotCreate(source, vol.MountPath(), false, 
true, d.state.OS.RunningInUserNS)
+}
+
+// DeleteVolume deletes a volume of the storage device. If any snapshots of 
the volume remain then
+// this function will return an error.
+func (d *btrfs) DeleteVolume(volType VolumeType, volName string, op 
*operations.Operation) error {
+       snapshots, err := d.VolumeSnapshots(volType, volName, op)
+       if err != nil {
+               return err
+       }
+
+       if len(snapshots) > 0 {
+               return fmt.Errorf("Cannot remove a volume that has snapshots")
+       }
+
+       volPath := GetVolumeMountPath(d.name, volType, volName)
+
+       if !shared.PathExists(volPath) || !isBtrfsSubvolume(volPath) {
+               return nil
+       }
+
+       err = btrfsSubvolumeDelete(volPath)
+       if err != nil {
+               return err
+       }
+
+       // Although the volume snapshot directory should already be removed, 
lets remove it here
+       // to just in case the top-level directory is left.
+       err = deleteParentSnapshotDirIfEmpty(d.name, volType, volName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// MountVolume simulates mounting a volume. As dir driver doesn't have volumes 
to mount it returns
+// false indicating that there is no need to issue an unmount.
+func (d *btrfs) MountVolume(volType VolumeType, volName string, op 
*operations.Operation) (bool, error) {
+       return false, nil
+}
+
+// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to 
avoid accidental modifications.
+func (d *btrfs) MountVolumeSnapshot(volType VolumeType, volName, snapshotName 
string, op *operations.Operation) (bool, error) {
+       return false, ErrNotImplemented
+}
+
+// UnmountVolume simulates unmounting a volume. As dir driver doesn't have 
volumes to unmount it
+// returns false indicating the volume was already unmounted.
+func (d *btrfs) UnmountVolume(volType VolumeType, volName string, op 
*operations.Operation) (bool, error) {
+       return false, nil
+}
+
+// UnmountVolumeSnapshot removes the read-only mount placed on top of a 
snapshot.
+func (d *btrfs) UnmountVolumeSnapshot(volType VolumeType, volName, 
snapshotName string, op *operations.Operation) (bool, error) {
+       return false, nil
+}
+
+// SetVolumeQuota sets the quota on the volume.
+func (d *btrfs) SetVolumeQuota(volType VolumeType, volName, size string, op 
*operations.Operation) error {
+       volPath := GetVolumeMountPath(d.name, volType, volName)
+
+       volID, err := d.getVolID(volType, volName)
+       if err != nil {
+               return err
+       }
+
+       return d.setQuota(volPath, volID, size)
+}
+
+// quotaProjectID generates a project quota ID from a volume ID.
+func (d *btrfs) quotaProjectID(volID int64) uint32 {
+       return uint32(volID + 10000)
+}
+
+// initQuota initialises the project quota on the path. The volID generates a 
quota project ID.
+func (d *btrfs) initQuota(path string, volID int64) error {
+       if volID == 0 {
+               return fmt.Errorf("Missing volume ID")
+       }
+
+       ok, err := quota.Supported(path)
+       if err != nil || !ok {
+               // Skipping quota as underlying filesystem doesn't suppport 
project quotas.
+               return nil
+       }
+
+       err = quota.SetProject(path, d.quotaProjectID(volID))
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// setQuota sets the project quota on the path. The volID generates a quota 
project ID.
+func (d *btrfs) setQuota(path string, volID int64, size string) error {
+       if volID == 0 {
+               return fmt.Errorf("Missing volume ID")
+       }
+
+       // If size not specified in volume config, then use pool's default 
volume.size setting.
+       if size == "" || size == "0" {
+               size = d.config["volume.size"]
+       }
+
+       sizeBytes, err := units.ParseByteSizeString(size)
+       if err != nil {
+               return err
+       }
+
+       ok, err := quota.Supported(path)
+       if err != nil || !ok {
+               if sizeBytes > 0 {
+                       // Skipping quota as underlying filesystem doesn't 
suppport project quotas.
+                       d.logger.Warn("The backing filesystem doesn't support 
quotas, skipping quota", log.Ctx{"path": path})
+               }
+               return nil
+       }
+
+       err = quota.SetProjectQuota(path, d.quotaProjectID(volID), sizeBytes)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// deleteQuota removes the project quota for a volID from a path.
+func (d *btrfs) deleteQuota(path string, volID int64) error {
+       if volID == 0 {
+               return fmt.Errorf("Missing volume ID")
+       }
+
+       ok, err := quota.Supported(path)
+       if err != nil || !ok {
+               // Skipping quota as underlying filesystem doesn't suppport 
project quotas.
+               return nil
+       }
+
+       err = quota.SetProject(path, 0)
+       if err != nil {
+               return err
+       }
+
+       err = quota.SetProjectQuota(path, d.quotaProjectID(volID), 0)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// CreateVolumeSnapshot creates a snapshot of a volume.
+func (d *btrfs) CreateVolumeSnapshot(volType VolumeType, volName string, 
newSnapshotName string, op *operations.Operation) error {
+       sourcePath := GetVolumeMountPath(d.name, volType, volName)
+       targetPath := GetVolumeMountPath(d.name, volType, newSnapshotName)
+
+       return btrfsSubvolumesSnapshotCreate(sourcePath, targetPath, true, 
true, d.state.OS.RunningInUserNS)
+}
+
+// DeleteVolumeSnapshot removes a snapshot from the storage device. The 
volName and snapshotName
+// must be bare names and should not be in the format "volume/snapshot".
+func (d *btrfs) DeleteVolumeSnapshot(volType VolumeType, volName string, 
snapshotName string, op *operations.Operation) error {
+       fullSnapshotName := GetSnapshotVolumeName(volName, snapshotName)
+       return btrfsSubvolumesDelete(GetVolumeMountPath(d.name, volType, 
fullSnapshotName))
+}
+
+// RenameVolumeSnapshot renames a volume snapshot.
+func (d *btrfs) RenameVolumeSnapshot(volType VolumeType, volName string, 
snapshotName string, newSnapshotName string, op *operations.Operation) error {
+       oldFullSnapshotName := GetSnapshotVolumeName(volName, snapshotName)
+       newFullSnapshotName := GetSnapshotVolumeName(volName, newSnapshotName)
+
+       oldPath := GetVolumeMountPath(d.name, volType, oldFullSnapshotName)
+       newPath := GetVolumeMountPath(d.name, volType, newFullSnapshotName)
+
+       return os.Rename(oldPath, newPath)
+}
+
+func (d *btrfs) getMountOptions() string {
+       if d.config["btrfs.mount_options"] != "" {
+               return d.config["btrfs.mount_options"]
+       }
+
+       return "user_subvol_rm_allowed"
+}
diff --git a/lxd/storage/drivers/driver_btrfs_utils.go 
b/lxd/storage/drivers/driver_btrfs_utils.go
new file mode 100644
index 0000000000..47e5ec2b93
--- /dev/null
+++ b/lxd/storage/drivers/driver_btrfs_utils.go
@@ -0,0 +1,380 @@
+package drivers
+
+import (
+       "fmt"
+       "os"
+       "path"
+       "path/filepath"
+       "sort"
+       "strconv"
+       "strings"
+
+       "github.com/lxc/lxd/lxd/util"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/logger"
+       "golang.org/x/sys/unix"
+)
+
+var errBtrfsNoQuota = fmt.Errorf("Quotas disabled on filesystem")
+var errBtrfsNoQGroup = fmt.Errorf("Unable to find quota group")
+
+func btrfsSubvolumeCreate(subvol string) error {
+       parentDestPath := filepath.Dir(subvol)
+       if !shared.PathExists(parentDestPath) {
+               err := os.MkdirAll(parentDestPath, 0711)
+               if err != nil {
+                       return err
+               }
+       }
+
+       _, err := shared.RunCommand(
+               "btrfs",
+               "subvolume",
+               "create",
+               subvol)
+       if err != nil {
+               logger.Errorf("Failed to create BTRFS subvolume \"%s\": %v", 
subvol, err)
+               return err
+       }
+
+       return nil
+}
+
+func btrfsSubvolumesGet(path string) ([]string, error) {
+       result := []string{}
+
+       if !strings.HasSuffix(path, "/") {
+               path = path + "/"
+       }
+
+       // Unprivileged users can't get to fs internals
+       filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error 
{
+               // Skip walk errors
+               if err != nil {
+                       return nil
+               }
+
+               // Ignore the base path
+               if strings.TrimRight(fpath, "/") == strings.TrimRight(path, 
"/") {
+                       return nil
+               }
+
+               // Subvolumes can only be directories
+               if !fi.IsDir() {
+                       return nil
+               }
+
+               // Check if a btrfs subvolume
+               if isBtrfsSubvolume(fpath) {
+                       result = append(result, strings.TrimPrefix(fpath, path))
+               }
+
+               return nil
+       })
+
+       return result, nil
+}
+
+func btrfsSubvolumeSnapshotsGet(path string) ([]string, error) {
+       result := []string{}
+
+       if !strings.HasSuffix(path, "/") {
+               path = path + "/"
+       }
+
+       // Unprivileged users can't get to fs internals
+       filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error 
{
+               // Skip walk errors
+               if err != nil {
+                       return nil
+               }
+
+               // Ignore the base path
+               if strings.TrimRight(fpath, "/") == strings.TrimRight(path, 
"/") {
+                       return nil
+               }
+
+               // Subvolumes can only be directories
+               if !fi.IsDir() {
+                       return nil
+               }
+
+               // Check if a btrfs subvolume snapshot
+               if isBtrfsSubvolumeSnapshot(fpath) {
+                       result = append(result, strings.TrimPrefix(fpath, path))
+               }
+
+               return nil
+       })
+
+       return result, nil
+}
+
+// isBtrfsSubvolume returns true if the given Path is a btrfs subvolume else
+// false.
+func isBtrfsSubvolume(subvolPath string) bool {
+       fs := unix.Stat_t{}
+       err := unix.Lstat(subvolPath, &fs)
+       if err != nil {
+               return false
+       }
+
+       // Check if BTRFS_FIRST_FREE_OBJECTID
+       if fs.Ino != 256 {
+               return false
+       }
+
+       return true
+}
+
+// isBtrfsSubvolumeSnapshot returns true if the given Path is a btrfs subvolume
+// snapshot else false.
+func isBtrfsSubvolumeSnapshot(subvolPath string) bool {
+       fs := unix.Stat_t{}
+       err := unix.Lstat(subvolPath, &fs)
+       if err != nil {
+               return false
+       }
+
+       return fs.Ino == 258
+}
+
+func isOnBtrfs(path string) bool {
+       fs := unix.Statfs_t{}
+
+       err := unix.Statfs(path, &fs)
+       if err != nil {
+               return false
+       }
+
+       if fs.Type != util.FilesystemSuperMagicBtrfs {
+               return false
+       }
+
+       return true
+}
+
+func btrfsLookupFsUUID(fs string) (string, error) {
+       output, err := shared.RunCommand(
+               "btrfs",
+               "filesystem",
+               "show",
+               "--raw",
+               fs)
+       if err != nil {
+               return "", fmt.Errorf("failed to detect UUID")
+       }
+
+       outputString := output
+       idx := strings.Index(outputString, "uuid: ")
+       outputString = outputString[idx+6:]
+       outputString = strings.TrimSpace(outputString)
+       idx = strings.Index(outputString, "\t")
+       outputString = outputString[:idx]
+       outputString = strings.Trim(outputString, "\n")
+
+       return outputString, nil
+}
+
+// btrfsSubvolumesDelete is the recursive variant on btrfsSubvolumeDelete,
+// it first deletes subvolumes of the subvolume and then the
+// subvolume itself.
+func btrfsSubvolumesDelete(subvol string) error {
+       // Delete subsubvols.
+       subsubvols, err := btrfsSubvolumesGet(subvol)
+       if err != nil {
+               return err
+       }
+       sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
+
+       for _, subsubvol := range subsubvols {
+               err := btrfsSubvolumeDelete(path.Join(subvol, subsubvol))
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Delete the subvol itself
+       err = btrfsSubvolumeDelete(subvol)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func btrfsSubvolumeDelete(subvol string) error {
+       // Attempt (but don't fail on) to delete any qgroup on the subvolume
+       qgroup, err := btrfsSubvolumeQGroup(subvol)
+       if err == nil {
+               shared.RunCommand(
+                       "btrfs",
+                       "qgroup",
+                       "destroy",
+                       qgroup,
+                       subvol)
+       }
+
+       // Attempt to make the subvolume writable
+       shared.RunCommand("btrfs", "property", "set", subvol, "ro", "false")
+
+       // Delete the subvolume itself
+       _, err = shared.RunCommand(
+               "btrfs",
+               "subvolume",
+               "delete",
+               subvol)
+
+       return err
+}
+
+func btrfsSubvolumeQGroup(subvol string) (string, error) {
+       output, err := shared.RunCommand(
+               "btrfs",
+               "qgroup",
+               "show",
+               "-e",
+               "-f",
+               subvol)
+
+       if err != nil {
+               return "", errBtrfsNoQuota
+       }
+
+       var qgroup string
+       for _, line := range strings.Split(output, "\n") {
+               if line == "" || strings.HasPrefix(line, "qgroupid") || 
strings.HasPrefix(line, "---") {
+                       continue
+               }
+
+               fields := strings.Fields(line)
+               if len(fields) != 4 {
+                       continue
+               }
+
+               qgroup = fields[0]
+       }
+
+       if qgroup == "" {
+               return "", errBtrfsNoQGroup
+       }
+
+       return qgroup, nil
+}
+
+func isBtrfsFilesystem(path string) bool {
+       _, err := shared.RunCommand("btrfs", "filesystem", "show", path)
+       if err != nil {
+               return false
+       }
+
+       return true
+}
+
+func btrfsPoolVolumeQGroupUsage(subvol string) (int64, error) {
+       output, err := shared.RunCommand(
+               "btrfs",
+               "qgroup",
+               "show",
+               "-e",
+               "-f",
+               subvol)
+
+       if err != nil {
+               return -1, fmt.Errorf("BTRFS quotas not supported. Try enabling 
them with \"btrfs quota enable\"")
+       }
+
+       for _, line := range strings.Split(output, "\n") {
+               if line == "" || strings.HasPrefix(line, "qgroupid") || 
strings.HasPrefix(line, "---") {
+                       continue
+               }
+
+               fields := strings.Fields(line)
+               if len(fields) != 4 {
+                       continue
+               }
+
+               usage, err := strconv.ParseInt(fields[2], 10, 64)
+               if err != nil {
+                       continue
+               }
+
+               return usage, nil
+       }
+
+       return -1, fmt.Errorf("Unable to find current qgroup usage")
+}
+
+func btrfsSubvolumesSnapshotCreate(source string, dest string, readonly bool, 
recursive bool, userns bool) error {
+       // Now snapshot all subvolumes of the root.
+       if recursive {
+               // Get a list of subvolumes of the root
+               subsubvols, err := btrfsSubvolumesGet(source)
+               if err != nil {
+                       return err
+               }
+               sort.Sort(sort.StringSlice(subsubvols))
+
+               if len(subsubvols) > 0 && readonly {
+                       // A root with subvolumes can never be readonly,
+                       // also don't make subvolumes readonly.
+                       readonly = false
+
+                       logger.Warnf("Subvolumes detected, ignoring ro flag")
+               }
+
+               // First snapshot the root
+               err = btrfsSubvolumeSnapshotCreate(source, dest, readonly, 
userns)
+               if err != nil {
+                       return err
+               }
+
+               for _, subsubvol := range subsubvols {
+                       // Clear the target for the subvol to use
+                       os.Remove(path.Join(dest, subsubvol))
+
+                       err := btrfsSubvolumeSnapshotCreate(path.Join(source, 
subsubvol), path.Join(dest, subsubvol), readonly, userns)
+                       if err != nil {
+                               return err
+                       }
+               }
+       } else {
+               err := btrfsSubvolumeSnapshotCreate(source, dest, readonly, 
userns)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+func btrfsSubvolumeSnapshotCreate(source string, dest string, readonly bool, 
userns bool) error {
+       var output string
+       var err error
+       if readonly && !userns {
+               output, err = shared.RunCommand(
+                       "btrfs",
+                       "subvolume",
+                       "snapshot",
+                       "-r",
+                       source,
+                       dest)
+       } else {
+               output, err = shared.RunCommand(
+                       "btrfs",
+                       "subvolume",
+                       "snapshot",
+                       source,
+                       dest)
+       }
+       if err != nil {
+               return fmt.Errorf(
+                       "subvolume snapshot failed, source=%s, dest=%s, 
output=%s",
+                       source,
+                       dest,
+                       output,
+               )
+       }
+
+       return err
+}
diff --git a/lxd/storage/drivers/load.go b/lxd/storage/drivers/load.go
index bced6925e7..34fe37c5d0 100644
--- a/lxd/storage/drivers/load.go
+++ b/lxd/storage/drivers/load.go
@@ -8,6 +8,7 @@ import (
 var drivers = map[string]func() driver{
        "dir":    func() driver { return &dir{} },
        "cephfs": func() driver { return &cephfs{} },
+       "btrfs":  func() driver { return &btrfs{} },
 }
 
 // Load returns a Driver for an existing low-level storage pool.
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to