Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/mounting squashfs extract #514

Merged
merged 3 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions pkg/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"stackerbuild.io/stacker/pkg/mount"
"stackerbuild.io/stacker/pkg/squashfs"
"stackerbuild.io/stacker/pkg/types"
)

Expand Down Expand Up @@ -168,23 +166,10 @@
for _, m := range ovl.Manifests {
manifest = m
}
cacheDir := path.Join(o.config.StackerDir, "layer-bases", "oci")
ociDir := path.Join(o.config.StackerDir, "layer-bases", "oci")

Check warning on line 169 in pkg/overlay/overlay.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/overlay.go#L169

Added line #L169 was not covered by tests
for _, layer := range manifest.Layers {
if !squashfs.IsSquashfsMediaType(layer.MediaType) {
continue
}
digest := layer.Digest
contents := overlayPath(o.config.RootFSDir, digest, "overlay")
mounted, err := mount.IsMountpoint(contents)
if err == nil && mounted {
// We have already mounted this atom
continue
}
if hasDirEntries(contents) {
// We have done an unsquashfs of this atom
continue
}
if err := unpackOne(cacheDir, contents, digest, true); err != nil {
err := unpackOne(layer, ociDir, overlayPath(o.config.RootFSDir, layer.Digest, "overlay"))
if err != nil {

Check warning on line 172 in pkg/overlay/overlay.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/overlay.go#L171-L172

Added lines #L171 - L172 were not covered by tests
return errors.Wrapf(err, "Failed mounting %#v", layer)
}
}
Expand Down
118 changes: 69 additions & 49 deletions pkg/overlay/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"path/filepath"
"runtime"
"strings"
"sync"
"time"

"github.com/klauspost/pgzip"
Expand All @@ -29,6 +30,8 @@
"stackerbuild.io/stacker/pkg/types"
)

var tarEx sync.Mutex

func safeOverlayName(d digest.Digest) string {
// dirs used in overlay lowerdir args can't have : in them, so lets
// sanitize it
Expand Down Expand Up @@ -57,36 +60,19 @@

pool := NewThreadPool(runtime.NumCPU())

for _, layer := range manifest.Layers {
digest := layer.Digest
contents := overlayPath(o.config.RootFSDir, digest, "overlay")
if squashfs.IsSquashfsMediaType(layer.MediaType) {
// don't really need to do this in parallel, but what
// the hell.
pool.Add(func(ctx context.Context) error {
return unpackOne(cacheDir, contents, digest, true)
})
} else {
switch layer.MediaType {
case ispec.MediaTypeImageLayer:
fallthrough
case ispec.MediaTypeImageLayerGzip:
// don't extract things that have already been
// extracted
if _, err := os.Stat(contents); err == nil {
continue
}

// TODO: when the umoci API grows support for uid
// shifting, we can use the fancier features of context
// cancelling in the thread pool...
pool.Add(func(ctx context.Context) error {
return unpackOne(cacheDir, contents, digest, false)
})
default:
return errors.Errorf("unknown media type %s", layer.MediaType)
}
seen := map[digest.Digest]bool{}
for _, curLayer := range manifest.Layers {
// avoid calling unpackOne twice for the same digest
if seen[curLayer.Digest] {
continue

Check warning on line 67 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L63-L67

Added lines #L63 - L67 were not covered by tests
}
seen[curLayer.Digest] = true

// copy layer to avoid race on pool access.
l := curLayer
pool.Add(func(ctx context.Context) error {
return unpackOne(l, cacheDir, overlayPath(o.config.RootFSDir, l.Digest, "overlay"))
})

Check warning on line 75 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L69-L75

Added lines #L69 - L75 were not covered by tests
}

pool.DoneAddingJobs()
Expand Down Expand Up @@ -154,6 +140,9 @@
return err
}

log.Debugf("new oci layer %s [%s] created from path %s as part of %s:%s",
desc.Digest, layerType, overlayPath(config.RootFSDir, theLayer.Digest), name, tag)

Check warning on line 145 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L143-L145

Added lines #L143 - L145 were not covered by tests
// slight hack, but this is much faster than a cp, and the
// layers are the same, just in different formats
err = os.Symlink(overlayPath(config.RootFSDir, theLayer.Digest), overlayPath(config.RootFSDir, desc.Digest))
Expand Down Expand Up @@ -232,7 +221,8 @@
return err
}
} else {
log.Debugf("converting between %v and %v", sourceLayerType, layerType)
log.Debugf("creating oci image %s (type=%s) by converting %s (type=%s)",
layerType.LayerName(name), layerType, sourceLayerType.LayerName(name), sourceLayerType)

Check warning on line 225 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L224-L225

Added lines #L224 - L225 were not covered by tests
err = ConvertAndOutput(o.config, cacheTag, name, layerType)
if err != nil {
return err
Expand Down Expand Up @@ -447,6 +437,7 @@
return false, errors.Wrapf(err, "couldn't make new layer overlay dir")
}

log.Debugf("renaming %s -> %s", dir, path.Join(target, "overlay"))

Check warning on line 440 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L440

Added line #L440 was not covered by tests
err = os.Rename(dir, path.Join(target, "overlay"))
if err != nil {
if !os.IsExist(err) {
Expand Down Expand Up @@ -477,6 +468,7 @@
for _, desc := range descs[1:] {
linkPath := overlayPath(config.RootFSDir, desc.Digest)

log.Debugf("link %s -> %s", linkPath, target)

Check warning on line 471 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L471

Added line #L471 was not covered by tests
err = os.Symlink(target, linkPath)
if err != nil {
// as above, this symlink may already exist; if it does, we can
Expand Down Expand Up @@ -548,7 +540,7 @@
mutators = append(mutators, mutator)
}

log.Debugf("Generating overlay_dirs layers")
log.Debugf("Generating overlay_dirs layers for %s", name)

Check warning on line 543 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L543

Added line #L543 was not covered by tests
mutated := false
for i, layerType := range layerTypes {
ods, ok := ovl.OverlayDirLayers[layerType]
Expand Down Expand Up @@ -655,29 +647,57 @@
return ovl.write(config, name)
}

func unpackOne(ociDir string, bundlePath string, digest digest.Digest, isSquashfs bool) error {
if isSquashfs {
return squashfs.ExtractSingleSquash(
path.Join(ociDir, "blobs", "sha256", digest.Encoded()),
bundlePath, "overlay")
// unpackOne - unpack a single layer (Descriptor) found in ociDir to extractDir
//
// The result of calling unpackOne is either error or the contents available
// at the provided extractDir. The extractDir should be either empty or
// fully populated with this layer.
func unpackOne(l ispec.Descriptor, ociDir string, extractDir string) error {
// population of a dir is not atomic, at least for tar extraction.
// As a result, we could hasDirEntries(extractDir) at the same time that
// something is un-populating that dir due to a failed extraction (like
// os.RemoveAll below).
// There needs to be a lock on the extract dir (scoped to the overlay storage backend).
// A sync.RWMutex would work well here since it is safe to check as long
// as no one is populating or unpopulating.
if hasDirEntries(extractDir) {
// the directory was already populated.
return nil

Check warning on line 665 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L655-L665

Added lines #L655 - L665 were not covered by tests
}

oci, err := umoci.OpenLayout(ociDir)
if err != nil {
return err
if squashfs.IsSquashfsMediaType(l.MediaType) {
return squashfs.ExtractSingleSquash(
path.Join(ociDir, "blobs", "sha256", l.Digest.Encoded()), extractDir)

Check warning on line 670 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L668-L670

Added lines #L668 - L670 were not covered by tests
}
defer oci.Close()
switch l.MediaType {
case ispec.MediaTypeImageLayer, ispec.MediaTypeImageLayerGzip:
tarEx.Lock()
defer tarEx.Unlock()

Check warning on line 675 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L672-L675

Added lines #L672 - L675 were not covered by tests

compressed, err := oci.GetBlob(context.Background(), digest)
if err != nil {
return err
}
defer compressed.Close()
oci, err := umoci.OpenLayout(ociDir)
if err != nil {
return err
}
defer oci.Close()

Check warning on line 681 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L677-L681

Added lines #L677 - L681 were not covered by tests

uncompressed, err := pgzip.NewReader(compressed)
if err != nil {
compressed, err := oci.GetBlob(context.Background(), l.Digest)
if err != nil {
return err
}
defer compressed.Close()

uncompressed, err := pgzip.NewReader(compressed)
if err != nil {
return err
}

Check warning on line 692 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L683-L692

Added lines #L683 - L692 were not covered by tests

err = layer.UnpackLayer(extractDir, uncompressed, nil)
if err != nil {
if rmErr := os.RemoveAll(extractDir); rmErr != nil {
log.Errorf("Failed to remove dir '%s' after failed extraction: %v", extractDir, rmErr)
}

Check warning on line 698 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L694-L698

Added lines #L694 - L698 were not covered by tests
}
return err
}

return layer.UnpackLayer(bundlePath, uncompressed, nil)
return errors.Errorf("unknown media type %s", l.MediaType)

Check warning on line 702 in pkg/overlay/pack.go

View check run for this annotation

Codecov / codecov/patch

pkg/overlay/pack.go#L702

Added line #L702 was not covered by tests
}
Loading
Loading