github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/graphdriver/overlay/overlay.go (about) 1 //go:build linux 2 // +build linux 3 4 package overlay // import "github.com/docker/docker/daemon/graphdriver/overlay" 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "path" 11 "path/filepath" 12 "strconv" 13 "strings" 14 15 "github.com/containerd/continuity/fs" 16 "github.com/docker/docker/daemon/graphdriver" 17 "github.com/docker/docker/daemon/graphdriver/copy" 18 "github.com/docker/docker/daemon/graphdriver/overlayutils" 19 "github.com/docker/docker/pkg/archive" 20 "github.com/docker/docker/pkg/containerfs" 21 "github.com/docker/docker/pkg/idtools" 22 "github.com/docker/docker/pkg/parsers" 23 "github.com/moby/locker" 24 "github.com/moby/sys/mount" 25 "github.com/opencontainers/selinux/go-selinux/label" 26 "github.com/sirupsen/logrus" 27 "golang.org/x/sys/unix" 28 ) 29 30 // This is a small wrapper over the NaiveDiffWriter that lets us have a custom 31 // implementation of ApplyDiff() 32 33 var ( 34 // ErrApplyDiffFallback is returned to indicate that a normal ApplyDiff is applied as a fallback from Naive diff writer. 35 ErrApplyDiffFallback = fmt.Errorf("Fall back to normal ApplyDiff") 36 backingFs = "<unknown>" 37 ) 38 39 // ApplyDiffProtoDriver wraps the ProtoDriver by extending the interface with ApplyDiff method. 40 type ApplyDiffProtoDriver interface { 41 graphdriver.ProtoDriver 42 // ApplyDiff writes the diff to the archive for the given id and parent id. 43 // It returns the size in bytes written if successful, an error ErrApplyDiffFallback is returned otherwise. 44 ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) 45 } 46 47 type naiveDiffDriverWithApply struct { 48 graphdriver.Driver 49 applyDiff ApplyDiffProtoDriver 50 } 51 52 // NaiveDiffDriverWithApply returns a NaiveDiff driver with custom ApplyDiff. 53 func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, idMap idtools.IdentityMapping) graphdriver.Driver { 54 return &naiveDiffDriverWithApply{ 55 Driver: graphdriver.NewNaiveDiffDriver(driver, idMap), 56 applyDiff: driver, 57 } 58 } 59 60 // ApplyDiff creates a diff layer with either the NaiveDiffDriver or with a fallback. 61 func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff io.Reader) (int64, error) { 62 b, err := d.applyDiff.ApplyDiff(id, parent, diff) 63 if err == ErrApplyDiffFallback { 64 return d.Driver.ApplyDiff(id, parent, diff) 65 } 66 return b, err 67 } 68 69 // This backend uses the overlay union filesystem for containers 70 // plus hard link file sharing for images. 71 72 // Each container/image can have a "root" subdirectory which is a plain 73 // filesystem hierarchy, or they can use overlay. 74 75 // If they use overlay there is a "upper" directory and a "lower-id" 76 // file, as well as "merged" and "work" directories. The "upper" 77 // directory has the upper layer of the overlay, and "lower-id" contains 78 // the id of the parent whose "root" directory shall be used as the lower 79 // layer in the overlay. The overlay itself is mounted in the "merged" 80 // directory, and the "work" dir is needed for overlay to work. 81 82 // When an overlay layer is created there are two cases, either the 83 // parent has a "root" dir, then we start out with an empty "upper" 84 // directory overlaid on the parents root. This is typically the 85 // case with the init layer of a container which is based on an image. 86 // If there is no "root" in the parent, we inherit the lower-id from 87 // the parent and start by making a copy in the parent's "upper" dir. 88 // This is typically the case for a container layer which copies 89 // its parent -init upper layer. 90 91 // Additionally we also have a custom implementation of ApplyLayer 92 // which makes a recursive copy of the parent "root" layer using 93 // hardlinks to share file data, and then applies the layer on top 94 // of that. This means all child images share file (but not directory) 95 // data with the parent. 96 97 type overlayOptions struct{} 98 99 // Driver contains information about the home directory and the list of active mounts that are created using this driver. 100 type Driver struct { 101 home string 102 idMap idtools.IdentityMapping 103 ctr *graphdriver.RefCounter 104 supportsDType bool 105 locker *locker.Locker 106 } 107 108 func init() { 109 graphdriver.Register("overlay", Init) 110 } 111 112 // Init returns the NaiveDiffDriver, a native diff driver for overlay filesystem. 113 // If overlay filesystem is not supported on the host, the error 114 // graphdriver.ErrNotSupported is returned. 115 // If an overlay filesystem is not supported over an existing filesystem then 116 // error graphdriver.ErrIncompatibleFS is returned. 117 func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) { 118 _, err := parseOptions(options) 119 if err != nil { 120 return nil, err 121 } 122 123 // Perform feature detection on /var/lib/docker/overlay if it's an existing directory. 124 // This covers situations where /var/lib/docker/overlay is a mount, and on a different 125 // filesystem than /var/lib/docker. 126 // If the path does not exist, fall back to using /var/lib/docker for feature detection. 127 testdir := home 128 if _, err := os.Stat(testdir); os.IsNotExist(err) { 129 testdir = filepath.Dir(testdir) 130 } 131 132 if err := overlayutils.SupportsOverlay(testdir, false); err != nil { 133 logrus.WithField("storage-driver", "overlay").Error(err) 134 return nil, graphdriver.ErrNotSupported 135 } 136 137 fsMagic, err := graphdriver.GetFSMagic(testdir) 138 if err != nil { 139 return nil, err 140 } 141 if fsName, ok := graphdriver.FsNames[fsMagic]; ok { 142 backingFs = fsName 143 } 144 145 supportsDType, err := fs.SupportsDType(testdir) 146 if err != nil { 147 return nil, err 148 } 149 if !supportsDType { 150 return nil, overlayutils.ErrDTypeNotSupported("overlay", backingFs) 151 } 152 153 currentID := idtools.CurrentIdentity() 154 dirID := idtools.Identity{ 155 UID: currentID.UID, 156 GID: idMap.RootPair().GID, 157 } 158 159 // Create the driver home dir 160 if err := idtools.MkdirAllAndChown(home, 0710, dirID); err != nil { 161 return nil, err 162 } 163 d := &Driver{ 164 home: home, 165 idMap: idMap, 166 ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)), 167 supportsDType: supportsDType, 168 locker: locker.New(), 169 } 170 171 return NaiveDiffDriverWithApply(d, d.idMap), nil 172 } 173 174 func parseOptions(options []string) (*overlayOptions, error) { 175 o := &overlayOptions{} 176 for _, option := range options { 177 key, _, err := parsers.ParseKeyValueOpt(option) 178 if err != nil { 179 return nil, err 180 } 181 key = strings.ToLower(key) 182 switch key { 183 default: 184 return nil, fmt.Errorf("overlay: unknown option %s", key) 185 } 186 } 187 return o, nil 188 } 189 190 func (d *Driver) String() string { 191 return "overlay" 192 } 193 194 // Status returns current driver information in a two dimensional string array. 195 // Output contains "Backing Filesystem" used in this implementation. 196 func (d *Driver) Status() [][2]string { 197 return [][2]string{ 198 {"Backing Filesystem", backingFs}, 199 {"Supports d_type", strconv.FormatBool(d.supportsDType)}, 200 } 201 } 202 203 // GetMetadata returns metadata about the overlay driver such as root, 204 // LowerDir, UpperDir, WorkDir and MergeDir used to store data. 205 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 206 dir := d.dir(id) 207 if _, err := os.Stat(dir); err != nil { 208 return nil, err 209 } 210 211 metadata := make(map[string]string) 212 213 // If id has a root, it is an image 214 rootDir := path.Join(dir, "root") 215 if _, err := os.Stat(rootDir); err == nil { 216 metadata["RootDir"] = rootDir 217 return metadata, nil 218 } 219 220 lowerID, err := os.ReadFile(path.Join(dir, "lower-id")) 221 if err != nil { 222 return nil, err 223 } 224 225 metadata["LowerDir"] = path.Join(d.dir(string(lowerID)), "root") 226 metadata["UpperDir"] = path.Join(dir, "upper") 227 metadata["WorkDir"] = path.Join(dir, "work") 228 metadata["MergedDir"] = path.Join(dir, "merged") 229 230 return metadata, nil 231 } 232 233 // Cleanup any state created by overlay which should be cleaned when daemon 234 // is being shutdown. For now, we just have to unmount the bind mounted 235 // we had created. 236 func (d *Driver) Cleanup() error { 237 return mount.RecursiveUnmount(d.home) 238 } 239 240 // CreateReadWrite creates a layer that is writable for use as a container 241 // file system. 242 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 243 return d.Create(id, parent, opts) 244 } 245 246 // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. 247 // The parent filesystem is used to configure these directories for the overlay. 248 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) { 249 if opts != nil && len(opts.StorageOpt) != 0 { 250 return fmt.Errorf("--storage-opt is not supported for overlay") 251 } 252 253 dir := d.dir(id) 254 root := d.idMap.RootPair() 255 256 currentID := idtools.CurrentIdentity() 257 dirID := idtools.Identity{ 258 UID: currentID.UID, 259 GID: root.GID, 260 } 261 if err := idtools.MkdirAndChown(dir, 0710, dirID); err != nil { 262 return err 263 } 264 265 defer func() { 266 // Clean up on failure 267 if retErr != nil { 268 os.RemoveAll(dir) 269 } 270 }() 271 272 // Toplevel images are just a "root" dir 273 if parent == "" { 274 // This must be 0755 otherwise unprivileged users will in the container will not be able to read / in the container 275 return idtools.MkdirAndChown(path.Join(dir, "root"), 0755, root) 276 } 277 278 parentDir := d.dir(parent) 279 280 // Ensure parent exists 281 if _, err := os.Lstat(parentDir); err != nil { 282 return err 283 } 284 285 // If parent has a root, just do an overlay to it 286 parentRoot := path.Join(parentDir, "root") 287 288 if s, err := os.Lstat(parentRoot); err == nil { 289 if err := idtools.MkdirAndChown(path.Join(dir, "upper"), s.Mode(), root); err != nil { 290 return err 291 } 292 if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil { 293 return err 294 } 295 return os.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0600) 296 } 297 298 // Otherwise, copy the upper and the lower-id from the parent 299 300 lowerID, err := os.ReadFile(path.Join(parentDir, "lower-id")) 301 if err != nil { 302 return err 303 } 304 305 if err := os.WriteFile(path.Join(dir, "lower-id"), lowerID, 0600); err != nil { 306 return err 307 } 308 309 parentUpperDir := path.Join(parentDir, "upper") 310 s, err := os.Lstat(parentUpperDir) 311 if err != nil { 312 return err 313 } 314 315 upperDir := path.Join(dir, "upper") 316 if err := idtools.MkdirAndChown(upperDir, s.Mode(), root); err != nil { 317 return err 318 } 319 if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil { 320 return err 321 } 322 323 return copy.DirCopy(parentUpperDir, upperDir, copy.Content, true) 324 } 325 326 func (d *Driver) dir(id string) string { 327 return path.Join(d.home, id) 328 } 329 330 // Remove cleans the directories that are created for this id. 331 func (d *Driver) Remove(id string) error { 332 if id == "" { 333 return fmt.Errorf("refusing to remove the directories: id is empty") 334 } 335 d.locker.Lock(id) 336 defer d.locker.Unlock(id) 337 return containerfs.EnsureRemoveAll(d.dir(id)) 338 } 339 340 // Get creates and mounts the required file system for the given id and returns the mount path. 341 func (d *Driver) Get(id, mountLabel string) (_ string, err error) { 342 d.locker.Lock(id) 343 defer d.locker.Unlock(id) 344 dir := d.dir(id) 345 if _, err := os.Stat(dir); err != nil { 346 return "", err 347 } 348 // If id has a root, just return it 349 rootDir := path.Join(dir, "root") 350 if _, err := os.Stat(rootDir); err == nil { 351 return rootDir, nil 352 } 353 354 mergedDir := path.Join(dir, "merged") 355 if count := d.ctr.Increment(mergedDir); count > 1 { 356 return mergedDir, nil 357 } 358 defer func() { 359 if err != nil { 360 if c := d.ctr.Decrement(mergedDir); c <= 0 { 361 if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil { 362 logrus.WithField("storage-driver", "overlay").Debugf("Failed to unmount %s: %v: %v", id, mntErr, err) 363 } 364 // Cleanup the created merged directory; see the comment in Put's rmdir 365 if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) { 366 logrus.WithField("storage-driver", "overlay").Warnf("Failed to remove %s: %v: %v", id, rmErr, err) 367 } 368 } 369 } 370 }() 371 lowerID, err := os.ReadFile(path.Join(dir, "lower-id")) 372 if err != nil { 373 return "", err 374 } 375 root := d.idMap.RootPair() 376 if err := idtools.MkdirAndChown(mergedDir, 0700, root); err != nil { 377 return "", err 378 } 379 var ( 380 lowerDir = path.Join(d.dir(string(lowerID)), "root") 381 upperDir = path.Join(dir, "upper") 382 workDir = path.Join(dir, "work") 383 opts = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerDir, upperDir, workDir) 384 ) 385 if err := unix.Mount("overlay", mergedDir, "overlay", 0, label.FormatMountLabel(opts, mountLabel)); err != nil { 386 return "", fmt.Errorf("error creating overlay mount to %s: %v", mergedDir, err) 387 } 388 // chown "workdir/work" to the remapped root UID/GID. Overlay fs inside a 389 // user namespace requires this to move a directory from lower to upper. 390 if err := root.Chown(path.Join(workDir, "work")); err != nil { 391 return "", err 392 } 393 return mergedDir, nil 394 } 395 396 // Put unmounts the mount path created for the give id. 397 // It also removes the 'merged' directory to force the kernel to unmount the 398 // overlay mount in other namespaces. 399 func (d *Driver) Put(id string) error { 400 d.locker.Lock(id) 401 defer d.locker.Unlock(id) 402 // If id has a root, just return 403 if _, err := os.Stat(path.Join(d.dir(id), "root")); err == nil { 404 return nil 405 } 406 mountpoint := path.Join(d.dir(id), "merged") 407 logger := logrus.WithField("storage-driver", "overlay") 408 if count := d.ctr.Decrement(mountpoint); count > 0 { 409 return nil 410 } 411 if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil { 412 logger.Debugf("Failed to unmount %s overlay: %v", id, err) 413 } 414 415 // Remove the mountpoint here. Removing the mountpoint (in newer kernels) 416 // will cause all other instances of this mount in other mount namespaces 417 // to be unmounted. This is necessary to avoid cases where an overlay mount 418 // that is present in another namespace will cause subsequent mounts 419 // operations to fail with ebusy. We ignore any errors here because this may 420 // fail on older kernels which don't have 421 // torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied. 422 if err := unix.Rmdir(mountpoint); err != nil { 423 logger.Debugf("Failed to remove %s overlay: %v", id, err) 424 } 425 return nil 426 } 427 428 // ApplyDiff applies the new layer on top of the root, if parent does not exist with will return an ErrApplyDiffFallback error. 429 func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64, err error) { 430 dir := d.dir(id) 431 432 if parent == "" { 433 return 0, ErrApplyDiffFallback 434 } 435 436 parentRootDir := path.Join(d.dir(parent), "root") 437 if _, err := os.Stat(parentRootDir); err != nil { 438 return 0, ErrApplyDiffFallback 439 } 440 441 // We now know there is a parent, and it has a "root" directory containing 442 // the full root filesystem. We can just hardlink it and apply the 443 // layer. This relies on two things: 444 // 1) ApplyDiff is only run once on a clean (no writes to upper layer) container 445 // 2) ApplyDiff doesn't do any in-place writes to files (would break hardlinks) 446 // These are all currently true and are not expected to break 447 448 tmpRootDir, err := os.MkdirTemp(dir, "tmproot") 449 if err != nil { 450 return 0, err 451 } 452 defer func() { 453 if err != nil { 454 os.RemoveAll(tmpRootDir) 455 } else { 456 os.RemoveAll(path.Join(dir, "upper")) 457 os.RemoveAll(path.Join(dir, "work")) 458 os.RemoveAll(path.Join(dir, "merged")) 459 os.RemoveAll(path.Join(dir, "lower-id")) 460 } 461 }() 462 463 if err = copy.DirCopy(parentRootDir, tmpRootDir, copy.Hardlink, true); err != nil { 464 return 0, err 465 } 466 467 options := &archive.TarOptions{IDMap: d.idMap} 468 if size, err = graphdriver.ApplyUncompressedLayer(tmpRootDir, diff, options); err != nil { 469 return 0, err 470 } 471 472 rootDir := path.Join(dir, "root") 473 if err := os.Rename(tmpRootDir, rootDir); err != nil { 474 return 0, err 475 } 476 477 return 478 } 479 480 // Exists checks to see if the id is already mounted. 481 func (d *Driver) Exists(id string) bool { 482 _, err := os.Stat(d.dir(id)) 483 return err == nil 484 }