From f84fb2849bb64df667dd65c1912a0e691001ea06 Mon Sep 17 00:00:00 2001
From: Marcin Rataj <lidel@lidel.org>
Date: Thu, 15 May 2025 23:43:43 +0200
Subject: [PATCH] fix(fuse): ipns error handling and friendly errors (#10807)

* fix(fusei/ux): check if paths exist, print err

* fix(fuse): ipns 'could not resolve'

error type changed when code got extracted to boxo, but it was not
caught because of FUSE tests do not cover IPNS in online mode

Closes #8095
Closes #2167
Closes #3013

* docs: clarify opt-in
---
 cmd/ipfs/kubo/daemon.go | 29 +++++++++++++++++++++++++++++
 docs/config.md          |  5 ++++-
 fuse/ipns/ipns_unix.go  |  3 ++-
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/cmd/ipfs/kubo/daemon.go b/cmd/ipfs/kubo/daemon.go
index 5cdf3fa1d..94b633f79 100644
--- a/cmd/ipfs/kubo/daemon.go
+++ b/cmd/ipfs/kubo/daemon.go
@@ -1065,16 +1065,25 @@ func mountFuse(req *cmds.Request, cctx *oldcmds.Context) error {
 	if !found {
 		fsdir = cfg.Mounts.IPFS
 	}
+	if err := checkFusePath("Mounts.IPFS", fsdir); err != nil {
+		return err
+	}
 
 	nsdir, found := req.Options[ipnsMountKwd].(string)
 	if !found {
 		nsdir = cfg.Mounts.IPNS
 	}
+	if err := checkFusePath("Mounts.IPNS", nsdir); err != nil {
+		return err
+	}
 
 	mfsdir, found := req.Options[mfsMountKwd].(string)
 	if !found {
 		mfsdir = cfg.Mounts.MFS
 	}
+	if err := checkFusePath("Mounts.MFS", mfsdir); err != nil {
+		return err
+	}
 
 	node, err := cctx.ConstructNode()
 	if err != nil {
@@ -1091,6 +1100,26 @@ func mountFuse(req *cmds.Request, cctx *oldcmds.Context) error {
 	return nil
 }
 
+func checkFusePath(name, path string) error {
+	if path == "" {
+		return fmt.Errorf("%s path cannot be empty", name)
+	}
+
+	fileInfo, err := os.Stat(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("%s path (%q) does not exist: %w", name, path, err)
+		}
+		return fmt.Errorf("error while inspecting %s path (%q): %w", name, path, err)
+	}
+
+	if !fileInfo.IsDir() {
+		return fmt.Errorf("%s path (%q) is not a directory", name, path)
+	}
+
+	return nil
+}
+
 func maybeRunGC(req *cmds.Request, node *core.IpfsNode) (<-chan error, error) {
 	enableGC, _ := req.Options[enableGCKwd].(bool)
 	if !enableGC {
diff --git a/docs/config.md b/docs/config.md
index 4b8d66ac0..e40ac5887 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1373,7 +1373,10 @@ Default: `cache`
 ## `Mounts`
 
 > [!CAUTION]
-> **EXPERIMENTAL:** read about current limitations at [fuse.md](./fuse.md).
+> **EXPERIMENTAL:**
+> This feature is disabled by default, requires an explicit opt-in with  `ipfs mount` or `ipfs daemon --mount`.
+>
+> Read about current limitations at [fuse.md](./fuse.md).
 
 FUSE mount point configuration options.
 
diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go
index 23704cabd..ea2e75301 100644
--- a/fuse/ipns/ipns_unix.go
+++ b/fuse/ipns/ipns_unix.go
@@ -16,6 +16,7 @@ import (
 
 	dag "github.com/ipfs/boxo/ipld/merkledag"
 	ft "github.com/ipfs/boxo/ipld/unixfs"
+	"github.com/ipfs/boxo/namesys"
 	"github.com/ipfs/boxo/path"
 
 	fuse "bazil.org/fuse"
@@ -95,7 +96,7 @@ func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key) (*mfs.Root
 	node, err := ipfs.ResolveNode(ctx, key.Path())
 	switch err {
 	case nil:
-	case iface.ErrResolveFailed:
+	case namesys.ErrResolveFailed:
 		node = ft.EmptyDirNode()
 	default:
 		log.Errorf("looking up %s: %s", key.Path(), err)
-- 
GitLab