github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/daemon/graphdriver/fuse-overlayfs/fuseoverlayfs.go (about) 1 // +build linux 2 3 package fuseoverlayfs // import "github.com/docker/docker/daemon/graphdriver/fuse-overlayfs" 4 5 import ( 6 "bytes" 7 "context" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path" 14 "path/filepath" 15 "strings" 16 17 "github.com/containerd/containerd/sys" 18 "github.com/docker/docker/daemon/graphdriver" 19 "github.com/docker/docker/daemon/graphdriver/overlayutils" 20 "github.com/docker/docker/pkg/archive" 21 "github.com/docker/docker/pkg/chrootarchive" 22 "github.com/docker/docker/pkg/containerfs" 23 "github.com/docker/docker/pkg/directory" 24 "github.com/docker/docker/pkg/idtools" 25 "github.com/docker/docker/pkg/parsers/kernel" 26 "github.com/docker/docker/pkg/system" 27 "github.com/moby/locker" 28 "github.com/moby/sys/mount" 29 "github.com/opencontainers/selinux/go-selinux/label" 30 "github.com/pkg/errors" 31 "github.com/sirupsen/logrus" 32 "golang.org/x/sys/unix" 33 ) 34 35 var ( 36 // untar defines the untar method 37 untar = chrootarchive.UntarUncompressed 38 ) 39 40 const ( 41 driverName = "fuse-overlayfs" 42 binary = "fuse-overlayfs" 43 linkDir = "l" 44 diffDirName = "diff" 45 workDirName = "work" 46 mergedDirName = "merged" 47 lowerFile = "lower" 48 maxDepth = 128 49 50 // idLength represents the number of random characters 51 // which can be used to create the unique link identifier 52 // for every layer. If this value is too long then the 53 // page size limit for the mount command may be exceeded. 54 // The idLength should be selected such that following equation 55 // is true (512 is a buffer for label metadata). 56 // ((idLength + len(linkDir) + 1) * maxDepth) <= (pageSize - 512) 57 idLength = 26 58 ) 59 60 // Driver contains information about the home directory and the list of active 61 // mounts that are created using this driver. 62 type Driver struct { 63 home string 64 uidMaps []idtools.IDMap 65 gidMaps []idtools.IDMap 66 ctr *graphdriver.RefCounter 67 naiveDiff graphdriver.DiffDriver 68 locker *locker.Locker 69 } 70 71 var ( 72 logger = logrus.WithField("storage-driver", driverName) 73 ) 74 75 func init() { 76 graphdriver.Register(driverName, Init) 77 } 78 79 // Init returns the naive diff driver for fuse-overlayfs. 80 // If fuse-overlayfs is not supported on the host, the error 81 // graphdriver.ErrNotSupported is returned. 82 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 83 if _, err := exec.LookPath(binary); err != nil { 84 logger.Error(err) 85 return nil, graphdriver.ErrNotSupported 86 } 87 if !kernel.CheckKernelVersion(4, 18, 0) { 88 return nil, graphdriver.ErrNotSupported 89 } 90 91 rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) 92 if err != nil { 93 return nil, err 94 } 95 // Create the driver home dir 96 if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { 97 return nil, err 98 } 99 100 d := &Driver{ 101 home: home, 102 uidMaps: uidMaps, 103 gidMaps: gidMaps, 104 ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicFUSE)), 105 locker: locker.New(), 106 } 107 108 d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps) 109 110 return d, nil 111 } 112 113 func (d *Driver) String() string { 114 return driverName 115 } 116 117 // Status returns current driver information in a two dimensional string array. 118 func (d *Driver) Status() [][2]string { 119 return [][2]string{} 120 } 121 122 // GetMetadata returns metadata about the overlay driver such as the LowerDir, 123 // UpperDir, WorkDir, and MergeDir used to store data. 124 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 125 dir := d.dir(id) 126 if _, err := os.Stat(dir); err != nil { 127 return nil, err 128 } 129 130 metadata := map[string]string{ 131 "WorkDir": path.Join(dir, workDirName), 132 "MergedDir": path.Join(dir, mergedDirName), 133 "UpperDir": path.Join(dir, diffDirName), 134 } 135 136 lowerDirs, err := d.getLowerDirs(id) 137 if err != nil { 138 return nil, err 139 } 140 if len(lowerDirs) > 0 { 141 metadata["LowerDir"] = strings.Join(lowerDirs, ":") 142 } 143 144 return metadata, nil 145 } 146 147 // Cleanup any state created by overlay which should be cleaned when daemon 148 // is being shutdown. For now, we just have to unmount the bind mounted 149 // we had created. 150 func (d *Driver) Cleanup() error { 151 return mount.RecursiveUnmount(d.home) 152 } 153 154 // CreateReadWrite creates a layer that is writable for use as a container 155 // file system. 156 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 157 if opts != nil && len(opts.StorageOpt) != 0 { 158 return fmt.Errorf("--storage-opt is not supported") 159 } 160 return d.create(id, parent, opts) 161 } 162 163 // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. 164 // The parent filesystem is used to configure these directories for the overlay. 165 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) { 166 if opts != nil && len(opts.StorageOpt) != 0 { 167 return fmt.Errorf("--storage-opt is not supported") 168 } 169 return d.create(id, parent, opts) 170 } 171 172 func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) { 173 dir := d.dir(id) 174 175 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 176 if err != nil { 177 return err 178 } 179 root := idtools.Identity{UID: rootUID, GID: rootGID} 180 181 if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil { 182 return err 183 } 184 if err := idtools.MkdirAndChown(dir, 0700, root); err != nil { 185 return err 186 } 187 188 defer func() { 189 // Clean up on failure 190 if retErr != nil { 191 os.RemoveAll(dir) 192 } 193 }() 194 195 if opts != nil && len(opts.StorageOpt) > 0 { 196 return fmt.Errorf("--storage-opt is not supported") 197 } 198 199 if err := idtools.MkdirAndChown(path.Join(dir, diffDirName), 0755, root); err != nil { 200 return err 201 } 202 203 lid := overlayutils.GenerateID(idLength, logger) 204 if err := os.Symlink(path.Join("..", id, diffDirName), path.Join(d.home, linkDir, lid)); err != nil { 205 return err 206 } 207 208 // Write link id to link file 209 if err := ioutil.WriteFile(path.Join(dir, "link"), []byte(lid), 0644); err != nil { 210 return err 211 } 212 213 // if no parent directory, done 214 if parent == "" { 215 return nil 216 } 217 218 if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0700, root); err != nil { 219 return err 220 } 221 222 if err := ioutil.WriteFile(path.Join(d.dir(parent), "committed"), []byte{}, 0600); err != nil { 223 return err 224 } 225 226 lower, err := d.getLower(parent) 227 if err != nil { 228 return err 229 } 230 if lower != "" { 231 if err := ioutil.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0666); err != nil { 232 return err 233 } 234 } 235 236 return nil 237 } 238 239 func (d *Driver) getLower(parent string) (string, error) { 240 parentDir := d.dir(parent) 241 242 // Ensure parent exists 243 if _, err := os.Lstat(parentDir); err != nil { 244 return "", err 245 } 246 247 // Read Parent link fileA 248 parentLink, err := ioutil.ReadFile(path.Join(parentDir, "link")) 249 if err != nil { 250 return "", err 251 } 252 lowers := []string{path.Join(linkDir, string(parentLink))} 253 254 parentLower, err := ioutil.ReadFile(path.Join(parentDir, lowerFile)) 255 if err == nil { 256 parentLowers := strings.Split(string(parentLower), ":") 257 lowers = append(lowers, parentLowers...) 258 } 259 if len(lowers) > maxDepth { 260 return "", errors.New("max depth exceeded") 261 } 262 return strings.Join(lowers, ":"), nil 263 } 264 265 func (d *Driver) dir(id string) string { 266 return path.Join(d.home, id) 267 } 268 269 func (d *Driver) getLowerDirs(id string) ([]string, error) { 270 var lowersArray []string 271 lowers, err := ioutil.ReadFile(path.Join(d.dir(id), lowerFile)) 272 if err == nil { 273 for _, s := range strings.Split(string(lowers), ":") { 274 lp, err := os.Readlink(path.Join(d.home, s)) 275 if err != nil { 276 return nil, err 277 } 278 lowersArray = append(lowersArray, path.Clean(path.Join(d.home, linkDir, lp))) 279 } 280 } else if !os.IsNotExist(err) { 281 return nil, err 282 } 283 return lowersArray, nil 284 } 285 286 // Remove cleans the directories that are created for this id. 287 func (d *Driver) Remove(id string) error { 288 if id == "" { 289 return fmt.Errorf("refusing to remove the directories: id is empty") 290 } 291 d.locker.Lock(id) 292 defer d.locker.Unlock(id) 293 dir := d.dir(id) 294 lid, err := ioutil.ReadFile(path.Join(dir, "link")) 295 if err == nil { 296 if len(lid) == 0 { 297 logger.Errorf("refusing to remove empty link for layer %v", id) 298 } else if err := os.RemoveAll(path.Join(d.home, linkDir, string(lid))); err != nil { 299 logger.Debugf("Failed to remove link: %v", err) 300 } 301 } 302 303 if err := system.EnsureRemoveAll(dir); err != nil && !os.IsNotExist(err) { 304 return err 305 } 306 return nil 307 } 308 309 // Get creates and mounts the required file system for the given id and returns the mount path. 310 func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr error) { 311 d.locker.Lock(id) 312 defer d.locker.Unlock(id) 313 dir := d.dir(id) 314 if _, err := os.Stat(dir); err != nil { 315 return nil, err 316 } 317 318 diffDir := path.Join(dir, diffDirName) 319 lowers, err := ioutil.ReadFile(path.Join(dir, lowerFile)) 320 if err != nil { 321 // If no lower, just return diff directory 322 if os.IsNotExist(err) { 323 return containerfs.NewLocalContainerFS(diffDir), nil 324 } 325 return nil, err 326 } 327 328 mergedDir := path.Join(dir, mergedDirName) 329 if count := d.ctr.Increment(mergedDir); count > 1 { 330 return containerfs.NewLocalContainerFS(mergedDir), nil 331 } 332 defer func() { 333 if retErr != nil { 334 if c := d.ctr.Decrement(mergedDir); c <= 0 { 335 if unmounted := fusermountU(mergedDir); !unmounted { 336 if mntErr := unix.Unmount(mergedDir, 0); mntErr != nil { 337 logger.Errorf("error unmounting %v: %v", mergedDir, mntErr) 338 } 339 } 340 // Cleanup the created merged directory; see the comment in Put's rmdir 341 if rmErr := unix.Rmdir(mergedDir); rmErr != nil && !os.IsNotExist(rmErr) { 342 logger.Debugf("Failed to remove %s: %v: %v", id, rmErr, err) 343 } 344 } 345 } 346 }() 347 348 workDir := path.Join(dir, workDirName) 349 splitLowers := strings.Split(string(lowers), ":") 350 absLowers := make([]string, len(splitLowers)) 351 for i, s := range splitLowers { 352 absLowers[i] = path.Join(d.home, s) 353 } 354 var readonly bool 355 if _, err := os.Stat(path.Join(dir, "committed")); err == nil { 356 readonly = true 357 } else if !os.IsNotExist(err) { 358 return nil, err 359 } 360 361 var opts string 362 if readonly { 363 opts = "lowerdir=" + diffDir + ":" + strings.Join(absLowers, ":") 364 } else { 365 opts = "lowerdir=" + strings.Join(absLowers, ":") + ",upperdir=" + diffDir + ",workdir=" + workDir 366 } 367 368 mountData := label.FormatMountLabel(opts, mountLabel) 369 mountTarget := mergedDir 370 371 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 372 if err != nil { 373 return nil, err 374 } 375 if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { 376 return nil, err 377 } 378 379 mountProgram := exec.Command(binary, "-o", mountData, mountTarget) 380 mountProgram.Dir = d.home 381 var b bytes.Buffer 382 mountProgram.Stderr = &b 383 if err = mountProgram.Run(); err != nil { 384 output := b.String() 385 if output == "" { 386 output = "<stderr empty>" 387 } 388 return nil, errors.Wrapf(err, "using mount program %s: %s", binary, output) 389 } 390 391 return containerfs.NewLocalContainerFS(mergedDir), nil 392 } 393 394 // Put unmounts the mount path created for the give id. 395 // It also removes the 'merged' directory to force the kernel to unmount the 396 // overlay mount in other namespaces. 397 func (d *Driver) Put(id string) error { 398 d.locker.Lock(id) 399 defer d.locker.Unlock(id) 400 dir := d.dir(id) 401 _, err := ioutil.ReadFile(path.Join(dir, lowerFile)) 402 if err != nil { 403 // If no lower, no mount happened and just return directly 404 if os.IsNotExist(err) { 405 return nil 406 } 407 return err 408 } 409 410 mountpoint := path.Join(dir, mergedDirName) 411 if count := d.ctr.Decrement(mountpoint); count > 0 { 412 return nil 413 } 414 if unmounted := fusermountU(mountpoint); !unmounted { 415 if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil { 416 logger.Debugf("Failed to unmount %s overlay: %s - %v", id, mountpoint, err) 417 } 418 } 419 // Remove the mountpoint here. Removing the mountpoint (in newer kernels) 420 // will cause all other instances of this mount in other mount namespaces 421 // to be unmounted. This is necessary to avoid cases where an overlay mount 422 // that is present in another namespace will cause subsequent mounts 423 // operations to fail with ebusy. We ignore any errors here because this may 424 // fail on older kernels which don't have 425 // torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe applied. 426 if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) { 427 logger.Debugf("Failed to remove %s overlay: %v", id, err) 428 } 429 return nil 430 } 431 432 // Exists checks to see if the id is already mounted. 433 func (d *Driver) Exists(id string) bool { 434 _, err := os.Stat(d.dir(id)) 435 return err == nil 436 } 437 438 // isParent determines whether the given parent is the direct parent of the 439 // given layer id 440 func (d *Driver) isParent(id, parent string) bool { 441 lowers, err := d.getLowerDirs(id) 442 if err != nil { 443 return false 444 } 445 if parent == "" && len(lowers) > 0 { 446 return false 447 } 448 449 parentDir := d.dir(parent) 450 var ld string 451 if len(lowers) > 0 { 452 ld = filepath.Dir(lowers[0]) 453 } 454 if ld == "" && parent == "" { 455 return true 456 } 457 return ld == parentDir 458 } 459 460 // ApplyDiff applies the new layer into a root 461 func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64, err error) { 462 if !d.isParent(id, parent) { 463 return d.naiveDiff.ApplyDiff(id, parent, diff) 464 } 465 466 applyDir := d.getDiffPath(id) 467 468 logger.Debugf("Applying tar in %s", applyDir) 469 // Overlay doesn't need the parent id to apply the diff 470 if err := untar(diff, applyDir, &archive.TarOptions{ 471 UIDMaps: d.uidMaps, 472 GIDMaps: d.gidMaps, 473 // Use AUFS whiteout format: https://github.com/containers/storage/blob/39a8d5ed9843844eafb5d2ba6e6a7510e0126f40/drivers/overlay/overlay.go#L1084-L1089 474 WhiteoutFormat: archive.AUFSWhiteoutFormat, 475 InUserNS: sys.RunningInUserNS(), 476 }); err != nil { 477 return 0, err 478 } 479 480 return directory.Size(context.TODO(), applyDir) 481 } 482 483 func (d *Driver) getDiffPath(id string) string { 484 dir := d.dir(id) 485 486 return path.Join(dir, diffDirName) 487 } 488 489 // DiffSize calculates the changes between the specified id 490 // and its parent and returns the size in bytes of the changes 491 // relative to its base filesystem directory. 492 func (d *Driver) DiffSize(id, parent string) (size int64, err error) { 493 return d.naiveDiff.DiffSize(id, parent) 494 } 495 496 // Diff produces an archive of the changes between the specified 497 // layer and its parent layer which may be "". 498 func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) { 499 return d.naiveDiff.Diff(id, parent) 500 } 501 502 // Changes produces a list of changes between the specified layer and its 503 // parent layer. If parent is "", then all changes will be ADD changes. 504 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) { 505 return d.naiveDiff.Changes(id, parent) 506 } 507 508 // fusermountU is from https://github.com/containers/storage/blob/39a8d5ed9843844eafb5d2ba6e6a7510e0126f40/drivers/overlay/overlay.go#L1016-L1040 509 func fusermountU(mountpoint string) (unmounted bool) { 510 // Attempt to unmount the FUSE mount using either fusermount or fusermount3. 511 // If they fail, fallback to unix.Unmount 512 for _, v := range []string{"fusermount3", "fusermount"} { 513 err := exec.Command(v, "-u", mountpoint).Run() 514 if err != nil && !os.IsNotExist(err) { 515 logrus.Debugf("Error unmounting %s with %s - %v", mountpoint, v, err) 516 } 517 if err == nil { 518 unmounted = true 519 break 520 } 521 } 522 // If fusermount|fusermount3 failed to unmount the FUSE file system, make sure all 523 // pending changes are propagated to the file system 524 if !unmounted { 525 fd, err := unix.Open(mountpoint, unix.O_DIRECTORY, 0) 526 if err == nil { 527 if err := unix.Syncfs(fd); err != nil { 528 logrus.Debugf("Error Syncfs(%s) - %v", mountpoint, err) 529 } 530 unix.Close(fd) 531 } 532 } 533 return 534 }