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