Skip to content

Commit

Permalink
feat: use squashfuse ready notifier if available
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Ariel Miculas <[email protected]>
  • Loading branch information
ariel-miculas committed Oct 19, 2023
1 parent 565b032 commit cdd4833
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 2 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
89 changes: 88 additions & 1 deletion pkg/squashfs/squashfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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, "/")

Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit cdd4833

Please sign in to comment.