master
go 197 lines 5.73 KB
Raw
1 //go:build (linux || darwin || freebsd) && !nofuse
2
3 // Package ipns implements a FUSE filesystem that interfaces with IPNS,
4 // the naming system for IPFS. Only names for which the node holds
5 // private keys are writable; all other names resolve to read-only
6 // symlinks pointing at the /ipfs mount.
7 package ipns
8
9 import (
10 "context"
11 "strings"
12 "syscall"
13
14 "github.com/hanwen/go-fuse/v2/fs"
15 "github.com/hanwen/go-fuse/v2/fuse"
16 dag "github.com/ipfs/boxo/ipld/merkledag"
17 ft "github.com/ipfs/boxo/ipld/unixfs"
18 mfs "github.com/ipfs/boxo/mfs"
19 "github.com/ipfs/boxo/namesys"
20 "github.com/ipfs/boxo/path"
21 cid "github.com/ipfs/go-cid"
22 logging "github.com/ipfs/go-log/v2"
23 "github.com/ipfs/kubo/config"
24 iface "github.com/ipfs/kubo/core/coreiface"
25 options "github.com/ipfs/kubo/core/coreiface/options"
26 fusemnt "github.com/ipfs/kubo/fuse/mount"
27 "github.com/ipfs/kubo/fuse/writable"
28 "github.com/ipfs/kubo/internal/fusemount"
29 )
30
31 var log = logging.Logger("fuse/ipns")
32
33 // Root is the root object of the /ipns filesystem tree.
34 type Root struct {
35 fs.Inode
36 Ipfs iface.CoreAPI
37 Keys map[string]iface.Key
38
39 // Used for symlinking into ipfs
40 IpfsRoot string
41 IpnsRoot string
42 LocalDirs map[string]*writable.Dir
43 Roots map[string]*mfs.Root
44
45 LocalLinks map[string]*Link
46 RepoPath string
47 }
48
49 func ipnsPubFunc(ipfs iface.CoreAPI, key iface.Key) mfs.PubFunc {
50 return func(ctx context.Context, c cid.Cid) error {
51 // Bypass the "cannot publish while IPNS is mounted" guard.
52 // Without this the mount's own publishes are blocked,
53 // causing silent data loss on daemon restart (issue #2168).
54 ctx = fusemount.ContextWithPublish(ctx)
55 _, err := ipfs.Name().Publish(ctx, path.FromCid(c), options.Name.Key(key.Name()), options.Name.AllowOffline(true))
56 return err
57 }
58 }
59
60 func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key, cfg *writable.Config, mfsOpts ...mfs.Option) (*mfs.Root, *writable.Dir, error) {
61 node, err := ipfs.ResolveNode(ctx, key.Path())
62 switch err {
63 case nil:
64 case namesys.ErrResolveFailed:
65 node = ft.EmptyDirNode()
66 default:
67 log.Errorf("looking up %s: %s", key.Path(), err)
68 return nil, nil, err
69 }
70
71 pbnode, ok := node.(*dag.ProtoNode)
72 if !ok {
73 return nil, nil, dag.ErrNotProtobuf
74 }
75
76 root, err := mfs.NewRoot(ctx, ipfs.Dag(), pbnode, ipnsPubFunc(ipfs, key), nil, mfsOpts...)
77 if err != nil {
78 return nil, nil, err
79 }
80
81 return root, writable.NewDir(root.GetDirectory(), cfg), nil
82 }
83
84 // CreateRoot creates the IPNS FUSE root with one writable directory per key.
85 func CreateRoot(ctx context.Context, ipfs iface.CoreAPI, keys map[string]iface.Key, ipfspath, ipnspath, repoPath string, mountsCfg config.Mounts, imp config.Import, mfsOpts ...mfs.Option) (*Root, error) {
86 cfg := &writable.Config{
87 StoreMtime: mountsCfg.StoreMtime.WithDefault(config.DefaultStoreMtime),
88 StoreMode: mountsCfg.StoreMode.WithDefault(config.DefaultStoreMode),
89 DAG: ipfs.Dag(),
90 RepoPath: repoPath,
91 Blksize: fusemnt.BlksizeFromChunker(imp.UnixFSChunker.WithDefault(config.DefaultUnixFSChunker)),
92 }
93
94 ldirs := make(map[string]*writable.Dir)
95 roots := make(map[string]*mfs.Root)
96 links := make(map[string]*Link)
97 for alias, k := range keys {
98 root, dir, err := loadRoot(ctx, ipfs, k, cfg, mfsOpts...)
99 if err != nil {
100 return nil, err
101 }
102
103 name := k.ID().String()
104 roots[name] = root
105 ldirs[name] = dir
106 links[alias] = &Link{Target: name}
107 }
108
109 return &Root{
110 Ipfs: ipfs,
111 IpfsRoot: ipfspath,
112 IpnsRoot: ipnspath,
113 Keys: keys,
114 LocalDirs: ldirs,
115 LocalLinks: links,
116 Roots: roots,
117 RepoPath: repoPath,
118 }, nil
119 }
120
121 // Getattr returns the root directory attributes.
122 func (r *Root) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
123 out.Attr.Mode = uint32(fusemnt.NamespaceRootMode.Perm())
124 return 0
125 }
126
127 // Statfs reports disk-space statistics for the underlying filesystem.
128 // macOS Finder checks free space before copying; without this it
129 // reports "not enough free space" because go-fuse returns zeroed stats.
130 func (r *Root) Statfs(_ context.Context, out *fuse.StatfsOut) syscall.Errno {
131 if r.RepoPath == "" {
132 return 0
133 }
134 var s syscall.Statfs_t
135 if err := syscall.Statfs(r.RepoPath, &s); err != nil {
136 return fs.ToErrno(err)
137 }
138 out.FromStatfsT(&s)
139 return 0
140 }
141
142 func (r *Root) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
143 switch name {
144 case "mach_kernel", ".hidden", "._.":
145 return nil, syscall.ENOENT
146 }
147
148 if lnk, ok := r.LocalLinks[name]; ok {
149 return r.NewInode(ctx, lnk, fs.StableAttr{Mode: syscall.S_IFLNK}), 0
150 }
151
152 if dir, ok := r.LocalDirs[name]; ok {
153 return r.NewInode(ctx, dir, fs.StableAttr{Mode: syscall.S_IFDIR}), 0
154 }
155
156 // Other links go through IPNS resolution and are symlinked into the /ipfs mount.
157 resolved, err := r.Ipfs.Name().Resolve(ctx, "/ipns/"+name)
158 if err != nil {
159 log.Warnf("ipns: namesys resolve error: %s", err)
160 return nil, syscall.ENOENT
161 }
162
163 if resolved.Namespace() != path.IPFSNamespace {
164 return nil, syscall.ENOENT
165 }
166
167 lnk := &Link{Target: r.IpfsRoot + "/" + strings.TrimPrefix(resolved.String(), "/ipfs/")}
168 return r.NewInode(ctx, lnk, fs.StableAttr{Mode: syscall.S_IFLNK}), 0
169 }
170
171 func (r *Root) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
172 entries := make([]fuse.DirEntry, 0, len(r.Keys)*2)
173 for alias, k := range r.Keys {
174 entries = append(entries,
175 fuse.DirEntry{Name: k.ID().String(), Mode: syscall.S_IFDIR},
176 fuse.DirEntry{Name: alias, Mode: syscall.S_IFLNK},
177 )
178 }
179 return fs.NewListDirStream(entries), 0
180 }
181
182 func (r *Root) Close() error {
183 for _, mr := range r.Roots {
184 if err := mr.Close(); err != nil {
185 return err
186 }
187 }
188 return nil
189 }
190
191 // Interface compliance checks for Root.
192 var (
193 _ fs.NodeGetattrer = (*Root)(nil)
194 _ fs.NodeLookuper = (*Root)(nil)
195 _ fs.NodeReaddirer = (*Root)(nil)
196 _ fs.NodeStatfser = (*Root)(nil)
197 )