github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/daemon/graphdriver/aufs/aufs.go (about) 1 // +build linux 2 3 /* 4 5 aufs driver directory structure 6 7 . 8 ├── layers // Metadata of layers 9 │ ├── 1 10 │ ├── 2 11 │ └── 3 12 ├── diff // Content of the layer 13 │ ├── 1 // Contains layers that need to be mounted for the id 14 │ ├── 2 15 │ └── 3 16 └── mnt // Mount points for the rw layers to be mounted 17 ├── 1 18 ├── 2 19 └── 3 20 21 */ 22 23 package aufs // import "github.com/docker/docker/daemon/graphdriver/aufs" 24 25 import ( 26 "bufio" 27 "context" 28 "fmt" 29 "io" 30 "io/ioutil" 31 "os" 32 "os/exec" 33 "path" 34 "path/filepath" 35 "strings" 36 "sync" 37 38 "github.com/containerd/containerd/pkg/userns" 39 "github.com/docker/docker/daemon/graphdriver" 40 "github.com/docker/docker/pkg/archive" 41 "github.com/docker/docker/pkg/chrootarchive" 42 "github.com/docker/docker/pkg/containerfs" 43 "github.com/docker/docker/pkg/directory" 44 "github.com/docker/docker/pkg/idtools" 45 "github.com/docker/docker/pkg/system" 46 "github.com/moby/locker" 47 "github.com/moby/sys/mount" 48 "github.com/opencontainers/selinux/go-selinux/label" 49 "github.com/pkg/errors" 50 "github.com/sirupsen/logrus" 51 "github.com/vbatts/tar-split/tar/storage" 52 "golang.org/x/sys/unix" 53 ) 54 55 var ( 56 // ErrAufsNotSupported is returned if aufs is not supported by the host. 57 ErrAufsNotSupported = fmt.Errorf("AUFS was not found in /proc/filesystems") 58 // ErrAufsNested means aufs cannot be used bc we are in a user namespace 59 ErrAufsNested = fmt.Errorf("AUFS cannot be used in non-init user namespace") 60 backingFs = "<unknown>" 61 62 enableDirpermLock sync.Once 63 enableDirperm bool 64 65 logger = logrus.WithField("storage-driver", "aufs") 66 ) 67 68 func init() { 69 graphdriver.Register("aufs", Init) 70 } 71 72 // Driver contains information about the filesystem mounted. 73 type Driver struct { 74 root string 75 uidMaps []idtools.IDMap 76 gidMaps []idtools.IDMap 77 ctr *graphdriver.RefCounter 78 pathCacheLock sync.Mutex 79 pathCache map[string]string 80 naiveDiff graphdriver.DiffDriver 81 locker *locker.Locker 82 mntL sync.Mutex 83 } 84 85 // Init returns a new AUFS driver. 86 // An error is returned if AUFS is not supported. 87 func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 88 // Try to load the aufs kernel module 89 if err := supportsAufs(); err != nil { 90 logger.Error(err) 91 return nil, graphdriver.ErrNotSupported 92 } 93 94 // Perform feature detection on /var/lib/docker/aufs if it's an existing directory. 95 // This covers situations where /var/lib/docker/aufs is a mount, and on a different 96 // filesystem than /var/lib/docker. 97 // If the path does not exist, fall back to using /var/lib/docker for feature detection. 98 testdir := root 99 if _, err := os.Stat(testdir); os.IsNotExist(err) { 100 testdir = filepath.Dir(testdir) 101 } 102 103 fsMagic, err := graphdriver.GetFSMagic(testdir) 104 if err != nil { 105 return nil, err 106 } 107 if fsName, ok := graphdriver.FsNames[fsMagic]; ok { 108 backingFs = fsName 109 } 110 111 switch fsMagic { 112 case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs: 113 logger.Errorf("AUFS is not supported over %s", backingFs) 114 return nil, graphdriver.ErrIncompatibleFS 115 } 116 117 paths := []string{ 118 "mnt", 119 "diff", 120 "layers", 121 } 122 123 a := &Driver{ 124 root: root, 125 uidMaps: uidMaps, 126 gidMaps: gidMaps, 127 pathCache: make(map[string]string), 128 ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicAufs)), 129 locker: locker.New(), 130 } 131 132 currentID := idtools.CurrentIdentity() 133 // Create the root aufs driver dir 134 if err := idtools.MkdirAllAndChown(root, 0701, currentID); err != nil { 135 return nil, err 136 } 137 138 // Populate the dir structure 139 for _, p := range paths { 140 if err := idtools.MkdirAllAndChown(path.Join(root, p), 0701, currentID); err != nil { 141 return nil, err 142 } 143 } 144 145 for _, path := range []string{"mnt", "diff"} { 146 p := filepath.Join(root, path) 147 entries, err := ioutil.ReadDir(p) 148 if err != nil { 149 logger.WithError(err).WithField("dir", p).Error("error reading dir entries") 150 continue 151 } 152 for _, entry := range entries { 153 if !entry.IsDir() { 154 continue 155 } 156 if strings.HasSuffix(entry.Name(), "-removing") { 157 logger.WithField("dir", entry.Name()).Debug("Cleaning up stale layer dir") 158 if err := system.EnsureRemoveAll(filepath.Join(p, entry.Name())); err != nil { 159 logger.WithField("dir", entry.Name()).WithError(err).Error("Error removing stale layer dir") 160 } 161 } 162 } 163 } 164 165 a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, uidMaps, gidMaps) 166 return a, nil 167 } 168 169 // Return a nil error if the kernel supports aufs 170 // We cannot modprobe because inside dind modprobe fails 171 // to run 172 func supportsAufs() error { 173 // We can try to modprobe aufs first before looking at 174 // proc/filesystems for when aufs is supported 175 exec.Command("modprobe", "aufs").Run() 176 177 if userns.RunningInUserNS() { 178 return ErrAufsNested 179 } 180 181 f, err := os.Open("/proc/filesystems") 182 if err != nil { 183 return err 184 } 185 defer f.Close() 186 187 s := bufio.NewScanner(f) 188 for s.Scan() { 189 if strings.Contains(s.Text(), "aufs") { 190 return nil 191 } 192 } 193 return ErrAufsNotSupported 194 } 195 196 func (a *Driver) rootPath() string { 197 return a.root 198 } 199 200 func (*Driver) String() string { 201 return "aufs" 202 } 203 204 // Status returns current information about the filesystem such as root directory, number of directories mounted, etc. 205 func (a *Driver) Status() [][2]string { 206 ids, _ := loadIds(path.Join(a.rootPath(), "layers")) 207 return [][2]string{ 208 {"Root Dir", a.rootPath()}, 209 {"Backing Filesystem", backingFs}, 210 {"Dirs", fmt.Sprintf("%d", len(ids))}, 211 {"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())}, 212 } 213 } 214 215 // GetMetadata not implemented 216 func (a *Driver) GetMetadata(id string) (map[string]string, error) { 217 return nil, nil 218 } 219 220 // Exists returns true if the given id is registered with 221 // this driver 222 func (a *Driver) Exists(id string) bool { 223 if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil { 224 return false 225 } 226 return true 227 } 228 229 // CreateReadWrite creates a layer that is writable for use as a container 230 // file system. 231 func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 232 return a.Create(id, parent, opts) 233 } 234 235 // Create three folders for each id 236 // mnt, layers, and diff 237 func (a *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { 238 239 if opts != nil && len(opts.StorageOpt) != 0 { 240 return fmt.Errorf("--storage-opt is not supported for aufs") 241 } 242 243 if err := a.createDirsFor(id); err != nil { 244 return err 245 } 246 // Write the layers metadata 247 f, err := os.Create(path.Join(a.rootPath(), "layers", id)) 248 if err != nil { 249 return err 250 } 251 defer f.Close() 252 253 if parent != "" { 254 ids, err := getParentIDs(a.rootPath(), parent) 255 if err != nil { 256 return err 257 } 258 259 if _, err := fmt.Fprintln(f, parent); err != nil { 260 return err 261 } 262 for _, i := range ids { 263 if _, err := fmt.Fprintln(f, i); err != nil { 264 return err 265 } 266 } 267 } 268 269 return nil 270 } 271 272 // createDirsFor creates two directories for the given id. 273 // mnt and diff 274 func (a *Driver) createDirsFor(id string) error { 275 paths := []string{ 276 "mnt", 277 "diff", 278 } 279 280 rootUID, rootGID, err := idtools.GetRootUIDGID(a.uidMaps, a.gidMaps) 281 if err != nil { 282 return err 283 } 284 // Directory permission is 0755. 285 // The path of directories are <aufs_root_path>/mnt/<image_id> 286 // and <aufs_root_path>/diff/<image_id> 287 for _, p := range paths { 288 if err := idtools.MkdirAllAndChown(path.Join(a.rootPath(), p, id), 0755, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { 289 return err 290 } 291 } 292 return nil 293 } 294 295 // Remove will unmount and remove the given id. 296 func (a *Driver) Remove(id string) error { 297 a.locker.Lock(id) 298 defer a.locker.Unlock(id) 299 a.pathCacheLock.Lock() 300 mountpoint, exists := a.pathCache[id] 301 a.pathCacheLock.Unlock() 302 if !exists { 303 mountpoint = a.getMountpoint(id) 304 } 305 306 if err := a.unmount(mountpoint); err != nil { 307 logger.WithError(err).WithField("method", "Remove()").Warn() 308 return err 309 } 310 311 // Remove the layers file for the id 312 if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) { 313 return errors.Wrapf(err, "error removing layers dir for %s", id) 314 } 315 316 if err := atomicRemove(a.getDiffPath(id)); err != nil { 317 return errors.Wrapf(err, "could not remove diff path for id %s", id) 318 } 319 320 // Atomically remove each directory in turn by first moving it out of the 321 // way (so that docker doesn't find it anymore) before doing removal of 322 // the whole tree. 323 if err := atomicRemove(mountpoint); err != nil { 324 if errors.Is(err, unix.EBUSY) { 325 logger.WithField("dir", mountpoint).WithError(err).Warn("error performing atomic remove due to EBUSY") 326 } 327 return errors.Wrapf(err, "could not remove mountpoint for id %s", id) 328 } 329 330 a.pathCacheLock.Lock() 331 delete(a.pathCache, id) 332 a.pathCacheLock.Unlock() 333 return nil 334 } 335 336 func atomicRemove(source string) error { 337 target := source + "-removing" 338 339 err := os.Rename(source, target) 340 switch { 341 case err == nil, os.IsNotExist(err): 342 case os.IsExist(err): 343 // Got error saying the target dir already exists, maybe the source doesn't exist due to a previous (failed) remove 344 if _, e := os.Stat(source); !os.IsNotExist(e) { 345 return errors.Wrapf(err, "target rename dir %q exists but should not, this needs to be manually cleaned up", target) 346 } 347 default: 348 return errors.Wrapf(err, "error preparing atomic delete") 349 } 350 351 return system.EnsureRemoveAll(target) 352 } 353 354 // Get returns the rootfs path for the id. 355 // This will mount the dir at its given path 356 func (a *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { 357 a.locker.Lock(id) 358 defer a.locker.Unlock(id) 359 parents, err := a.getParentLayerPaths(id) 360 if err != nil && !os.IsNotExist(err) { 361 return nil, err 362 } 363 364 a.pathCacheLock.Lock() 365 m, exists := a.pathCache[id] 366 a.pathCacheLock.Unlock() 367 368 if !exists { 369 m = a.getDiffPath(id) 370 if len(parents) > 0 { 371 m = a.getMountpoint(id) 372 } 373 } 374 if count := a.ctr.Increment(m); count > 1 { 375 return containerfs.NewLocalContainerFS(m), nil 376 } 377 378 // If a dir does not have a parent ( no layers )do not try to mount 379 // just return the diff path to the data 380 if len(parents) > 0 { 381 if err := a.mount(id, m, mountLabel, parents); err != nil { 382 return nil, err 383 } 384 } 385 386 a.pathCacheLock.Lock() 387 a.pathCache[id] = m 388 a.pathCacheLock.Unlock() 389 return containerfs.NewLocalContainerFS(m), nil 390 } 391 392 // Put unmounts and updates list of active mounts. 393 func (a *Driver) Put(id string) error { 394 a.locker.Lock(id) 395 defer a.locker.Unlock(id) 396 a.pathCacheLock.Lock() 397 m, exists := a.pathCache[id] 398 if !exists { 399 m = a.getMountpoint(id) 400 a.pathCache[id] = m 401 } 402 a.pathCacheLock.Unlock() 403 if count := a.ctr.Decrement(m); count > 0 { 404 return nil 405 } 406 407 err := a.unmount(m) 408 if err != nil { 409 logger.WithError(err).WithField("method", "Put()").Warn() 410 } 411 return err 412 } 413 414 // isParent returns if the passed in parent is the direct parent of the passed in layer 415 func (a *Driver) isParent(id, parent string) bool { 416 parents, _ := getParentIDs(a.rootPath(), id) 417 if parent == "" && len(parents) > 0 { 418 return false 419 } 420 return !(len(parents) > 0 && parent != parents[0]) 421 } 422 423 // Diff produces an archive of the changes between the specified 424 // layer and its parent layer which may be "". 425 func (a *Driver) Diff(id, parent string) (io.ReadCloser, error) { 426 if !a.isParent(id, parent) { 427 return a.naiveDiff.Diff(id, parent) 428 } 429 430 // AUFS doesn't need the parent layer to produce a diff. 431 return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ 432 Compression: archive.Uncompressed, 433 ExcludePatterns: []string{archive.WhiteoutMetaPrefix + "*", "!" + archive.WhiteoutOpaqueDir}, 434 UIDMaps: a.uidMaps, 435 GIDMaps: a.gidMaps, 436 }) 437 } 438 439 type fileGetNilCloser struct { 440 storage.FileGetter 441 } 442 443 func (f fileGetNilCloser) Close() error { 444 return nil 445 } 446 447 // DiffGetter returns a FileGetCloser that can read files from the directory that 448 // contains files for the layer differences. Used for direct access for tar-split. 449 func (a *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) { 450 p := path.Join(a.rootPath(), "diff", id) 451 return fileGetNilCloser{storage.NewPathFileGetter(p)}, nil 452 } 453 454 func (a *Driver) applyDiff(id string, diff io.Reader) error { 455 return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ 456 UIDMaps: a.uidMaps, 457 GIDMaps: a.gidMaps, 458 }) 459 } 460 461 // DiffSize calculates the changes between the specified id 462 // and its parent and returns the size in bytes of the changes 463 // relative to its base filesystem directory. 464 func (a *Driver) DiffSize(id, parent string) (size int64, err error) { 465 if !a.isParent(id, parent) { 466 return a.naiveDiff.DiffSize(id, parent) 467 } 468 // AUFS doesn't need the parent layer to calculate the diff size. 469 return directory.Size(context.TODO(), path.Join(a.rootPath(), "diff", id)) 470 } 471 472 // ApplyDiff extracts the changeset from the given diff into the 473 // layer with the specified id and parent, returning the size of the 474 // new layer in bytes. 475 func (a *Driver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) { 476 if !a.isParent(id, parent) { 477 return a.naiveDiff.ApplyDiff(id, parent, diff) 478 } 479 480 // AUFS doesn't need the parent id to apply the diff if it is the direct parent. 481 if err = a.applyDiff(id, diff); err != nil { 482 return 483 } 484 485 return a.DiffSize(id, parent) 486 } 487 488 // Changes produces a list of changes between the specified layer 489 // and its parent layer. If parent is "", then all changes will be ADD changes. 490 func (a *Driver) Changes(id, parent string) ([]archive.Change, error) { 491 if !a.isParent(id, parent) { 492 return a.naiveDiff.Changes(id, parent) 493 } 494 495 // AUFS doesn't have snapshots, so we need to get changes from all parent 496 // layers. 497 layers, err := a.getParentLayerPaths(id) 498 if err != nil { 499 return nil, err 500 } 501 return archive.Changes(layers, path.Join(a.rootPath(), "diff", id)) 502 } 503 504 func (a *Driver) getParentLayerPaths(id string) ([]string, error) { 505 parentIds, err := getParentIDs(a.rootPath(), id) 506 if err != nil { 507 return nil, err 508 } 509 layers := make([]string, len(parentIds)) 510 511 // Get the diff paths for all the parent ids 512 for i, p := range parentIds { 513 layers[i] = path.Join(a.rootPath(), "diff", p) 514 } 515 return layers, nil 516 } 517 518 func (a *Driver) mount(id string, target string, mountLabel string, layers []string) error { 519 // If the id is mounted or we get an error return 520 if mounted, err := a.mounted(target); err != nil || mounted { 521 return err 522 } 523 524 rw := a.getDiffPath(id) 525 526 if err := a.aufsMount(layers, rw, target, mountLabel); err != nil { 527 return fmt.Errorf("error creating aufs mount to %s: %v", target, err) 528 } 529 return nil 530 } 531 532 func (a *Driver) unmount(mountPath string) error { 533 if mounted, err := a.mounted(mountPath); err != nil || !mounted { 534 return err 535 } 536 return Unmount(mountPath) 537 } 538 539 func (a *Driver) mounted(mountpoint string) (bool, error) { 540 return graphdriver.Mounted(graphdriver.FsMagicAufs, mountpoint) 541 } 542 543 // Cleanup aufs and unmount all mountpoints 544 func (a *Driver) Cleanup() error { 545 dir := a.mntPath() 546 files, err := ioutil.ReadDir(dir) 547 if err != nil { 548 return errors.Wrap(err, "aufs readdir error") 549 } 550 for _, f := range files { 551 if !f.IsDir() { 552 continue 553 } 554 555 m := path.Join(dir, f.Name()) 556 557 if err := a.unmount(m); err != nil { 558 logger.WithError(err).WithField("method", "Cleanup()").Warn() 559 } 560 } 561 return mount.RecursiveUnmount(a.root) 562 } 563 564 func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err error) { 565 defer func() { 566 if err != nil { 567 mount.Unmount(target) 568 } 569 }() 570 571 // Mount options are clipped to page size(4096 bytes). If there are more 572 // layers then these are remounted individually using append. 573 574 offset := 54 575 if useDirperm() { 576 offset += len(",dirperm1") 577 } 578 b := make([]byte, unix.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel 579 bp := copy(b, fmt.Sprintf("br:%s=rw", rw)) 580 581 index := 0 582 for ; index < len(ro); index++ { 583 layer := fmt.Sprintf(":%s=ro+wh", ro[index]) 584 if bp+len(layer) > len(b) { 585 break 586 } 587 bp += copy(b[bp:], layer) 588 } 589 590 opts := "dio,xino=/dev/shm/aufs.xino" 591 if useDirperm() { 592 opts += ",dirperm1" 593 } 594 data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel) 595 a.mntL.Lock() 596 err = unix.Mount("none", target, "aufs", 0, data) 597 a.mntL.Unlock() 598 if err != nil { 599 err = errors.Wrap(err, "mount target="+target+" data="+data) 600 return 601 } 602 603 for index < len(ro) { 604 bp = 0 605 for ; index < len(ro); index++ { 606 layer := fmt.Sprintf("append:%s=ro+wh,", ro[index]) 607 if bp+len(layer) > len(b) { 608 break 609 } 610 bp += copy(b[bp:], layer) 611 } 612 data := label.FormatMountLabel(string(b[:bp]), mountLabel) 613 a.mntL.Lock() 614 err = unix.Mount("none", target, "aufs", unix.MS_REMOUNT, data) 615 a.mntL.Unlock() 616 if err != nil { 617 err = errors.Wrap(err, "mount target="+target+" flags=MS_REMOUNT data="+data) 618 return 619 } 620 } 621 622 return 623 } 624 625 // useDirperm checks dirperm1 mount option can be used with the current 626 // version of aufs. 627 func useDirperm() bool { 628 enableDirpermLock.Do(func() { 629 base, err := ioutil.TempDir("", "docker-aufs-base") 630 if err != nil { 631 logger.Errorf("error checking dirperm1: %v", err) 632 return 633 } 634 defer os.RemoveAll(base) 635 636 union, err := ioutil.TempDir("", "docker-aufs-union") 637 if err != nil { 638 logger.Errorf("error checking dirperm1: %v", err) 639 return 640 } 641 defer os.RemoveAll(union) 642 643 opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base) 644 if err := unix.Mount("none", union, "aufs", 0, opts); err != nil { 645 return 646 } 647 enableDirperm = true 648 if err := Unmount(union); err != nil { 649 logger.Errorf("error checking dirperm1: failed to unmount %v", err) 650 } 651 }) 652 return enableDirperm 653 }