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