github.com/afbjorklund/moby@v20.10.5+incompatible/daemon/graphdriver/btrfs/btrfs.go (about) 1 // +build linux 2 3 package btrfs // import "github.com/docker/docker/daemon/graphdriver/btrfs" 4 5 /* 6 #include <stdlib.h> 7 #include <dirent.h> 8 #include <btrfs/ioctl.h> 9 #include <btrfs/ctree.h> 10 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); 13 } 14 */ 15 import "C" 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "math" 21 "os" 22 "path" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "sync" 27 "unsafe" 28 29 "github.com/docker/docker/daemon/graphdriver" 30 "github.com/docker/docker/pkg/containerfs" 31 "github.com/docker/docker/pkg/idtools" 32 "github.com/docker/docker/pkg/parsers" 33 "github.com/docker/docker/pkg/system" 34 units "github.com/docker/go-units" 35 "github.com/moby/sys/mount" 36 "github.com/opencontainers/selinux/go-selinux/label" 37 "github.com/pkg/errors" 38 "github.com/sirupsen/logrus" 39 "golang.org/x/sys/unix" 40 ) 41 42 func init() { 43 graphdriver.Register("btrfs", Init) 44 } 45 46 type btrfsOptions struct { 47 minSpace uint64 48 size uint64 49 } 50 51 // Init returns a new BTRFS driver. 52 // An error is returned if BTRFS is not supported. 53 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 54 55 // Perform feature detection on /var/lib/docker/btrfs if it's an existing directory. 56 // This covers situations where /var/lib/docker/btrfs is a mount, and on a different 57 // filesystem than /var/lib/docker. 58 // If the path does not exist, fall back to using /var/lib/docker for feature detection. 59 testdir := home 60 if _, err := os.Stat(testdir); os.IsNotExist(err) { 61 testdir = filepath.Dir(testdir) 62 } 63 64 fsMagic, err := graphdriver.GetFSMagic(testdir) 65 if err != nil { 66 return nil, err 67 } 68 69 if fsMagic != graphdriver.FsMagicBtrfs { 70 return nil, graphdriver.ErrPrerequisites 71 } 72 73 if err := idtools.MkdirAllAndChown(home, 0701, idtools.CurrentIdentity()); err != nil { 74 return nil, err 75 } 76 77 opt, userDiskQuota, err := parseOptions(options) 78 if err != nil { 79 return nil, err 80 } 81 82 // For some reason shared mount propagation between a container 83 // and the host does not work for btrfs, and a remedy is to bind 84 // mount graphdriver home to itself (even without changing the 85 // propagation mode). 86 err = mount.MakeMount(home) 87 if err != nil { 88 return nil, errors.Wrapf(err, "failed to make %s a mount", home) 89 } 90 91 driver := &Driver{ 92 home: home, 93 uidMaps: uidMaps, 94 gidMaps: gidMaps, 95 options: opt, 96 } 97 98 if userDiskQuota { 99 if err := driver.subvolEnableQuota(); err != nil { 100 return nil, err 101 } 102 } 103 104 return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil 105 } 106 107 func parseOptions(opt []string) (btrfsOptions, bool, error) { 108 var options btrfsOptions 109 userDiskQuota := false 110 for _, option := range opt { 111 key, val, err := parsers.ParseKeyValueOpt(option) 112 if err != nil { 113 return options, userDiskQuota, err 114 } 115 key = strings.ToLower(key) 116 switch key { 117 case "btrfs.min_space": 118 minSpace, err := units.RAMInBytes(val) 119 if err != nil { 120 return options, userDiskQuota, err 121 } 122 userDiskQuota = true 123 options.minSpace = uint64(minSpace) 124 default: 125 return options, userDiskQuota, fmt.Errorf("Unknown option %s", key) 126 } 127 } 128 return options, userDiskQuota, nil 129 } 130 131 // Driver contains information about the filesystem mounted. 132 type Driver struct { 133 // root of the file system 134 home string 135 uidMaps []idtools.IDMap 136 gidMaps []idtools.IDMap 137 options btrfsOptions 138 quotaEnabled bool 139 once sync.Once 140 } 141 142 // String prints the name of the driver (btrfs). 143 func (d *Driver) String() string { 144 return "btrfs" 145 } 146 147 // Status returns current driver information in a two dimensional string array. 148 // Output contains "Build Version" and "Library Version" of the btrfs libraries used. 149 // Version information can be used to check compatibility with your kernel. 150 func (d *Driver) Status() [][2]string { 151 status := [][2]string{} 152 if bv := btrfsBuildVersion(); bv != "-" { 153 status = append(status, [2]string{"Build Version", bv}) 154 } 155 if lv := btrfsLibVersion(); lv != -1 { 156 status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) 157 } 158 return status 159 } 160 161 // GetMetadata returns empty metadata for this driver. 162 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 163 return nil, nil 164 } 165 166 // Cleanup unmounts the home directory. 167 func (d *Driver) Cleanup() error { 168 err := d.subvolDisableQuota() 169 umountErr := mount.Unmount(d.home) 170 171 // in case we have two errors, prefer the one from disableQuota() 172 if err != nil { 173 return err 174 } 175 176 if umountErr != nil { 177 return umountErr 178 } 179 180 return nil 181 } 182 183 func free(p *C.char) { 184 C.free(unsafe.Pointer(p)) 185 } 186 187 func openDir(path string) (*C.DIR, error) { 188 Cpath := C.CString(path) 189 defer free(Cpath) 190 191 dir := C.opendir(Cpath) 192 if dir == nil { 193 return nil, fmt.Errorf("Can't open dir") 194 } 195 return dir, nil 196 } 197 198 func closeDir(dir *C.DIR) { 199 if dir != nil { 200 C.closedir(dir) 201 } 202 } 203 204 func getDirFd(dir *C.DIR) uintptr { 205 return uintptr(C.dirfd(dir)) 206 } 207 208 func subvolCreate(path, name string) error { 209 dir, err := openDir(path) 210 if err != nil { 211 return err 212 } 213 defer closeDir(dir) 214 215 var args C.struct_btrfs_ioctl_vol_args 216 for i, c := range []byte(name) { 217 args.name[i] = C.char(c) 218 } 219 220 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, 221 uintptr(unsafe.Pointer(&args))) 222 if errno != 0 { 223 return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error()) 224 } 225 return nil 226 } 227 228 func subvolSnapshot(src, dest, name string) error { 229 srcDir, err := openDir(src) 230 if err != nil { 231 return err 232 } 233 defer closeDir(srcDir) 234 235 destDir, err := openDir(dest) 236 if err != nil { 237 return err 238 } 239 defer closeDir(destDir) 240 241 var args C.struct_btrfs_ioctl_vol_args_v2 242 args.fd = C.__s64(getDirFd(srcDir)) 243 244 var cs = C.CString(name) 245 C.set_name_btrfs_ioctl_vol_args_v2(&args, cs) 246 C.free(unsafe.Pointer(cs)) 247 248 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, 249 uintptr(unsafe.Pointer(&args))) 250 if errno != 0 { 251 return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error()) 252 } 253 return nil 254 } 255 256 func isSubvolume(p string) (bool, error) { 257 var bufStat unix.Stat_t 258 if err := unix.Lstat(p, &bufStat); err != nil { 259 return false, err 260 } 261 262 // return true if it is a btrfs subvolume 263 return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil 264 } 265 266 func subvolDelete(dirpath, name string, quotaEnabled bool) error { 267 dir, err := openDir(dirpath) 268 if err != nil { 269 return err 270 } 271 defer closeDir(dir) 272 fullPath := path.Join(dirpath, name) 273 274 var args C.struct_btrfs_ioctl_vol_args 275 276 // walk the btrfs subvolumes 277 walkSubvolumes := func(p string, f os.FileInfo, err error) error { 278 if err != nil { 279 if os.IsNotExist(err) && p != fullPath { 280 // missing most likely because the path was a subvolume that got removed in the previous iteration 281 // since it's gone anyway, we don't care 282 return nil 283 } 284 return fmt.Errorf("error walking subvolumes: %v", err) 285 } 286 // we want to check children only so skip itself 287 // it will be removed after the filepath walk anyways 288 if f.IsDir() && p != fullPath { 289 sv, err := isSubvolume(p) 290 if err != nil { 291 return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err) 292 } 293 if sv { 294 if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil { 295 return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err) 296 } 297 } 298 } 299 return nil 300 } 301 if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil { 302 return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err) 303 } 304 305 if quotaEnabled { 306 if qgroupid, err := subvolLookupQgroup(fullPath); err == nil { 307 var args C.struct_btrfs_ioctl_qgroup_create_args 308 args.qgroupid = C.__u64(qgroupid) 309 310 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE, 311 uintptr(unsafe.Pointer(&args))) 312 if errno != 0 { 313 logrus.WithField("storage-driver", "btrfs").Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error()) 314 } 315 } else { 316 logrus.WithField("storage-driver", "btrfs").Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error()) 317 } 318 } 319 320 // all subvolumes have been removed 321 // now remove the one originally passed in 322 for i, c := range []byte(name) { 323 args.name[i] = C.char(c) 324 } 325 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, 326 uintptr(unsafe.Pointer(&args))) 327 if errno != 0 { 328 return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error()) 329 } 330 return nil 331 } 332 333 func (d *Driver) updateQuotaStatus() { 334 d.once.Do(func() { 335 if !d.quotaEnabled { 336 // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed 337 if err := subvolQgroupStatus(d.home); err != nil { 338 // quota is still not enabled 339 return 340 } 341 d.quotaEnabled = true 342 } 343 }) 344 } 345 346 func (d *Driver) subvolEnableQuota() error { 347 d.updateQuotaStatus() 348 349 if d.quotaEnabled { 350 return nil 351 } 352 353 dir, err := openDir(d.home) 354 if err != nil { 355 return err 356 } 357 defer closeDir(dir) 358 359 var args C.struct_btrfs_ioctl_quota_ctl_args 360 args.cmd = C.BTRFS_QUOTA_CTL_ENABLE 361 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, 362 uintptr(unsafe.Pointer(&args))) 363 if errno != 0 { 364 return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error()) 365 } 366 367 d.quotaEnabled = true 368 369 return nil 370 } 371 372 func (d *Driver) subvolDisableQuota() error { 373 d.updateQuotaStatus() 374 375 if !d.quotaEnabled { 376 return nil 377 } 378 379 dir, err := openDir(d.home) 380 if err != nil { 381 return err 382 } 383 defer closeDir(dir) 384 385 var args C.struct_btrfs_ioctl_quota_ctl_args 386 args.cmd = C.BTRFS_QUOTA_CTL_DISABLE 387 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, 388 uintptr(unsafe.Pointer(&args))) 389 if errno != 0 { 390 return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error()) 391 } 392 393 d.quotaEnabled = false 394 395 return nil 396 } 397 398 func (d *Driver) subvolRescanQuota() error { 399 d.updateQuotaStatus() 400 401 if !d.quotaEnabled { 402 return nil 403 } 404 405 dir, err := openDir(d.home) 406 if err != nil { 407 return err 408 } 409 defer closeDir(dir) 410 411 var args C.struct_btrfs_ioctl_quota_rescan_args 412 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT, 413 uintptr(unsafe.Pointer(&args))) 414 if errno != 0 { 415 return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error()) 416 } 417 418 return nil 419 } 420 421 func subvolLimitQgroup(path string, size uint64) error { 422 dir, err := openDir(path) 423 if err != nil { 424 return err 425 } 426 defer closeDir(dir) 427 428 var args C.struct_btrfs_ioctl_qgroup_limit_args 429 args.lim.max_referenced = C.__u64(size) 430 args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER 431 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT, 432 uintptr(unsafe.Pointer(&args))) 433 if errno != 0 { 434 return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error()) 435 } 436 437 return nil 438 } 439 440 // subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path 441 // with search key of BTRFS_QGROUP_STATUS_KEY. 442 // In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY. 443 // For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035 444 func subvolQgroupStatus(path string) error { 445 dir, err := openDir(path) 446 if err != nil { 447 return err 448 } 449 defer closeDir(dir) 450 451 var args C.struct_btrfs_ioctl_search_args 452 args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID 453 args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY 454 args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY 455 args.key.max_objectid = C.__u64(math.MaxUint64) 456 args.key.max_offset = C.__u64(math.MaxUint64) 457 args.key.max_transid = C.__u64(math.MaxUint64) 458 args.key.nr_items = 4096 459 460 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH, 461 uintptr(unsafe.Pointer(&args))) 462 if errno != 0 { 463 return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error()) 464 } 465 sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf)) 466 if sh._type != C.BTRFS_QGROUP_STATUS_KEY { 467 return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type) 468 } 469 return nil 470 } 471 472 func subvolLookupQgroup(path string) (uint64, error) { 473 dir, err := openDir(path) 474 if err != nil { 475 return 0, err 476 } 477 defer closeDir(dir) 478 479 var args C.struct_btrfs_ioctl_ino_lookup_args 480 args.objectid = C.BTRFS_FIRST_FREE_OBJECTID 481 482 _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP, 483 uintptr(unsafe.Pointer(&args))) 484 if errno != 0 { 485 return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error()) 486 } 487 if args.treeid == 0 { 488 return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir) 489 } 490 491 return uint64(args.treeid), nil 492 } 493 494 func (d *Driver) subvolumesDir() string { 495 return path.Join(d.home, "subvolumes") 496 } 497 498 func (d *Driver) subvolumesDirID(id string) string { 499 return path.Join(d.subvolumesDir(), id) 500 } 501 502 func (d *Driver) quotasDir() string { 503 return path.Join(d.home, "quotas") 504 } 505 506 func (d *Driver) quotasDirID(id string) string { 507 return path.Join(d.quotasDir(), id) 508 } 509 510 // CreateReadWrite creates a layer that is writable for use as a container 511 // file system. 512 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 513 return d.Create(id, parent, opts) 514 } 515 516 // Create the filesystem with given id. 517 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { 518 quotas := path.Join(d.home, "quotas") 519 subvolumes := path.Join(d.home, "subvolumes") 520 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 521 if err != nil { 522 return err 523 } 524 if err := idtools.MkdirAllAndChown(subvolumes, 0701, idtools.CurrentIdentity()); err != nil { 525 return err 526 } 527 if parent == "" { 528 if err := subvolCreate(subvolumes, id); err != nil { 529 return err 530 } 531 } else { 532 parentDir := d.subvolumesDirID(parent) 533 st, err := os.Stat(parentDir) 534 if err != nil { 535 return err 536 } 537 if !st.IsDir() { 538 return fmt.Errorf("%s: not a directory", parentDir) 539 } 540 if err := subvolSnapshot(parentDir, subvolumes, id); err != nil { 541 return err 542 } 543 } 544 545 var storageOpt map[string]string 546 if opts != nil { 547 storageOpt = opts.StorageOpt 548 } 549 550 if _, ok := storageOpt["size"]; ok { 551 driver := &Driver{} 552 if err := d.parseStorageOpt(storageOpt, driver); err != nil { 553 return err 554 } 555 556 if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil { 557 return err 558 } 559 if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.CurrentIdentity()); err != nil { 560 return err 561 } 562 if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil { 563 return err 564 } 565 } 566 567 // if we have a remapped root (user namespaces enabled), change the created snapshot 568 // dir ownership to match 569 if rootUID != 0 || rootGID != 0 { 570 if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil { 571 return err 572 } 573 } 574 575 mountLabel := "" 576 if opts != nil { 577 mountLabel = opts.MountLabel 578 } 579 580 return label.Relabel(path.Join(subvolumes, id), mountLabel, false) 581 } 582 583 // Parse btrfs storage options 584 func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error { 585 // Read size to change the subvolume disk quota per container 586 for key, val := range storageOpt { 587 key := strings.ToLower(key) 588 switch key { 589 case "size": 590 size, err := units.RAMInBytes(val) 591 if err != nil { 592 return err 593 } 594 driver.options.size = uint64(size) 595 default: 596 return fmt.Errorf("Unknown option %s", key) 597 } 598 } 599 600 return nil 601 } 602 603 // Set btrfs storage size 604 func (d *Driver) setStorageSize(dir string, driver *Driver) error { 605 if driver.options.size == 0 { 606 return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size))) 607 } 608 if d.options.minSpace > 0 && driver.options.size < d.options.minSpace { 609 return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace))) 610 } 611 if err := d.subvolEnableQuota(); err != nil { 612 return err 613 } 614 return subvolLimitQgroup(dir, driver.options.size) 615 } 616 617 // Remove the filesystem with given id. 618 func (d *Driver) Remove(id string) error { 619 dir := d.subvolumesDirID(id) 620 if _, err := os.Stat(dir); err != nil { 621 return err 622 } 623 quotasDir := d.quotasDirID(id) 624 if _, err := os.Stat(quotasDir); err == nil { 625 if err := os.Remove(quotasDir); err != nil { 626 return err 627 } 628 } else if !os.IsNotExist(err) { 629 return err 630 } 631 632 // Call updateQuotaStatus() to invoke status update 633 d.updateQuotaStatus() 634 635 if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil { 636 return err 637 } 638 if err := system.EnsureRemoveAll(dir); err != nil { 639 return err 640 } 641 return d.subvolRescanQuota() 642 } 643 644 // Get the requested filesystem id. 645 func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { 646 dir := d.subvolumesDirID(id) 647 st, err := os.Stat(dir) 648 if err != nil { 649 return nil, err 650 } 651 652 if !st.IsDir() { 653 return nil, fmt.Errorf("%s: not a directory", dir) 654 } 655 656 if quota, err := ioutil.ReadFile(d.quotasDirID(id)); err == nil { 657 if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace { 658 if err := d.subvolEnableQuota(); err != nil { 659 return nil, err 660 } 661 if err := subvolLimitQgroup(dir, size); err != nil { 662 return nil, err 663 } 664 } 665 } 666 667 return containerfs.NewLocalContainerFS(dir), nil 668 } 669 670 // Put is not implemented for BTRFS as there is no cleanup required for the id. 671 func (d *Driver) Put(id string) error { 672 // Get() creates no runtime resources (like e.g. mounts) 673 // so this doesn't need to do anything. 674 return nil 675 } 676 677 // Exists checks if the id exists in the filesystem. 678 func (d *Driver) Exists(id string) bool { 679 dir := d.subvolumesDirID(id) 680 _, err := os.Stat(dir) 681 return err == nil 682 }