github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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 "os" 20 "path" 21 "path/filepath" 22 "strings" 23 "syscall" 24 "unsafe" 25 26 "github.com/docker/docker/daemon/graphdriver" 27 "github.com/docker/docker/pkg/idtools" 28 "github.com/docker/docker/pkg/mount" 29 "github.com/docker/docker/pkg/parsers" 30 "github.com/docker/go-units" 31 "github.com/opencontainers/runc/libcontainer/label" 32 ) 33 34 func init() { 35 graphdriver.Register("btrfs", Init) 36 } 37 38 type btrfsOptions struct { 39 minSpace uint64 40 size uint64 41 } 42 43 // Init returns a new BTRFS driver. 44 // An error is returned if BTRFS is not supported. 45 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 46 47 fsMagic, err := graphdriver.GetFSMagic(home) 48 if err != nil { 49 return nil, err 50 } 51 52 if fsMagic != graphdriver.FsMagicBtrfs { 53 return nil, graphdriver.ErrPrerequisites 54 } 55 56 rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) 57 if err != nil { 58 return nil, err 59 } 60 if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil { 61 return nil, err 62 } 63 64 if err := mount.MakePrivate(home); err != nil { 65 return nil, err 66 } 67 68 opt, userDiskQuota, err := parseOptions(options) 69 if err != nil { 70 return nil, err 71 } 72 73 driver := &Driver{ 74 home: home, 75 uidMaps: uidMaps, 76 gidMaps: gidMaps, 77 options: opt, 78 } 79 80 if userDiskQuota { 81 if err := driver.subvolEnableQuota(); err != nil { 82 return nil, err 83 } 84 } 85 86 return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil 87 } 88 89 func parseOptions(opt []string) (btrfsOptions, bool, error) { 90 var options btrfsOptions 91 userDiskQuota := false 92 for _, option := range opt { 93 key, val, err := parsers.ParseKeyValueOpt(option) 94 if err != nil { 95 return options, userDiskQuota, err 96 } 97 key = strings.ToLower(key) 98 switch key { 99 case "btrfs.min_space": 100 minSpace, err := units.RAMInBytes(val) 101 if err != nil { 102 return options, userDiskQuota, err 103 } 104 userDiskQuota = true 105 options.minSpace = uint64(minSpace) 106 default: 107 return options, userDiskQuota, fmt.Errorf("Unknown option %s", key) 108 } 109 } 110 return options, userDiskQuota, nil 111 } 112 113 // Driver contains information about the filesystem mounted. 114 type Driver struct { 115 //root of the file system 116 home string 117 uidMaps []idtools.IDMap 118 gidMaps []idtools.IDMap 119 options btrfsOptions 120 quotaEnabled bool 121 } 122 123 // String prints the name of the driver (btrfs). 124 func (d *Driver) String() string { 125 return "btrfs" 126 } 127 128 // Status returns current driver information in a two dimensional string array. 129 // Output contains "Build Version" and "Library Version" of the btrfs libraries used. 130 // Version information can be used to check compatibility with your kernel. 131 func (d *Driver) Status() [][2]string { 132 status := [][2]string{} 133 if bv := btrfsBuildVersion(); bv != "-" { 134 status = append(status, [2]string{"Build Version", bv}) 135 } 136 if lv := btrfsLibVersion(); lv != -1 { 137 status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) 138 } 139 return status 140 } 141 142 // GetMetadata returns empty metadata for this driver. 143 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 144 return nil, nil 145 } 146 147 // Cleanup unmounts the home directory. 148 func (d *Driver) Cleanup() error { 149 if err := d.subvolDisableQuota(); err != nil { 150 return err 151 } 152 153 return mount.Unmount(d.home) 154 } 155 156 func free(p *C.char) { 157 C.free(unsafe.Pointer(p)) 158 } 159 160 func openDir(path string) (*C.DIR, error) { 161 Cpath := C.CString(path) 162 defer free(Cpath) 163 164 dir := C.opendir(Cpath) 165 if dir == nil { 166 return nil, fmt.Errorf("Can't open dir") 167 } 168 return dir, nil 169 } 170 171 func closeDir(dir *C.DIR) { 172 if dir != nil { 173 C.closedir(dir) 174 } 175 } 176 177 func getDirFd(dir *C.DIR) uintptr { 178 return uintptr(C.dirfd(dir)) 179 } 180 181 func subvolCreate(path, name string) error { 182 dir, err := openDir(path) 183 if err != nil { 184 return err 185 } 186 defer closeDir(dir) 187 188 var args C.struct_btrfs_ioctl_vol_args 189 for i, c := range []byte(name) { 190 args.name[i] = C.char(c) 191 } 192 193 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, 194 uintptr(unsafe.Pointer(&args))) 195 if errno != 0 { 196 return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error()) 197 } 198 return nil 199 } 200 201 func subvolSnapshot(src, dest, name string) error { 202 srcDir, err := openDir(src) 203 if err != nil { 204 return err 205 } 206 defer closeDir(srcDir) 207 208 destDir, err := openDir(dest) 209 if err != nil { 210 return err 211 } 212 defer closeDir(destDir) 213 214 var args C.struct_btrfs_ioctl_vol_args_v2 215 args.fd = C.__s64(getDirFd(srcDir)) 216 217 var cs = C.CString(name) 218 C.set_name_btrfs_ioctl_vol_args_v2(&args, cs) 219 C.free(unsafe.Pointer(cs)) 220 221 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, 222 uintptr(unsafe.Pointer(&args))) 223 if errno != 0 { 224 return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error()) 225 } 226 return nil 227 } 228 229 func isSubvolume(p string) (bool, error) { 230 var bufStat syscall.Stat_t 231 if err := syscall.Lstat(p, &bufStat); err != nil { 232 return false, err 233 } 234 235 // return true if it is a btrfs subvolume 236 return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil 237 } 238 239 func subvolDelete(dirpath, name string) error { 240 dir, err := openDir(dirpath) 241 if err != nil { 242 return err 243 } 244 defer closeDir(dir) 245 fullPath := path.Join(dirpath, name) 246 247 var args C.struct_btrfs_ioctl_vol_args 248 249 // walk the btrfs subvolumes 250 walkSubvolumes := func(p string, f os.FileInfo, err error) error { 251 if err != nil { 252 if os.IsNotExist(err) && p != fullPath { 253 // missing most likely because the path was a subvolume that got removed in the previous iteration 254 // since it's gone anyway, we don't care 255 return nil 256 } 257 return fmt.Errorf("error walking subvolumes: %v", err) 258 } 259 // we want to check children only so skip itself 260 // it will be removed after the filepath walk anyways 261 if f.IsDir() && p != fullPath { 262 sv, err := isSubvolume(p) 263 if err != nil { 264 return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err) 265 } 266 if sv { 267 if err := subvolDelete(path.Dir(p), f.Name()); err != nil { 268 return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err) 269 } 270 } 271 } 272 return nil 273 } 274 if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil { 275 return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err) 276 } 277 278 // all subvolumes have been removed 279 // now remove the one originally passed in 280 for i, c := range []byte(name) { 281 args.name[i] = C.char(c) 282 } 283 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, 284 uintptr(unsafe.Pointer(&args))) 285 if errno != 0 { 286 return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error()) 287 } 288 return nil 289 } 290 291 func (d *Driver) subvolEnableQuota() error { 292 if d.quotaEnabled { 293 return nil 294 } 295 // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed 296 if _, err := subvolLookupQgroup(d.home); err == nil { 297 d.quotaEnabled = true 298 return nil 299 } 300 301 dir, err := openDir(d.home) 302 if err != nil { 303 return err 304 } 305 defer closeDir(dir) 306 307 var args C.struct_btrfs_ioctl_quota_ctl_args 308 args.cmd = C.BTRFS_QUOTA_CTL_ENABLE 309 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, 310 uintptr(unsafe.Pointer(&args))) 311 if errno != 0 { 312 return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error()) 313 } 314 315 d.quotaEnabled = true 316 317 return nil 318 } 319 320 func (d *Driver) subvolDisableQuota() error { 321 if !d.quotaEnabled { 322 // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed 323 if _, err := subvolLookupQgroup(d.home); err != nil { 324 // quota is still not enabled 325 return nil 326 } 327 d.quotaEnabled = true 328 } 329 330 dir, err := openDir(d.home) 331 if err != nil { 332 return err 333 } 334 defer closeDir(dir) 335 336 var args C.struct_btrfs_ioctl_quota_ctl_args 337 args.cmd = C.BTRFS_QUOTA_CTL_DISABLE 338 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, 339 uintptr(unsafe.Pointer(&args))) 340 if errno != 0 { 341 return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error()) 342 } 343 344 d.quotaEnabled = false 345 346 return nil 347 } 348 349 func (d *Driver) subvolRescanQuota() error { 350 if !d.quotaEnabled { 351 // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed 352 if _, err := subvolLookupQgroup(d.home); err != nil { 353 // quota is still not enabled 354 return nil 355 } 356 d.quotaEnabled = true 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_rescan_args 366 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT, 367 uintptr(unsafe.Pointer(&args))) 368 if errno != 0 { 369 return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error()) 370 } 371 372 return nil 373 } 374 375 func subvolLimitQgroup(path string, size uint64) error { 376 dir, err := openDir(path) 377 if err != nil { 378 return err 379 } 380 defer closeDir(dir) 381 382 var args C.struct_btrfs_ioctl_qgroup_limit_args 383 args.lim.max_referenced = C.__u64(size) 384 args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER 385 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT, 386 uintptr(unsafe.Pointer(&args))) 387 if errno != 0 { 388 return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error()) 389 } 390 391 return nil 392 } 393 394 func subvolLookupQgroup(path string) (uint64, error) { 395 dir, err := openDir(path) 396 if err != nil { 397 return 0, err 398 } 399 defer closeDir(dir) 400 401 var args C.struct_btrfs_ioctl_ino_lookup_args 402 args.objectid = C.BTRFS_FIRST_FREE_OBJECTID 403 404 _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP, 405 uintptr(unsafe.Pointer(&args))) 406 if errno != 0 { 407 return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error()) 408 } 409 if args.treeid == 0 { 410 return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir) 411 } 412 413 return uint64(args.treeid), nil 414 } 415 416 func (d *Driver) subvolumesDir() string { 417 return path.Join(d.home, "subvolumes") 418 } 419 420 func (d *Driver) subvolumesDirID(id string) string { 421 return path.Join(d.subvolumesDir(), id) 422 } 423 424 // CreateReadWrite creates a layer that is writable for use as a container 425 // file system. 426 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 427 return d.Create(id, parent, opts) 428 } 429 430 // Create the filesystem with given id. 431 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { 432 subvolumes := path.Join(d.home, "subvolumes") 433 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 434 if err != nil { 435 return err 436 } 437 if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil { 438 return err 439 } 440 if parent == "" { 441 if err := subvolCreate(subvolumes, id); err != nil { 442 return err 443 } 444 } else { 445 parentDir := d.subvolumesDirID(parent) 446 st, err := os.Stat(parentDir) 447 if err != nil { 448 return err 449 } 450 if !st.IsDir() { 451 return fmt.Errorf("%s: not a directory", parentDir) 452 } 453 if err := subvolSnapshot(parentDir, subvolumes, id); err != nil { 454 return err 455 } 456 } 457 458 var storageOpt map[string]string 459 if opts != nil { 460 storageOpt = opts.StorageOpt 461 } 462 463 if _, ok := storageOpt["size"]; ok { 464 driver := &Driver{} 465 if err := d.parseStorageOpt(storageOpt, driver); err != nil { 466 return err 467 } 468 if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil { 469 return err 470 } 471 } 472 473 // if we have a remapped root (user namespaces enabled), change the created snapshot 474 // dir ownership to match 475 if rootUID != 0 || rootGID != 0 { 476 if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil { 477 return err 478 } 479 } 480 481 mountLabel := "" 482 if opts != nil { 483 mountLabel = opts.MountLabel 484 } 485 486 return label.Relabel(path.Join(subvolumes, id), mountLabel, false) 487 } 488 489 // Parse btrfs storage options 490 func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error { 491 // Read size to change the subvolume disk quota per container 492 for key, val := range storageOpt { 493 key := strings.ToLower(key) 494 switch key { 495 case "size": 496 size, err := units.RAMInBytes(val) 497 if err != nil { 498 return err 499 } 500 driver.options.size = uint64(size) 501 default: 502 return fmt.Errorf("Unknown option %s", key) 503 } 504 } 505 506 return nil 507 } 508 509 // Set btrfs storage size 510 func (d *Driver) setStorageSize(dir string, driver *Driver) error { 511 if driver.options.size <= 0 { 512 return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size))) 513 } 514 if d.options.minSpace > 0 && driver.options.size < d.options.minSpace { 515 return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace))) 516 } 517 518 if err := d.subvolEnableQuota(); err != nil { 519 return err 520 } 521 522 if err := subvolLimitQgroup(dir, driver.options.size); err != nil { 523 return err 524 } 525 526 return nil 527 } 528 529 // Remove the filesystem with given id. 530 func (d *Driver) Remove(id string) error { 531 dir := d.subvolumesDirID(id) 532 if _, err := os.Stat(dir); err != nil { 533 return err 534 } 535 if err := subvolDelete(d.subvolumesDir(), id); err != nil { 536 return err 537 } 538 if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) { 539 return err 540 } 541 if err := d.subvolRescanQuota(); err != nil { 542 return err 543 } 544 return nil 545 } 546 547 // Get the requested filesystem id. 548 func (d *Driver) Get(id, mountLabel string) (string, error) { 549 dir := d.subvolumesDirID(id) 550 st, err := os.Stat(dir) 551 if err != nil { 552 return "", err 553 } 554 555 if !st.IsDir() { 556 return "", fmt.Errorf("%s: not a directory", dir) 557 } 558 559 return dir, nil 560 } 561 562 // Put is not implemented for BTRFS as there is no cleanup required for the id. 563 func (d *Driver) Put(id string) error { 564 // Get() creates no runtime resources (like e.g. mounts) 565 // so this doesn't need to do anything. 566 return nil 567 } 568 569 // Exists checks if the id exists in the filesystem. 570 func (d *Driver) Exists(id string) bool { 571 dir := d.subvolumesDirID(id) 572 _, err := os.Stat(dir) 573 return err == nil 574 }