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