Skip to content

Commit

Permalink
aufs,overlay: backfill intermediate directories when possible
Browse files Browse the repository at this point in the history
When extracting layer contents, if we find ourselves needing to
implicitly create a directory (due to its not being included in the
layer diff), try to give it the permissions, ownership, attributes, and
datestamp of the corresponding directory from the layer which would be
stacked below it.

When a layer omits any of the directories which contain items which that
layer adds or modifies, this should prevent the default values that we
would use from overriding those which correspond to the same directory
in a lower layer, which could later be mistaken as an indication that
one or more of those was intentionally changed, forcing the directory to
be pulled up.

Signed-off-by: Nalin Dahyabhai <[email protected]>
  • Loading branch information
nalind committed Jul 6, 2023
1 parent dc79a0d commit 2a0c3c2
Show file tree
Hide file tree
Showing 7 changed files with 1,104 additions and 1 deletion.
12 changes: 12 additions & 0 deletions drivers/aufs/aufs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"time"

graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/drivers/unionbackfill"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/directory"
Expand All @@ -46,6 +47,7 @@ import (
mountpk "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/parsers"
"github.com/containers/storage/pkg/system"
"github.com/containers/storage/pkg/tarbackfill"
"github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -564,6 +566,16 @@ func (a *Driver) applyDiff(id string, idMappings *idtools.IDMappings, diff io.Re
if idMappings == nil {
idMappings = &idtools.IDMappings{}
}
parentDiffDirs, err := a.getParentLayerPaths(id)
if err != nil {
return err
}
if len(parentDiffDirs) > 0 {
backfiller := unionbackfill.NewBackfiller(idMappings, parentDiffDirs)
rc := tarbackfill.NewIOReaderWithBackfiller(diff, backfiller)
defer rc.Close()
diff = rc
}
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
UIDMaps: idMappings.UIDs(),
GIDMaps: idMappings.GIDs(),
Expand Down
15 changes: 14 additions & 1 deletion drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/drivers/overlayutils"
"github.com/containers/storage/drivers/quota"
"github.com/containers/storage/drivers/unionbackfill"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/directory"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/parsers"
"github.com/containers/storage/pkg/system"
"github.com/containers/storage/pkg/tarbackfill"
"github.com/containers/storage/pkg/unshare"
units "github.com/docker/go-units"
"github.com/hashicorp/go-multierror"
Expand Down Expand Up @@ -2116,7 +2118,18 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts)

logrus.Debugf("Applying tar in %s", applyDir)
// Overlay doesn't need the parent id to apply the diff
if err := untar(options.Diff, applyDir, &archive.TarOptions{
diff := options.Diff
lowerDiffDirs, err := d.getLowerDiffPaths(id)
if err != nil {
return 0, err
}
if len(lowerDiffDirs) > 0 {
backfiller := unionbackfill.NewBackfiller(idMappings, lowerDiffDirs)
rc := tarbackfill.NewIOReaderWithBackfiller(diff, backfiller)
defer rc.Close()
diff = rc
}
if err := untar(diff, applyDir, &archive.TarOptions{
UIDMaps: idMappings.UIDs(),
GIDMaps: idMappings.GIDs(),
IgnoreChownErrors: d.options.ignoreChownErrors,
Expand Down
106 changes: 106 additions & 0 deletions drivers/unionbackfill/backfill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package unionbackfill

import (
"archive/tar"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/system"
)

// NewBackfiller supplies a backfiller whose Backfill method provides the
// ownership/permissions/attributes of a directory from a lower layer so that
// we don't have to create it in an upper layer using default values that will
// be mistaken for a reason that the directory was pulled up to that layer.
func NewBackfiller(idmap *idtools.IDMappings, lowerDiffDirs []string) *backfiller {
if idmap != nil {
uidMaps, gidMaps := idmap.UIDs(), idmap.GIDs()
if len(uidMaps) > 0 || len(gidMaps) > 0 {
idmap = idtools.NewIDMappingsFromMaps(append([]idtools.IDMap{}, uidMaps...), append([]idtools.IDMap{}, gidMaps...))
}
}
return &backfiller{idmap: idmap, lowerDiffDirs: append([]string{}, lowerDiffDirs...)}
}

type backfiller struct {
idmap *idtools.IDMappings
lowerDiffDirs []string
}

// Backfill supplies the ownership/permissions/attributes of a directory from a
// lower layer so that we don't have to create it in an upper layer using
// default values that will be mistaken for a reason that the directory was
// pulled up to that layer.
func (b *backfiller) Backfill(pathname string) (*tar.Header, error) {
for _, lowerDiffDir := range b.lowerDiffDirs {
candidate := filepath.Join(lowerDiffDir, pathname)
// if the asked-for path is in this lower, return a tar header for it
if st, err := os.Lstat(candidate); err == nil {
var linkTarget string
if st.Mode()&fs.ModeType == fs.ModeSymlink {
target, err := os.Readlink(candidate)
if err != nil {
return nil, err
}
linkTarget = target
}
hdr, err := tar.FileInfoHeader(st, linkTarget)
if err != nil {
return nil, err
}
// this is where we'd delete "opaque" from the header, if FileInfoHeader read xattrs
hdr.Name = strings.Trim(filepath.ToSlash(pathname), "/")
if st.Mode()&fs.ModeType == fs.ModeDir {
hdr.Name += "/"
}
if b.idmap != nil && !b.idmap.Empty() {
if uid, gid, err := b.idmap.ToContainer(idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}); err == nil {
hdr.Uid, hdr.Gid = uid, gid
}
}
return hdr, nil
}
// if the directory or any of its parents is marked opaque, we're done looking at lowers
p := strings.Trim(pathname, "/")
subpathname := ""
for {
dir, subdir := filepath.Split(p)
dir = strings.Trim(dir, "/")
if dir == p {
break
}
// kernel overlay style
xval, err := system.Lgetxattr(filepath.Join(lowerDiffDir, dir), archive.GetOverlayXattrName("opaque"))
if err == nil && len(xval) == 1 && xval[0] == 'y' {
return nil, nil
}
// aufs or fuse-overlayfs using aufs-like whiteouts
if _, err := os.Stat(filepath.Join(lowerDiffDir, dir, archive.WhiteoutOpaqueDir)); err == nil {
return nil, nil
}
// kernel overlay "redirect" - starting with the next lower layer, we'll need to look elsewhere
subpathname = strings.Trim(path.Join(subdir, subpathname), "/")
xval, err = system.Lgetxattr(filepath.Join(lowerDiffDir, dir), archive.GetOverlayXattrName("redirect"))
if err == nil && len(xval) > 0 {
subdir := string(xval)
if path.IsAbs(subdir) {
// path is relative to the root of the mount point
pathname = path.Join(subdir, subpathname)
} else {
// path is relative to the current directory
parent, _ := filepath.Split(dir)
parent = strings.Trim(parent, "/")
pathname = path.Join(parent, subdir, subpathname)
}
break
}
p = dir
}
}
return nil, nil
}
Loading

0 comments on commit 2a0c3c2

Please sign in to comment.