From 49f9e67520bd66ccf39f7b595a016d9f214228e1 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 12 Jun 2026 22:21:32 +0200 Subject: [PATCH 1/2] mount: factor out mountpoint validation into helper --- cmd/restic/cmd_mount.go | 63 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 6e85901dc..34170047d 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -21,6 +21,7 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/global" "github.com/restic/restic/internal/ui" + "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/fuse" @@ -134,36 +135,9 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args } mountpoint := args[0] - - // Check the existence of the mount point at the earliest stage to - // prevent unnecessary computations while opening the repository. - stat, err := os.Stat(mountpoint) - if errors.Is(err, os.ErrNotExist) { - printer.P("Mountpoint %s doesn't exist", mountpoint) - return errors.Fatal("invalid mountpoint") - } else if !stat.IsDir() { - printer.P("Mountpoint %s is not a directory", mountpoint) - return errors.Fatal("invalid mountpoint") - } - - err = unix.Access(mountpoint, unix.W_OK|unix.X_OK) - if err != nil { - printer.P("Mountpoint %s is not writeable or not executable", mountpoint) - return errors.Fatal("inaccessible mountpoint") - } - - // Refuse to mount onto (or under, or over) the local repository directory. - // Doing so makes the FUSE server read its own backend files through the - // mount it just created, deadlocking the kernel (GH #5234). - loc, err := location.Parse(gopts.Backends, gopts.Repo) - if err != nil { + if err := validateMountpoint(mountpoint, printer, gopts); err != nil { return err } - if loc.Scheme == "local" { - if err := checkMountpointOverlap(loc.Config.(*local.Config).Path, mountpoint); err != nil { - return err - } - } debug.Log("start mount") defer debug.Log("finish mount") @@ -247,6 +221,39 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args return err } +func validateMountpoint(mountpoint string, printer progress.Printer, gopts global.Options) error { + // Check the existence of the mount point at the earliest stage to + // prevent unnecessary computations while opening the repository. + stat, err := os.Stat(mountpoint) + if errors.Is(err, os.ErrNotExist) { + printer.P("Mountpoint %s doesn't exist", mountpoint) + return errors.Fatal("invalid mountpoint") + } else if !stat.IsDir() { + printer.P("Mountpoint %s is not a directory", mountpoint) + return errors.Fatal("invalid mountpoint") + } + + err = unix.Access(mountpoint, unix.W_OK|unix.X_OK) + if err != nil { + printer.P("Mountpoint %s is not writeable or not executable", mountpoint) + return errors.Fatal("inaccessible mountpoint") + } + + // Refuse to mount onto (or under, or over) the local repository directory. + // Doing so makes the FUSE server read its own backend files through the + // mount it just created, deadlocking the kernel (GH #5234). + loc, err := location.Parse(gopts.Backends, gopts.Repo) + if err != nil { + return err + } + if loc.Scheme == "local" { + if err := checkMountpointOverlap(loc.Config.(*local.Config).Path, mountpoint); err != nil { + return err + } + } + return nil +} + // checkMountpointOverlap returns an error.Fatal if the local repository at // repoPath and the mountpoint overlap: equal paths, mountpoint nested inside // the repo, or the repo nested inside the mountpoint. Any overlap deadlocks From e94ec65efb1cf404f457a10b599ab862de95ca67 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 12 Jun 2026 22:30:53 +0200 Subject: [PATCH 2/2] mount: unify mountpoint validation error messages --- cmd/restic/cmd_mount.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 34170047d..cb88a70e2 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -21,7 +21,6 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/global" "github.com/restic/restic/internal/ui" - "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/fuse" @@ -135,7 +134,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args } mountpoint := args[0] - if err := validateMountpoint(mountpoint, printer, gopts); err != nil { + if err := validateMountpoint(mountpoint, gopts); err != nil { return err } @@ -221,22 +220,19 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args return err } -func validateMountpoint(mountpoint string, printer progress.Printer, gopts global.Options) error { +func validateMountpoint(mountpoint string, gopts global.Options) error { // Check the existence of the mount point at the earliest stage to // prevent unnecessary computations while opening the repository. stat, err := os.Stat(mountpoint) if errors.Is(err, os.ErrNotExist) { - printer.P("Mountpoint %s doesn't exist", mountpoint) - return errors.Fatal("invalid mountpoint") + return errors.Fatal(fmt.Sprintf("mountpoint %s does not exist", mountpoint)) } else if !stat.IsDir() { - printer.P("Mountpoint %s is not a directory", mountpoint) - return errors.Fatal("invalid mountpoint") + return errors.Fatal(fmt.Sprintf("mountpoint %s is not a directory", mountpoint)) } err = unix.Access(mountpoint, unix.W_OK|unix.X_OK) if err != nil { - printer.P("Mountpoint %s is not writeable or not executable", mountpoint) - return errors.Fatal("inaccessible mountpoint") + return errors.Fatal(fmt.Sprintf("mountpoint %s is not writeable or not executable", mountpoint)) } // Refuse to mount onto (or under, or over) the local repository directory.