Skip to content

Commit fbd4de8

Browse files
giuseppeclaude
andcommitted
storage, overlay: add lower-layers file
Write a new "lower-layers" file alongside the existing "lower" file. It stores layer IDs directly instead of l/ symlink references; the reading side appends "/diff" itself. When present, lower-layers is preferred over lower. The old lower file is still written so that older tools (buildah, skopeo) continue to work. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent 8af7873 commit fbd4de8

File tree

2 files changed

+76
-185
lines changed

2 files changed

+76
-185
lines changed

storage/drivers/overlay/overlay.go

Lines changed: 76 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ const (
8484
stagingDir = "staging"
8585
tempDirName = "tempdirs"
8686
lowerFile = "lower"
87-
maxDepth = 500
87+
// lowerLayersFile references lower layers directly by layer ID
88+
// instead of going through the l/ symlinks. The code appends
89+
// "/diff" itself when consuming entries. It is preferred over
90+
// lowerFile when present. The old lowerFile is still written
91+
// for backward compatibility with older tools.
92+
lowerLayersFile = "lower-layers"
93+
maxDepth = 500
8894

8995
stagingLockFile = "staging.lock"
9096

@@ -327,7 +333,16 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
327333

328334
runhome := filepath.Join(options.RunRoot, filepath.Base(home))
329335

330-
// Create the driver home dir
336+
// Create the driver home dir.
337+
// NOTE: the l/ subdirectory currently also serves as an anchor that
338+
// prevents the home directory from being removed when all layers are
339+
// deleted. If l/ is dropped entirely in the future, an alternative
340+
// mechanism (e.g. a sentinel file) must be put in place to keep the
341+
// home directory around. Without it, supportsOverlay() on the error
342+
// path and checkAndRecordOverlaySupport() would successfully rmdir
343+
// the home, breaking XFS project quotas set on the directory and
344+
// causing ScanPriorDrivers() to no longer detect the overlay driver
345+
// as in use.
331346
if err := os.MkdirAll(path.Join(home, linkDir), 0o755); err != nil {
332347
return nil, err
333348
}
@@ -1178,6 +1193,35 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
11781193
}
11791194
}
11801195

1196+
// Write a lower-layers file referencing layers by ID instead of
1197+
// l/ symlink references. The reading side appends "/diff" itself.
1198+
parentDir := d.dir(parent)
1199+
layerLower := parent
1200+
parentLower, err := os.ReadFile(path.Join(parentDir, lowerLayersFile))
1201+
if err == nil {
1202+
layerLower += ":" + string(parentLower)
1203+
} else if !errors.Is(err, unix.ENOENT) {
1204+
return err
1205+
} else {
1206+
// Parent has no lower-layers file. Convert old-format lower
1207+
// entries (l/ symlinks) to layer IDs.
1208+
oldLower, err := os.ReadFile(path.Join(parentDir, lowerFile))
1209+
if err == nil {
1210+
for _, s := range strings.Split(string(oldLower), ":") {
1211+
target, err := os.Readlink(d.dir(s))
1212+
if err != nil {
1213+
return fmt.Errorf("reading symlink for lower %q: %w", s, err)
1214+
}
1215+
layerLower += ":" + filepath.Base(filepath.Dir(target))
1216+
}
1217+
} else if !errors.Is(err, unix.ENOENT) {
1218+
return err
1219+
}
1220+
}
1221+
if err := os.WriteFile(path.Join(dir, lowerLayersFile), []byte(layerLower), 0o666); err != nil {
1222+
return err
1223+
}
1224+
11811225
return nil
11821226
}
11831227

@@ -1215,20 +1259,9 @@ func (d *Driver) getLower(parent string) (string, error) {
12151259
return "", err
12161260
}
12171261

1218-
// Read Parent link fileA
12191262
parentLink, err := os.ReadFile(path.Join(parentDir, "link"))
12201263
if err != nil {
1221-
if !errors.Is(err, fs.ErrNotExist) {
1222-
return "", err
1223-
}
1224-
logrus.Warnf("Can't read parent link %q because it does not exist. Going through storage to recreate the missing links.", path.Join(parentDir, "link"))
1225-
if err := d.recreateSymlinks(); err != nil {
1226-
return "", fmt.Errorf("recreating the links: %w", err)
1227-
}
1228-
parentLink, err = os.ReadFile(path.Join(parentDir, "link"))
1229-
if err != nil {
1230-
return "", err
1231-
}
1264+
return "", err
12321265
}
12331266
lowers := []string{path.Join(linkDir, string(parentLink))}
12341267

@@ -1283,27 +1316,25 @@ func (d *Driver) dir2(id string, useImageStore bool) (string, string, bool) {
12831316

12841317
func (d *Driver) getLowerDirs(id string) ([]string, error) {
12851318
var lowersArray []string
1286-
lowers, err := os.ReadFile(path.Join(d.dir(id), lowerFile))
1319+
dir := d.dir(id)
1320+
lowers, err := os.ReadFile(path.Join(dir, lowerLayersFile))
1321+
if err != nil {
1322+
if !errors.Is(err, unix.ENOENT) {
1323+
return nil, err
1324+
}
1325+
lowers, err = os.ReadFile(path.Join(dir, lowerFile))
1326+
}
12871327
if err == nil {
12881328
for s := range strings.SplitSeq(string(lowers), ":") {
12891329
lower := d.dir(s)
12901330
lp, err := os.Readlink(lower)
1291-
// if the link does not exist, we lost the symlinks during a sudden reboot.
1292-
// Let's go ahead and recreate those symlinks.
12931331
if err != nil {
1294-
if errors.Is(err, fs.ErrNotExist) {
1295-
logrus.Warnf("Can't read link %q because it does not exist. A storage corruption might have occurred, attempting to recreate the missing symlinks. It might be best wipe the storage to avoid further errors due to storage corruption.", lower)
1296-
if err := d.recreateSymlinks(); err != nil {
1297-
return nil, fmt.Errorf("recreating the missing symlinks: %w", err)
1298-
}
1299-
// let's call Readlink on lower again now that we have recreated the missing symlinks
1300-
lp, err = os.Readlink(lower)
1301-
if err != nil {
1302-
return nil, err
1303-
}
1304-
} else {
1305-
return nil, err
1332+
if errors.Is(err, syscall.EINVAL) {
1333+
// Not a symlink: layer ID, append /diff.
1334+
lowersArray = append(lowersArray, path.Join(lower, "diff"))
1335+
continue
13061336
}
1337+
return nil, err
13071338
}
13081339
lowersArray = append(lowersArray, path.Clean(d.dir(path.Join("link", lp))))
13091340
}
@@ -1415,112 +1446,6 @@ func (d *Driver) DeferredRemove(id string) (tempdir.CleanupTempDirFunc, error) {
14151446
return t.Cleanup, nil
14161447
}
14171448

1418-
// recreateSymlinks goes through the driver's home directory and checks if the diff directory
1419-
// under each layer has a symlink created for it under the linkDir. If the symlink does not
1420-
// exist, it creates them
1421-
func (d *Driver) recreateSymlinks() error {
1422-
// We have at most 3 corrective actions per layer, so 10 iterations is plenty.
1423-
const maxIterations = 10
1424-
1425-
// List all the directories under the home directory
1426-
dirs, err := os.ReadDir(d.home)
1427-
if err != nil {
1428-
return fmt.Errorf("reading driver home directory %q: %w", d.home, err)
1429-
}
1430-
// This makes the link directory if it doesn't exist
1431-
if err := idtools.MkdirAllAndChown(path.Join(d.home, linkDir), 0o755, idtools.IDPair{UID: 0, GID: 0}); err != nil {
1432-
return err
1433-
}
1434-
// Keep looping as long as we take some corrective action in each iteration
1435-
var errs error
1436-
madeProgress := true
1437-
iterations := 0
1438-
for madeProgress {
1439-
errs = nil
1440-
madeProgress = false
1441-
// Check that for each layer, there's a link in "l" with the name in
1442-
// the layer's "link" file that points to the layer's "diff" directory.
1443-
for _, dir := range dirs {
1444-
// Skip over the linkDir, stagingDir, tempDirName and anything that is not a directory
1445-
if dir.Name() == linkDir || dir.Name() == stagingDir || dir.Name() == tempDirName || !dir.IsDir() {
1446-
continue
1447-
}
1448-
// Read the "link" file under each layer to get the name of the symlink
1449-
data, err := os.ReadFile(path.Join(d.dir(dir.Name()), "link"))
1450-
if err != nil {
1451-
errs = errors.Join(errs, fmt.Errorf("reading name of symlink for %q: %w", dir.Name(), err))
1452-
continue
1453-
}
1454-
linkPath := path.Join(d.home, linkDir, strings.Trim(string(data), "\n"))
1455-
// Check if the symlink exists, and if it doesn't, create it again with the
1456-
// name we got from the "link" file
1457-
err = fileutils.Lexists(linkPath)
1458-
if err != nil && errors.Is(err, fs.ErrNotExist) {
1459-
if err := os.Symlink(path.Join("..", dir.Name(), "diff"), linkPath); err != nil {
1460-
errs = errors.Join(errs, err)
1461-
continue
1462-
}
1463-
madeProgress = true
1464-
} else if err != nil {
1465-
errs = errors.Join(errs, err)
1466-
continue
1467-
}
1468-
}
1469-
1470-
// linkDirFullPath is the full path to the linkDir
1471-
linkDirFullPath := filepath.Join(d.home, "l")
1472-
// Now check if we somehow lost a "link" file, by making sure
1473-
// that each symlink we have corresponds to one.
1474-
links, err := os.ReadDir(linkDirFullPath)
1475-
if err != nil {
1476-
errs = errors.Join(errs, err)
1477-
continue
1478-
}
1479-
// Go through all of the symlinks in the "l" directory
1480-
for _, link := range links {
1481-
// Read the symlink's target, which should be "../$layer/diff"
1482-
target, err := os.Readlink(filepath.Join(linkDirFullPath, link.Name()))
1483-
if err != nil {
1484-
errs = errors.Join(errs, err)
1485-
continue
1486-
}
1487-
targetComponents := strings.Split(target, string(os.PathSeparator))
1488-
if len(targetComponents) != 3 || targetComponents[0] != ".." || targetComponents[2] != "diff" {
1489-
errs = errors.Join(errs, fmt.Errorf("link target of %q looks weird: %q", link, target))
1490-
// force the link to be recreated on the next pass
1491-
if err := os.Remove(filepath.Join(linkDirFullPath, link.Name())); err != nil {
1492-
if !errors.Is(err, fs.ErrNotExist) {
1493-
errs = errors.Join(errs, fmt.Errorf("removing link %q: %w", link, err))
1494-
} // else don’t report any error, but also don’t set madeProgress.
1495-
continue
1496-
}
1497-
madeProgress = true
1498-
continue
1499-
}
1500-
// Reconstruct the name of the target's link file and check that
1501-
// it has the basename of our symlink in it.
1502-
targetID := targetComponents[1]
1503-
linkFile := filepath.Join(d.dir(targetID), "link")
1504-
data, err := os.ReadFile(linkFile)
1505-
if err != nil || string(data) != link.Name() {
1506-
// NOTE: If two or more links point to the same target, we will update linkFile
1507-
// with every value of link.Name(), and set madeProgress = true every time.
1508-
if err := os.WriteFile(linkFile, []byte(link.Name()), 0o644); err != nil {
1509-
errs = errors.Join(errs, fmt.Errorf("correcting link for layer %s: %w", targetID, err))
1510-
continue
1511-
}
1512-
madeProgress = true
1513-
}
1514-
}
1515-
iterations++
1516-
if iterations >= maxIterations {
1517-
errs = errors.Join(errs, fmt.Errorf("reached %d iterations in overlay graph driver’s recreateSymlink, giving up", iterations))
1518-
break
1519-
}
1520-
}
1521-
return errs
1522-
}
1523-
15241449
// Get creates and mounts the required file system for the given id and returns the mount path.
15251450
func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
15261451
return d.get(id, false, options)
@@ -1615,9 +1540,15 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
16151540
readWrite = false
16161541
}
16171542

1618-
lowers, err := os.ReadFile(path.Join(dir, lowerFile))
1619-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
1620-
return "", err
1543+
lowers, err := os.ReadFile(path.Join(dir, lowerLayersFile))
1544+
if err != nil {
1545+
if !errors.Is(err, unix.ENOENT) {
1546+
return "", err
1547+
}
1548+
lowers, err = os.ReadFile(path.Join(dir, lowerFile))
1549+
if err != nil && !os.IsNotExist(err) {
1550+
return "", err
1551+
}
16211552
}
16221553
splitLowers := strings.Split(string(lowers), ":")
16231554
if len(splitLowers) > maxDepth {
@@ -1729,16 +1660,7 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
17291660
}
17301661
lower = ""
17311662
}
1732-
// if it is a "not found" error, that means the symlinks were lost in a sudden reboot
1733-
// so call the recreateSymlinks function to go through all the layer dirs and recreate
1734-
// the symlinks with the name from their respective "link" files
1735-
if lower == "" && errors.Is(err, fs.ErrNotExist) {
1736-
logrus.Warnf("Can't stat lower layer %q because it does not exist. Going through storage to recreate the missing symlinks.", newpath)
1737-
if err := d.recreateSymlinks(); err != nil {
1738-
return "", fmt.Errorf("recreating the missing symlinks: %w", err)
1739-
}
1740-
lower = newpath
1741-
} else if lower == "" {
1663+
if lower == "" {
17421664
return "", fmt.Errorf("can't stat lower layer %q: %w", newpath, err)
17431665
}
17441666
} else {
@@ -1751,7 +1673,12 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
17511673

17521674
linkContent, err := os.Readlink(lower)
17531675
if err != nil {
1754-
return "", err
1676+
if !errors.Is(err, syscall.EINVAL) {
1677+
return "", err
1678+
}
1679+
// Not a symlink: layer ID from lower-layers, append /diff.
1680+
lower = path.Join(lower, "diff")
1681+
linkContent = lower
17551682
}
17561683
lowerID := filepath.Base(filepath.Dir(linkContent))
17571684
composefsMount, err := maybeAddComposefsMount(lowerID, i+1, readWrite)

storage/tests/overlay-recreate.bats

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)