Skip to content

Commit

Permalink
feat: Use kernel mount of squashfs if possible. (#361)
Browse files Browse the repository at this point in the history
Try to mount a squashfs image with a kernel mount.
This should be faster than squashfuse mount, but will only
work if we are host root.

Environment variable STACKER_ALLOW_SQUASHFS_KERNEL_MOUNTS can
control whether or not kernel mounts can be tried.
It defaults to 'true', but if it is set to 'false', then no
attempt will be made.

Signed-off-by: Scott Moser <[email protected]>
  • Loading branch information
Scott Moser authored Dec 15, 2022
1 parent 146621e commit dc0ada0
Showing 1 changed file with 58 additions and 0 deletions.
58 changes: 58 additions & 0 deletions squashfs/squashfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path"
"strings"
"sync"
"syscall"

"github.com/pkg/errors"
"golang.org/x/sys/unix"
Expand All @@ -20,6 +21,9 @@ import (
var checkZstdSupported sync.Once
var zstdIsSuspported bool

var tryKernelMountSquash bool = true
var kernelSquashMountFailed error = errors.New("kernel squash mount failed")

// ExcludePaths represents a list of paths to exclude in a squashfs listing.
// Users should do something like filepath.Walk() over the whole filesystem,
// calling AddExclude() or AddInclude() based on whether they want to include
Expand Down Expand Up @@ -162,12 +166,66 @@ func MakeSquashfs(tempdir string, rootfs string, eps *ExcludePaths, verity Verit
return blob, GenerateSquashfsMediaType(compression, verity), rootHash, nil
}

// maybeKernelSquashMount - try to mount squashfile with kernel mount
//
// if global tryKernelMountSquash is false, do not try
// if environment variable STACKER_ALLOW_SQUASHFS_KERNEL_MOUNTS is "false", do not try.
// try. If it fails, log message and set tryKernelMountSquash=false.
func maybeKernelSquashMount(squashFile, extractDir string) (bool, error) {
if !tryKernelMountSquash {
return false, nil
}

const strTrue, strFalse = "true", "false"
const envName = "STACKER_ALLOW_SQUASHFS_KERNEL_MOUNTS"
envVal := os.Getenv(envName)
if envVal == strFalse {
log.Debugf("Not trying kernel mounts per %s=%s", envName, envVal)
tryKernelMountSquash = false
return false, nil
} else if envVal != strTrue && envVal != "" {
return false, errors.Errorf("%s must be '%s' or '%s', found '%s'", envName, strTrue, strFalse, envVal)
}

ecmd := []string{"mount", "-tsquashfs", "-oloop,ro", squashFile, extractDir}
var output bytes.Buffer
cmd := exec.Command(ecmd[0], ecmd[1:]...)
cmd.Stdin = nil
cmd.Stdout = &output
cmd.Stderr = cmd.Stdout
err := cmd.Run()
if err == nil {
return true, nil
}
exitError, ok := err.(*exec.ExitError)
if !ok {
tryKernelMountSquash = false
return false, errors.Errorf("Unexpected error (no-rc), in exec (%v): %v", ecmd, err)
}

status, ok := exitError.Sys().(syscall.WaitStatus)
if !ok {
tryKernelMountSquash = false
return false, errors.Errorf("Unexpected error (no-status) in exec (%v): %v", ecmd, err)
}

// we can't really tell why the mount failed. mount(8) does not give a lot specific rc exits.
log.Debugf("maybeKernelSquashMount(%s) exited %d: %s\n", squashFile, status.ExitStatus(), output.String())
return false, kernelSquashMountFailed
}

func ExtractSingleSquash(squashFile string, extractDir string, storageType string) error {
err := os.MkdirAll(extractDir, 0755)
if err != nil {
return err
}

if mounted, err := maybeKernelSquashMount(squashFile, extractDir); err == nil && mounted {
return nil
} else if err != kernelSquashMountFailed {
return err
}

findSqfusePath := func() string {
if p := which("squashfuse_ll"); p != "" {
return p
Expand Down

0 comments on commit dc0ada0

Please sign in to comment.