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