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 baf4d85
Showing 1 changed file with 88 additions and 1 deletion.
89 changes: 88 additions & 1 deletion pkg/squashfs/squashfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"golang.org/x/sys/unix"
"stackerbuild.io/stacker/pkg/log"
"stackerbuild.io/stacker/pkg/mount"
"github.com/Masterminds/semver/v3"
)

var checkZstdSupported sync.Once
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, fmt.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 baf4d85

Please sign in to comment.