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