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