|
@@ -16,13 +16,16 @@ import "C"
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "math"
|
|
|
"os"
|
|
|
"path"
|
|
|
"path/filepath"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
"syscall"
|
|
|
"unsafe"
|
|
|
|
|
|
+ "github.com/Sirupsen/logrus"
|
|
|
"github.com/docker/docker/daemon/graphdriver"
|
|
|
"github.com/docker/docker/pkg/idtools"
|
|
|
"github.com/docker/docker/pkg/mount"
|
|
@@ -119,6 +122,7 @@ type Driver struct {
|
|
|
gidMaps []idtools.IDMap
|
|
|
options btrfsOptions
|
|
|
quotaEnabled bool
|
|
|
+ once sync.Once
|
|
|
}
|
|
|
|
|
|
// String prints the name of the driver (btrfs).
|
|
@@ -237,7 +241,7 @@ func isSubvolume(p string) (bool, error) {
|
|
|
return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
|
|
|
}
|
|
|
|
|
|
-func subvolDelete(dirpath, name string) error {
|
|
|
+func subvolDelete(dirpath, name string, quotaEnabled bool) error {
|
|
|
dir, err := openDir(dirpath)
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -265,7 +269,7 @@ func subvolDelete(dirpath, name string) error {
|
|
|
return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
|
|
|
}
|
|
|
if sv {
|
|
|
- if err := subvolDelete(path.Dir(p), f.Name()); err != nil {
|
|
|
+ if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil {
|
|
|
return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
|
|
|
}
|
|
|
}
|
|
@@ -276,6 +280,21 @@ func subvolDelete(dirpath, name string) error {
|
|
|
return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
|
|
|
}
|
|
|
|
|
|
+ if quotaEnabled {
|
|
|
+ if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
|
|
|
+ var args C.struct_btrfs_ioctl_qgroup_create_args
|
|
|
+ args.qgroupid = C.__u64(qgroupid)
|
|
|
+
|
|
|
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
|
|
|
+ uintptr(unsafe.Pointer(&args)))
|
|
|
+ if errno != 0 {
|
|
|
+ logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// all subvolumes have been removed
|
|
|
// now remove the one originally passed in
|
|
|
for i, c := range []byte(name) {
|
|
@@ -289,15 +308,25 @@ func subvolDelete(dirpath, name string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func (d *Driver) updateQuotaStatus() {
|
|
|
+ d.once.Do(func() {
|
|
|
+ if !d.quotaEnabled {
|
|
|
+ // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
|
|
+ if err := subvolQgroupStatus(d.home); err != nil {
|
|
|
+ // quota is still not enabled
|
|
|
+ return
|
|
|
+ }
|
|
|
+ d.quotaEnabled = true
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
func (d *Driver) subvolEnableQuota() error {
|
|
|
+ d.updateQuotaStatus()
|
|
|
+
|
|
|
if d.quotaEnabled {
|
|
|
return nil
|
|
|
}
|
|
|
- // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
|
|
- if _, err := subvolLookupQgroup(d.home); err == nil {
|
|
|
- d.quotaEnabled = true
|
|
|
- return nil
|
|
|
- }
|
|
|
|
|
|
dir, err := openDir(d.home)
|
|
|
if err != nil {
|
|
@@ -319,13 +348,10 @@ func (d *Driver) subvolEnableQuota() error {
|
|
|
}
|
|
|
|
|
|
func (d *Driver) subvolDisableQuota() error {
|
|
|
+ d.updateQuotaStatus()
|
|
|
+
|
|
|
if !d.quotaEnabled {
|
|
|
- // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
|
|
- if _, err := subvolLookupQgroup(d.home); err != nil {
|
|
|
- // quota is still not enabled
|
|
|
- return nil
|
|
|
- }
|
|
|
- d.quotaEnabled = true
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
dir, err := openDir(d.home)
|
|
@@ -348,13 +374,10 @@ func (d *Driver) subvolDisableQuota() error {
|
|
|
}
|
|
|
|
|
|
func (d *Driver) subvolRescanQuota() error {
|
|
|
+ d.updateQuotaStatus()
|
|
|
+
|
|
|
if !d.quotaEnabled {
|
|
|
- // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
|
|
- if _, err := subvolLookupQgroup(d.home); err != nil {
|
|
|
- // quota is still not enabled
|
|
|
- return nil
|
|
|
- }
|
|
|
- d.quotaEnabled = true
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
dir, err := openDir(d.home)
|
|
@@ -392,6 +415,38 @@ func subvolLimitQgroup(path string, size uint64) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
|
|
|
+// with search key of BTRFS_QGROUP_STATUS_KEY.
|
|
|
+// In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY.
|
|
|
+// For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
|
|
|
+func subvolQgroupStatus(path string) error {
|
|
|
+ dir, err := openDir(path)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer closeDir(dir)
|
|
|
+
|
|
|
+ var args C.struct_btrfs_ioctl_search_args
|
|
|
+ args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
|
|
|
+ args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
|
|
|
+ args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
|
|
|
+ args.key.max_objectid = C.__u64(math.MaxUint64)
|
|
|
+ args.key.max_offset = C.__u64(math.MaxUint64)
|
|
|
+ args.key.max_transid = C.__u64(math.MaxUint64)
|
|
|
+ args.key.nr_items = 4096
|
|
|
+
|
|
|
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
|
|
|
+ uintptr(unsafe.Pointer(&args)))
|
|
|
+ if errno != 0 {
|
|
|
+ return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error())
|
|
|
+ }
|
|
|
+ sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
|
|
|
+ if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
|
|
|
+ return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
func subvolLookupQgroup(path string) (uint64, error) {
|
|
|
dir, err := openDir(path)
|
|
|
if err != nil {
|
|
@@ -533,7 +588,11 @@ func (d *Driver) Remove(id string) error {
|
|
|
if _, err := os.Stat(dir); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- if err := subvolDelete(d.subvolumesDir(), id); err != nil {
|
|
|
+
|
|
|
+ // Call updateQuotaStatus() to invoke status update
|
|
|
+ d.updateQuotaStatus()
|
|
|
+
|
|
|
+ if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := system.EnsureRemoveAll(dir); err != nil {
|