8 #include <btrfs/ioctl.h>
9 #include <btrfs/ctree.h>
11 static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) {
12 snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value);
30 "github.com/Sirupsen/logrus"
31 "github.com/docker/docker/daemon/graphdriver"
32 "github.com/docker/docker/pkg/idtools"
33 "github.com/docker/docker/pkg/mount"
34 "github.com/docker/docker/pkg/parsers"
35 "github.com/docker/docker/pkg/system"
36 "github.com/docker/go-units"
37 "github.com/opencontainers/selinux/go-selinux/label"
41 graphdriver.Register("btrfs", Init)
44 type btrfsOptions struct {
49 // Init returns a new BTRFS driver.
50 // An error is returned if BTRFS is not supported.
51 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
53 fsMagic, err := graphdriver.GetFSMagic(home)
58 if fsMagic != graphdriver.FsMagicBtrfs {
59 return nil, graphdriver.ErrPrerequisites
62 rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
66 if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil {
70 if err := mount.MakePrivate(home); err != nil {
74 opt, userDiskQuota, err := parseOptions(options)
87 if err := driver.subvolEnableQuota(); err != nil {
92 return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
95 func parseOptions(opt []string) (btrfsOptions, bool, error) {
96 var options btrfsOptions
97 userDiskQuota := false
98 for _, option := range opt {
99 key, val, err := parsers.ParseKeyValueOpt(option)
101 return options, userDiskQuota, err
103 key = strings.ToLower(key)
105 case "btrfs.min_space":
106 minSpace, err := units.RAMInBytes(val)
108 return options, userDiskQuota, err
111 options.minSpace = uint64(minSpace)
113 return options, userDiskQuota, fmt.Errorf("Unknown option %s", key)
116 return options, userDiskQuota, nil
119 // Driver contains information about the filesystem mounted.
121 //root of the file system
123 uidMaps []idtools.IDMap
124 gidMaps []idtools.IDMap
130 // String prints the name of the driver (btrfs).
131 func (d *Driver) String() string {
135 // Status returns current driver information in a two dimensional string array.
136 // Output contains "Build Version" and "Library Version" of the btrfs libraries used.
137 // Version information can be used to check compatibility with your kernel.
138 func (d *Driver) Status() [][2]string {
139 status := [][2]string{}
140 if bv := btrfsBuildVersion(); bv != "-" {
141 status = append(status, [2]string{"Build Version", bv})
143 if lv := btrfsLibVersion(); lv != -1 {
144 status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
149 // GetMetadata returns empty metadata for this driver.
150 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
154 // Cleanup unmounts the home directory.
155 func (d *Driver) Cleanup() error {
156 if err := d.subvolDisableQuota(); err != nil {
160 return mount.Unmount(d.home)
163 func free(p *C.char) {
164 C.free(unsafe.Pointer(p))
167 func openDir(path string) (*C.DIR, error) {
168 Cpath := C.CString(path)
171 dir := C.opendir(Cpath)
173 return nil, fmt.Errorf("Can't open dir")
178 func closeDir(dir *C.DIR) {
184 func getDirFd(dir *C.DIR) uintptr {
185 return uintptr(C.dirfd(dir))
188 func subvolCreate(path, name string) error {
189 dir, err := openDir(path)
195 var args C.struct_btrfs_ioctl_vol_args
196 for i, c := range []byte(name) {
197 args.name[i] = C.char(c)
200 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE,
201 uintptr(unsafe.Pointer(&args)))
203 return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error())
208 func subvolSnapshot(src, dest, name string) error {
209 srcDir, err := openDir(src)
213 defer closeDir(srcDir)
215 destDir, err := openDir(dest)
219 defer closeDir(destDir)
221 var args C.struct_btrfs_ioctl_vol_args_v2
222 args.fd = C.__s64(getDirFd(srcDir))
224 var cs = C.CString(name)
225 C.set_name_btrfs_ioctl_vol_args_v2(&args, cs)
226 C.free(unsafe.Pointer(cs))
228 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2,
229 uintptr(unsafe.Pointer(&args)))
231 return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error())
236 func isSubvolume(p string) (bool, error) {
237 var bufStat syscall.Stat_t
238 if err := syscall.Lstat(p, &bufStat); err != nil {
242 // return true if it is a btrfs subvolume
243 return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
246 func subvolDelete(dirpath, name string, quotaEnabled bool) error {
247 dir, err := openDir(dirpath)
252 fullPath := path.Join(dirpath, name)
254 var args C.struct_btrfs_ioctl_vol_args
256 // walk the btrfs subvolumes
257 walkSubvolumes := func(p string, f os.FileInfo, err error) error {
259 if os.IsNotExist(err) && p != fullPath {
260 // missing most likely because the path was a subvolume that got removed in the previous iteration
261 // since it's gone anyway, we don't care
264 return fmt.Errorf("error walking subvolumes: %v", err)
266 // we want to check children only so skip itself
267 // it will be removed after the filepath walk anyways
268 if f.IsDir() && p != fullPath {
269 sv, err := isSubvolume(p)
271 return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
274 if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil {
275 return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
281 if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil {
282 return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
286 if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
287 var args C.struct_btrfs_ioctl_qgroup_create_args
288 args.qgroupid = C.__u64(qgroupid)
290 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
291 uintptr(unsafe.Pointer(&args)))
293 logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
296 logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
300 // all subvolumes have been removed
301 // now remove the one originally passed in
302 for i, c := range []byte(name) {
303 args.name[i] = C.char(c)
305 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY,
306 uintptr(unsafe.Pointer(&args)))
308 return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error())
313 func (d *Driver) updateQuotaStatus() {
316 // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
317 if err := subvolQgroupStatus(d.home); err != nil {
318 // quota is still not enabled
321 d.quotaEnabled = true
326 func (d *Driver) subvolEnableQuota() error {
327 d.updateQuotaStatus()
333 dir, err := openDir(d.home)
339 var args C.struct_btrfs_ioctl_quota_ctl_args
340 args.cmd = C.BTRFS_QUOTA_CTL_ENABLE
341 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
342 uintptr(unsafe.Pointer(&args)))
344 return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error())
347 d.quotaEnabled = true
352 func (d *Driver) subvolDisableQuota() error {
353 d.updateQuotaStatus()
359 dir, err := openDir(d.home)
365 var args C.struct_btrfs_ioctl_quota_ctl_args
366 args.cmd = C.BTRFS_QUOTA_CTL_DISABLE
367 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
368 uintptr(unsafe.Pointer(&args)))
370 return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error())
373 d.quotaEnabled = false
378 func (d *Driver) subvolRescanQuota() error {
379 d.updateQuotaStatus()
385 dir, err := openDir(d.home)
391 var args C.struct_btrfs_ioctl_quota_rescan_args
392 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT,
393 uintptr(unsafe.Pointer(&args)))
395 return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error())
401 func subvolLimitQgroup(path string, size uint64) error {
402 dir, err := openDir(path)
408 var args C.struct_btrfs_ioctl_qgroup_limit_args
409 args.lim.max_referenced = C.__u64(size)
410 args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER
411 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT,
412 uintptr(unsafe.Pointer(&args)))
414 return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error())
420 // subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
421 // with search key of BTRFS_QGROUP_STATUS_KEY.
422 // In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY.
423 // For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
424 func subvolQgroupStatus(path string) error {
425 dir, err := openDir(path)
431 var args C.struct_btrfs_ioctl_search_args
432 args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
433 args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
434 args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
435 args.key.max_objectid = C.__u64(math.MaxUint64)
436 args.key.max_offset = C.__u64(math.MaxUint64)
437 args.key.max_transid = C.__u64(math.MaxUint64)
438 args.key.nr_items = 4096
440 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
441 uintptr(unsafe.Pointer(&args)))
443 return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error())
445 sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
446 if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
447 return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type)
452 func subvolLookupQgroup(path string) (uint64, error) {
453 dir, err := openDir(path)
459 var args C.struct_btrfs_ioctl_ino_lookup_args
460 args.objectid = C.BTRFS_FIRST_FREE_OBJECTID
462 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP,
463 uintptr(unsafe.Pointer(&args)))
465 return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error())
467 if args.treeid == 0 {
468 return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir)
471 return uint64(args.treeid), nil
474 func (d *Driver) subvolumesDir() string {
475 return path.Join(d.home, "subvolumes")
478 func (d *Driver) subvolumesDirID(id string) string {
479 return path.Join(d.subvolumesDir(), id)
482 func (d *Driver) quotasDir() string {
483 return path.Join(d.home, "quotas")
486 func (d *Driver) quotasDirID(id string) string {
487 return path.Join(d.quotasDir(), id)
490 // CreateReadWrite creates a layer that is writable for use as a container
492 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
493 return d.Create(id, parent, opts)
496 // Create the filesystem with given id.
497 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
498 quotas := path.Join(d.home, "quotas")
499 subvolumes := path.Join(d.home, "subvolumes")
500 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
504 if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil {
508 if err := subvolCreate(subvolumes, id); err != nil {
512 parentDir := d.subvolumesDirID(parent)
513 st, err := os.Stat(parentDir)
518 return fmt.Errorf("%s: not a directory", parentDir)
520 if err := subvolSnapshot(parentDir, subvolumes, id); err != nil {
525 var storageOpt map[string]string
527 storageOpt = opts.StorageOpt
530 if _, ok := storageOpt["size"]; ok {
532 if err := d.parseStorageOpt(storageOpt, driver); err != nil {
536 if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
539 if err := idtools.MkdirAllAs(quotas, 0700, rootUID, rootGID); err != nil {
542 if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil {
547 // if we have a remapped root (user namespaces enabled), change the created snapshot
548 // dir ownership to match
549 if rootUID != 0 || rootGID != 0 {
550 if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
557 mountLabel = opts.MountLabel
560 return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
563 // Parse btrfs storage options
564 func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
565 // Read size to change the subvolume disk quota per container
566 for key, val := range storageOpt {
567 key := strings.ToLower(key)
570 size, err := units.RAMInBytes(val)
574 driver.options.size = uint64(size)
576 return fmt.Errorf("Unknown option %s", key)
583 // Set btrfs storage size
584 func (d *Driver) setStorageSize(dir string, driver *Driver) error {
585 if driver.options.size <= 0 {
586 return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
588 if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
589 return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
592 if err := d.subvolEnableQuota(); err != nil {
596 if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
603 // Remove the filesystem with given id.
604 func (d *Driver) Remove(id string) error {
605 dir := d.subvolumesDirID(id)
606 if _, err := os.Stat(dir); err != nil {
609 quotasDir := d.quotasDirID(id)
610 if _, err := os.Stat(quotasDir); err == nil {
611 if err := os.Remove(quotasDir); err != nil {
614 } else if !os.IsNotExist(err) {
618 // Call updateQuotaStatus() to invoke status update
619 d.updateQuotaStatus()
621 if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
624 if err := system.EnsureRemoveAll(dir); err != nil {
627 if err := d.subvolRescanQuota(); err != nil {
633 // Get the requested filesystem id.
634 func (d *Driver) Get(id, mountLabel string) (string, error) {
635 dir := d.subvolumesDirID(id)
636 st, err := os.Stat(dir)
642 return "", fmt.Errorf("%s: not a directory", dir)
645 if quota, err := ioutil.ReadFile(d.quotasDirID(id)); err == nil {
646 if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace {
647 if err := d.subvolEnableQuota(); err != nil {
650 if err := subvolLimitQgroup(dir, size); err != nil {
659 // Put is not implemented for BTRFS as there is no cleanup required for the id.
660 func (d *Driver) Put(id string) error {
661 // Get() creates no runtime resources (like e.g. mounts)
662 // so this doesn't need to do anything.
666 // Exists checks if the id exists in the filesystem.
667 func (d *Driver) Exists(id string) bool {
668 dir := d.subvolumesDirID(id)
669 _, err := os.Stat(dir)