github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/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 "syscall" 28 "unsafe" 29 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" 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 fsMagic, err := graphdriver.GetFSMagic(home) 54 if err != nil { 55 return nil, err 56 } 57 58 if fsMagic != graphdriver.FsMagicBtrfs { 59 return nil, graphdriver.ErrPrerequisites 60 } 61 62 rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) 63 if err != nil { 64 return nil, err 65 } 66 if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil { 67 return nil, err 68 } 69 70 if err := mount.MakePrivate(home); err != nil { 71 return nil, err 72 } 73 74 opt, userDiskQuota, err := parseOptions(options) 75 if err != nil { 76 return nil, err 77 } 78 79 driver := &Driver{ 80 home: home, 81 uidMaps: uidMaps, 82 gidMaps: gidMaps, 83 options: opt, 84 } 85 86 if userDiskQuota { 87 if err := driver.subvolEnableQuota(); err != nil { 88 return nil, err 89 } 90 } 91 92 return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil 93 } 94 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) 100 if err != nil { 101 return options, userDiskQuota, err 102 } 103 key = strings.ToLower(key) 104 switch key { 105 case "btrfs.min_space": 106 minSpace, err := units.RAMInBytes(val) 107 if err != nil { 108 return options, userDiskQuota, err 109 } 110 userDiskQuota = true 111 options.minSpace = uint64(minSpace) 112 default: 113 return options, userDiskQuota, fmt.Errorf("Unknown option %s", key) 114 } 115 } 116 return options, userDiskQuota, nil 117 } 118 119 // Driver contains information about the filesystem mounted. 120 type Driver struct { 121 //root of the file system 122 home string 123 uidMaps []idtools.IDMap 124 gidMaps []idtools.IDMap 125 options btrfsOptions 126 quotaEnabled bool 127 once sync.Once 128 } 129 130 // String prints the name of the driver (btrfs). 131 func (d *Driver) String() string { 132 return "btrfs" 133 } 134 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}) 142 } 143 if lv := btrfsLibVersion(); lv != -1 { 144 status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) 145 } 146 return status 147 } 148 149 // GetMetadata returns empty metadata for this driver. 150 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 151 return nil, nil 152 } 153 154 // Cleanup unmounts the home directory. 155 func (d *Driver) Cleanup() error { 156 if err := d.subvolDisableQuota(); err != nil { 157 return err 158 } 159 160 return mount.Unmount(d.home) 161 } 162 163 func free(p *C.char) { 164 C.free(unsafe.Pointer(p)) 165 } 166 167 func openDir(path string) (*C.DIR, error) { 168 Cpath := C.CString(path) 169 defer free(Cpath) 170 171 dir := C.opendir(Cpath) 172 if dir == nil { 173 return nil, fmt.Errorf("Can't open dir") 174 } 175 return dir, nil 176 } 177 178 func closeDir(dir *C.DIR) { 179 if dir != nil { 180 C.closedir(dir) 181 } 182 } 183 184 func getDirFd(dir *C.DIR) uintptr { 185 return uintptr(C.dirfd(dir)) 186 } 187 188 func subvolCreate(path, name string) error { 189 dir, err := openDir(path) 190 if err != nil { 191 return err 192 } 193 defer closeDir(dir) 194 195 var args C.struct_btrfs_ioctl_vol_args 196 for i, c := range []byte(name) { 197 args.name[i] = C.char(c) 198 } 199 200 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, 201 uintptr(unsafe.Pointer(&args))) 202 if errno != 0 { 203 return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error()) 204 } 205 return nil 206 } 207 208 func subvolSnapshot(src, dest, name string) error { 209 srcDir, err := openDir(src) 210 if err != nil { 211 return err 212 } 213 defer closeDir(srcDir) 214 215 destDir, err := openDir(dest) 216 if err != nil { 217 return err 218 } 219 defer closeDir(destDir) 220 221 var args C.struct_btrfs_ioctl_vol_args_v2 222 args.fd = C.__s64(getDirFd(srcDir)) 223 224 var cs = C.CString(name) 225 C.set_name_btrfs_ioctl_vol_args_v2(&args, cs) 226 C.free(unsafe.Pointer(cs)) 227 228 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, 229 uintptr(unsafe.Pointer(&args))) 230 if errno != 0 { 231 return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error()) 232 } 233 return nil 234 } 235 236 func isSubvolume(p string) (bool, error) { 237 var bufStat syscall.Stat_t 238 if err := syscall.Lstat(p, &bufStat); err != nil { 239 return false, err 240 } 241 242 // return true if it is a btrfs subvolume 243 return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil 244 } 245 246 func subvolDelete(dirpath, name string, quotaEnabled bool) error { 247 dir, err := openDir(dirpath) 248 if err != nil { 249 return err 250 } 251 defer closeDir(dir) 252 fullPath := path.Join(dirpath, name) 253 254 var args C.struct_btrfs_ioctl_vol_args 255 256 // walk the btrfs subvolumes 257 walkSubvolumes := func(p string, f os.FileInfo, err error) error { 258 if err != nil { 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 262 return nil 263 } 264 return fmt.Errorf("error walking subvolumes: %v", err) 265 } 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) 270 if err != nil { 271 return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err) 272 } 273 if sv { 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) 276 } 277 } 278 } 279 return nil 280 } 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) 283 } 284 285 if quotaEnabled { 286 if qgroupid, err := subvolLookupQgroup(fullPath); err == nil { 287 var args C.struct_btrfs_ioctl_qgroup_create_args 288 args.qgroupid = C.__u64(qgroupid) 289 290 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE, 291 uintptr(unsafe.Pointer(&args))) 292 if errno != 0 { 293 logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error()) 294 } 295 } else { 296 logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error()) 297 } 298 } 299 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) 304 } 305 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, 306 uintptr(unsafe.Pointer(&args))) 307 if errno != 0 { 308 return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error()) 309 } 310 return nil 311 } 312 313 func (d *Driver) updateQuotaStatus() { 314 d.once.Do(func() { 315 if !d.quotaEnabled { 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 319 return 320 } 321 d.quotaEnabled = true 322 } 323 }) 324 } 325 326 func (d *Driver) subvolEnableQuota() error { 327 d.updateQuotaStatus() 328 329 if d.quotaEnabled { 330 return nil 331 } 332 333 dir, err := openDir(d.home) 334 if err != nil { 335 return err 336 } 337 defer closeDir(dir) 338 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))) 343 if errno != 0 { 344 return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error()) 345 } 346 347 d.quotaEnabled = true 348 349 return nil 350 } 351 352 func (d *Driver) subvolDisableQuota() error { 353 d.updateQuotaStatus() 354 355 if !d.quotaEnabled { 356 return nil 357 } 358 359 dir, err := openDir(d.home) 360 if err != nil { 361 return err 362 } 363 defer closeDir(dir) 364 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))) 369 if errno != 0 { 370 return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error()) 371 } 372 373 d.quotaEnabled = false 374 375 return nil 376 } 377 378 func (d *Driver) subvolRescanQuota() error { 379 d.updateQuotaStatus() 380 381 if !d.quotaEnabled { 382 return nil 383 } 384 385 dir, err := openDir(d.home) 386 if err != nil { 387 return err 388 } 389 defer closeDir(dir) 390 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))) 394 if errno != 0 { 395 return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error()) 396 } 397 398 return nil 399 } 400 401 func subvolLimitQgroup(path string, size uint64) error { 402 dir, err := openDir(path) 403 if err != nil { 404 return err 405 } 406 defer closeDir(dir) 407 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))) 413 if errno != 0 { 414 return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error()) 415 } 416 417 return nil 418 } 419 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) 426 if err != nil { 427 return err 428 } 429 defer closeDir(dir) 430 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 439 440 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH, 441 uintptr(unsafe.Pointer(&args))) 442 if errno != 0 { 443 return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error()) 444 } 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) 448 } 449 return nil 450 } 451 452 func subvolLookupQgroup(path string) (uint64, error) { 453 dir, err := openDir(path) 454 if err != nil { 455 return 0, err 456 } 457 defer closeDir(dir) 458 459 var args C.struct_btrfs_ioctl_ino_lookup_args 460 args.objectid = C.BTRFS_FIRST_FREE_OBJECTID 461 462 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP, 463 uintptr(unsafe.Pointer(&args))) 464 if errno != 0 { 465 return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error()) 466 } 467 if args.treeid == 0 { 468 return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir) 469 } 470 471 return uint64(args.treeid), nil 472 } 473 474 func (d *Driver) subvolumesDir() string { 475 return path.Join(d.home, "subvolumes") 476 } 477 478 func (d *Driver) subvolumesDirID(id string) string { 479 return path.Join(d.subvolumesDir(), id) 480 } 481 482 func (d *Driver) quotasDir() string { 483 return path.Join(d.home, "quotas") 484 } 485 486 func (d *Driver) quotasDirID(id string) string { 487 return path.Join(d.quotasDir(), id) 488 } 489 490 // CreateReadWrite creates a layer that is writable for use as a container 491 // file system. 492 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 493 return d.Create(id, parent, opts) 494 } 495 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) 501 if err != nil { 502 return err 503 } 504 if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil { 505 return err 506 } 507 if parent == "" { 508 if err := subvolCreate(subvolumes, id); err != nil { 509 return err 510 } 511 } else { 512 parentDir := d.subvolumesDirID(parent) 513 st, err := os.Stat(parentDir) 514 if err != nil { 515 return err 516 } 517 if !st.IsDir() { 518 return fmt.Errorf("%s: not a directory", parentDir) 519 } 520 if err := subvolSnapshot(parentDir, subvolumes, id); err != nil { 521 return err 522 } 523 } 524 525 var storageOpt map[string]string 526 if opts != nil { 527 storageOpt = opts.StorageOpt 528 } 529 530 if _, ok := storageOpt["size"]; ok { 531 driver := &Driver{} 532 if err := d.parseStorageOpt(storageOpt, driver); err != nil { 533 return err 534 } 535 536 if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil { 537 return err 538 } 539 if err := idtools.MkdirAllAs(quotas, 0700, rootUID, rootGID); err != nil { 540 return err 541 } 542 if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil { 543 return err 544 } 545 } 546 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 { 551 return err 552 } 553 } 554 555 mountLabel := "" 556 if opts != nil { 557 mountLabel = opts.MountLabel 558 } 559 560 return label.Relabel(path.Join(subvolumes, id), mountLabel, false) 561 } 562 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) 568 switch key { 569 case "size": 570 size, err := units.RAMInBytes(val) 571 if err != nil { 572 return err 573 } 574 driver.options.size = uint64(size) 575 default: 576 return fmt.Errorf("Unknown option %s", key) 577 } 578 } 579 580 return nil 581 } 582 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))) 587 } 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))) 590 } 591 592 if err := d.subvolEnableQuota(); err != nil { 593 return err 594 } 595 596 if err := subvolLimitQgroup(dir, driver.options.size); err != nil { 597 return err 598 } 599 600 return nil 601 } 602 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 { 607 return err 608 } 609 quotasDir := d.quotasDirID(id) 610 if _, err := os.Stat(quotasDir); err == nil { 611 if err := os.Remove(quotasDir); err != nil { 612 return err 613 } 614 } else if !os.IsNotExist(err) { 615 return err 616 } 617 618 // Call updateQuotaStatus() to invoke status update 619 d.updateQuotaStatus() 620 621 if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil { 622 return err 623 } 624 if err := system.EnsureRemoveAll(dir); err != nil { 625 return err 626 } 627 if err := d.subvolRescanQuota(); err != nil { 628 return err 629 } 630 return nil 631 } 632 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) 637 if err != nil { 638 return "", err 639 } 640 641 if !st.IsDir() { 642 return "", fmt.Errorf("%s: not a directory", dir) 643 } 644 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 { 648 return "", err 649 } 650 if err := subvolLimitQgroup(dir, size); err != nil { 651 return "", err 652 } 653 } 654 } 655 656 return dir, nil 657 } 658 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. 663 return nil 664 } 665 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) 670 return err == nil 671 }