From cdd4833929f7592902c4635de380ebebac57e7a6 Mon Sep 17 00:00:00 2001 From: Ariel Miculas Date: Thu, 19 Oct 2023 14:18:32 +0300 Subject: [PATCH] feat: use squashfuse ready notifier if available When using squasfuse, check whether it supports -o notifypipe. If it does, then use that instead of our manual checking the mountpoint inode, which is less reliable. Co-Developed-by: Serge Hallyn Signed-off-by: Ariel Miculas --- go.mod | 2 +- pkg/squashfs/squashfs.go | 89 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index d27da772..078596f0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module stackerbuild.io/stacker go 1.20 require ( + github.com/Masterminds/semver/v3 v3.2.1 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/apex/log v1.9.0 github.com/apparentlymart/go-shquot v0.0.1 @@ -45,7 +46,6 @@ require ( github.com/DataDog/zstd v1.4.8 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect diff --git a/pkg/squashfs/squashfs.go b/pkg/squashfs/squashfs.go index d08ddd0c..86873869 100644 --- a/pkg/squashfs/squashfs.go +++ b/pkg/squashfs/squashfs.go @@ -15,6 +15,7 @@ import ( "syscall" "time" + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" "golang.org/x/sys/unix" "stackerbuild.io/stacker/pkg/log" @@ -205,6 +206,32 @@ func findSquashfusePath() string { return which("squashfuse") } +// sqfuseSupportsMountNotification - returns true if squashfuse supports mount +// notification, false otherwise +// sqfuse is the path to the squashfuse binary +func sqfuseSupportsMountNotification(sqfuse string) bool { + cmd := exec.Command(sqfuse) + + // `squashfuse` always returns an error... so we ignore it. + out, _ := cmd.CombinedOutput() + + first_line := strings.Split(string(out[:]), "\n")[0] + version := strings.Split(first_line, " ")[1] + v, err := semver.NewVersion(version) + if err != nil { + return false + } + // squashfuse notify mechanism was merged in 0.5.0 + constraint, err := semver.NewConstraint(">= 0.5.0") + if err != nil { + return false + } + if constraint.Check(v) { + return true + } + return false +} + var squashNotFound = errors.Errorf("squashfuse program not found") // squashFuse - mount squashFile to extractDir @@ -217,6 +244,22 @@ func squashFuse(squashFile, extractDir string) (*exec.Cmd, error) { return cmd, squashNotFound } + sqNotify := sqfuseSupportsMountNotification(sqfuse) + + notifyOpts := "" + notifyPath := "" + if sqNotify { + sockdir, err := os.MkdirTemp("", "sock") + if err != nil { + return cmd, err + } + notifyPath = filepath.Join(sockdir, "notifypipe") + if err := syscall.Mkfifo(notifyPath, 0640); err != nil { + return cmd, err + } + notifyOpts = "notify_pipe=" + notifyPath + } + // given extractDir of path/to/some/dir[/], log to path/to/some/.dir-squashfs.log extractDir = strings.TrimSuffix(extractDir, "/") @@ -240,7 +283,11 @@ func squashFuse(squashFile, extractDir string) (*exec.Cmd, error) { // It would be nice to only enable debug (or maybe to only log to file at all) // if 'stacker --debug', but we do not have access to that info here. // to debug squashfuse, use "allow_other,debug" - cmd = exec.Command(sqfuse, "-f", "-o", "allow_other,debug", squashFile, extractDir) + optionArgs := "allow_other,debug" + if notifyOpts != "" { + optionArgs += "," + notifyOpts + } + cmd = exec.Command(sqfuse, "-f", "-o", optionArgs, squashFile, extractDir) cmd.Stdin = nil cmd.Stdout = cmdOut cmd.Stderr = cmdOut @@ -260,9 +307,49 @@ func squashFuse(squashFile, extractDir string) (*exec.Cmd, error) { // c. a timeout (timeLimit) was hit startTime := time.Now() timeLimit := 30 * time.Second + alarmCh := make(chan struct{}) go func() { cmd.Wait() + close(alarmCh) }() + if sqNotify { + notifyCh := make(chan byte) + log.Infof("%s supports notify pipe, watching %q", sqfuse, notifyPath) + go func() { + f, err := os.Open(notifyPath) + if err != nil { + return + } + defer f.Close() + b1 := make([]byte, 1) + for { + n1, err := f.Read(b1) + if err != nil { + return + } + if err == nil && n1 >= 1 { + break + } + } + notifyCh <- b1[0] + }() + if err != nil { + return cmd, errors.Wrapf(err, "Failed reading %q", notifyPath) + } + + select { + case <-alarmCh: + cmd.Process.Kill() + return cmd, errors.Wrapf(err, "Gave up on squashFuse mount of %s with %s after %s", squashFile, sqfuse, timeLimit) + case ret := <-notifyCh: + if ret == 's' { + return cmd, nil + } else { + return cmd, errors.Errorf("squashfuse returned an error, check %s", logf) + } + } + } + log.Infof("%s does not support notify pipe", sqfuse) for count := 0; !fileChanged(fiPre, extractDir); count++ { if cmd.ProcessState != nil { // process exited, the Wait() call in the goroutine above