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