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