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