fix: detect mounts via /proc/self/mountinfo so stale FUSE works
Update PKGBUILD version / update-pkgver (push) Successful in 5s

mountinfo.Mounted lstats the path. When the sshfs link dies, every stat
on the mountpoint returns EIO, so -l filtered the dead mount out, mount
failed in MkdirAll, and -u failed before fusermount. Switch detection to
mountinfo.GetMounts (no stat) and add a fusermount -uz fallback so a
stale mount can actually be torn down.
This commit is contained in:
2026-05-04 10:08:13 +02:00
parent 8edddc5a28
commit e6a02e5bf7
3 changed files with 102 additions and 19 deletions
+40 -19
View File
@@ -167,9 +167,9 @@ func main() {
fmt.Println("Mount: ", mount)
}
chkmount, chkmount_err := mountinfo.Mounted(mount)
chkmount, chkmount_err := is_mounted_at(mount)
if chkmount_err != nil {
fmt.Fprintf(os.Stderr, "mountinfo.Mounted() failed with %s\n", chkmount_err)
fmt.Fprintf(os.Stderr, "is_mounted_at() failed with %s\n", chkmount_err)
os.Exit(5)
}
if !chkmount {
@@ -258,12 +258,28 @@ func verify_mount_dir(name string) (string, error) {
if err != nil {
return "", err
}
// If the path is already a mountpoint (possibly stale), skip MkdirAll —
// it would stat the target, which fails with EIO on stale FUSE mounts.
if ok, _ := is_mounted_at(mount); ok {
return mount, nil
}
if err := os.MkdirAll(mount, 0700); err != nil {
return "", fmt.Errorf("create mount dir %q: %w", mount, err)
}
return mount, nil
}
// is_mounted_at reports whether path is a mountpoint per /proc/self/mountinfo.
// Unlike mountinfo.Mounted, this does not stat the path, so stale FUSE mounts
// (whose targets return EIO on stat) are still detected.
func is_mounted_at(path string) (bool, error) {
mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(path))
if err != nil {
return false, err
}
return len(mounts) > 0, nil
}
func list_mounts(out io.Writer) error {
base, err := mount_base(false)
if err != nil {
@@ -272,24 +288,28 @@ func list_mounts(out io.Writer) error {
}
return err
}
entries, err := os.ReadDir(base)
mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(base))
if err != nil {
return fmt.Errorf("read base %q: %w", base, err)
return fmt.Errorf("read mountinfo: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
prefix := base + string(os.PathSeparator)
for _, m := range mounts {
rel := strings.TrimPrefix(m.Mountpoint, prefix)
if rel == "" || strings.Contains(rel, string(os.PathSeparator)) {
continue
}
path := filepath.Join(base, entry.Name())
ok, merr := mountinfo.Mounted(path)
if merr != nil || !ok {
continue
}
fmt.Fprintln(out, entry.Name())
fmt.Fprintln(out, rel)
}
return nil
}
func run_fusermount(flag, mount string) error {
cmd := exec.Command("fusermount", flag, mount)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func unmount_sshfs(alias string) error {
if err := validate_ssh_field("alias", alias, rxHostUser); err != nil {
return err
@@ -305,18 +325,19 @@ func unmount_sshfs(alias string) error {
if err != nil {
return err
}
ok, err := mountinfo.Mounted(mount)
ok, err := is_mounted_at(mount)
if err != nil {
return fmt.Errorf("mountinfo.Mounted(%q): %w", mount, err)
return fmt.Errorf("check mount %q: %w", mount, err)
}
if !ok {
return fmt.Errorf("not mounted: %s", alias)
}
cmd := exec.Command("fusermount", "-u", mount)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("fusermount -u %q: %w", mount, err)
if err := run_fusermount("-u", mount); err != nil {
// stale FUSE mounts often need lazy unmount; -u fails with
// "Transport endpoint is not connected" on those.
if err2 := run_fusermount("-uz", mount); err2 != nil {
return fmt.Errorf("fusermount -u %q: %w (lazy retry: %v)", mount, err, err2)
}
}
if err := os.Remove(mount); err != nil {
fmt.Fprintf(os.Stderr, "warning: remove empty mount dir %q: %v\n", mount, err)