master
go 410 lines 11.4 KB
Raw
1 // FUSE filesystem for the read-only /ipfs mount. go-fuse only builds on linux, darwin, and freebsd.
2 //go:build (linux || darwin || freebsd) && !nofuse
3
4 package readonly
5
6 import (
7 "context"
8 "io"
9 "os"
10 "sync"
11 "syscall"
12 "time"
13
14 "github.com/hanwen/go-fuse/v2/fs"
15 "github.com/hanwen/go-fuse/v2/fuse"
16 "github.com/ipfs/boxo/files"
17 mdag "github.com/ipfs/boxo/ipld/merkledag"
18 ft "github.com/ipfs/boxo/ipld/unixfs"
19 uio "github.com/ipfs/boxo/ipld/unixfs/io"
20 "github.com/ipfs/boxo/path"
21 "github.com/ipfs/go-cid"
22 ipld "github.com/ipfs/go-ipld-format"
23 logging "github.com/ipfs/go-log/v2"
24 core "github.com/ipfs/kubo/core"
25 fusemnt "github.com/ipfs/kubo/fuse/mount"
26 cidlink "github.com/ipld/go-ipld-prime/linking/cid"
27 )
28
29 var log = logging.Logger("fuse/ipfs")
30
31 // /ipfs paths are immutable (content-addressed by CID), so the kernel
32 // can cache attributes and directory entries for as long as it wants.
33 // var (not const) because fs.Options needs a *time.Duration.
34 var immutableAttrCacheTime = 365 * 24 * time.Hour
35
36 // Root is the root object of the /ipfs filesystem tree.
37 type Root struct {
38 fs.Inode
39 ipfs *core.IpfsNode
40 repoPath string
41 }
42
43 // NewRoot constructs a new readonly root node.
44 func NewRoot(ipfs *core.IpfsNode) *Root {
45 return &Root{ipfs: ipfs, repoPath: ipfs.Repo.Path()}
46 }
47
48 // Statfs reports disk-space statistics for the underlying filesystem.
49 // macOS Finder checks free space before copying; without this it
50 // reports "not enough free space" because go-fuse returns zeroed stats.
51 func (r *Root) Statfs(_ context.Context, out *fuse.StatfsOut) syscall.Errno {
52 if r.repoPath == "" {
53 return 0
54 }
55 var s syscall.Statfs_t
56 if err := syscall.Statfs(r.repoPath, &s); err != nil {
57 return fs.ToErrno(err)
58 }
59 out.FromStatfsT(&s)
60 return 0
61 }
62
63 func (*Root) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
64 out.Attr.Mode = uint32(fusemnt.NamespaceRootMode.Perm())
65 out.SetTimeout(immutableAttrCacheTime)
66 return 0
67 }
68
69 func (r *Root) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
70 log.Debugf("Root Lookup: '%s'", name)
71 switch name {
72 case "mach_kernel", ".hidden", "._.":
73 return nil, syscall.ENOENT
74 }
75
76 p, err := path.NewPath("/ipfs/" + name)
77 if err != nil {
78 log.Debugf("fuse failed to parse path: %q: %s", name, err)
79 return nil, syscall.ENOENT
80 }
81
82 imPath, err := path.NewImmutablePath(p)
83 if err != nil {
84 log.Debugf("fuse failed to convert path: %q: %s", name, err)
85 return nil, syscall.ENOENT
86 }
87
88 nd, ndLnk, err := r.ipfs.UnixFSPathResolver.ResolvePath(ctx, imPath)
89 if err != nil {
90 return nil, syscall.ENOENT
91 }
92
93 cidLnk, ok := ndLnk.(cidlink.Link)
94 if !ok {
95 log.Debugf("non-cidlink returned from ResolvePath: %v", ndLnk)
96 return nil, syscall.ENOENT
97 }
98
99 blk, err := r.ipfs.Blockstore.Get(ctx, cidLnk.Cid)
100 if err != nil {
101 log.Debugf("fuse failed to retrieve block: %v: %s", cidLnk, err)
102 return nil, syscall.ENOENT
103 }
104
105 var fnd ipld.Node
106 switch cidLnk.Cid.Prefix().Codec {
107 case cid.DagProtobuf:
108 fnd, err = mdag.DecodeProtobuf(blk.RawData())
109 case cid.Raw:
110 fnd, err = mdag.RawNodeConverter(blk, nd)
111 default:
112 log.Error("fuse node was not a supported type")
113 return nil, syscall.ENOTSUP
114 }
115 if err != nil {
116 log.Errorf("could not decode block as protobuf or raw node: %s", err)
117 return nil, syscall.ENOENT
118 }
119
120 child := &Node{ipfs: r.ipfs, nd: fnd}
121 stable := stableAttrFor(child)
122
123 // Fill attrs in the lookup response so the kernel doesn't cache zeros.
124 child.fillAttr(&out.Attr)
125 out.SetEntryTimeout(immutableAttrCacheTime)
126 out.SetAttrTimeout(immutableAttrCacheTime)
127 return r.NewInode(ctx, child, stable), 0
128 }
129
130 // Readdir on the namespace root is not allowed (execute-only).
131 func (*Root) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
132 return nil, syscall.EPERM
133 }
134
135 // Node is the core object representing a filesystem tree node.
136 type Node struct {
137 fs.Inode
138 ipfs *core.IpfsNode
139 nd ipld.Node
140 cached *ft.FSNode
141 }
142
143 func (n *Node) loadData() error {
144 if pbnd, ok := n.nd.(*mdag.ProtoNode); ok {
145 fsn, err := ft.FSNodeFromBytes(pbnd.Data())
146 if err != nil {
147 return err
148 }
149 n.cached = fsn
150 }
151 return nil
152 }
153
154 func (n *Node) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
155 log.Debug("Node attr")
156 out.SetTimeout(immutableAttrCacheTime)
157 n.fillAttr(&out.Attr)
158 return 0
159 }
160
161 // Open creates a DagReader that is reused across sequential Read
162 // calls, avoiding re-traversal of the DAG from the root on each read.
163 func (n *Node) Open(ctx context.Context, _ uint32) (fs.FileHandle, uint32, syscall.Errno) {
164 r, err := uio.NewDagReader(ctx, n.nd, n.ipfs.DAG)
165 if err != nil {
166 return nil, 0, fusemnt.ReadErrno(err)
167 }
168 return &roFileHandle{r: r}, fuse.FOPEN_KEEP_CACHE, 0
169 }
170
171 // roFileHandle holds a DagReader for the lifetime of an open file.
172 // All methods are serialized by mu because the FUSE server dispatches
173 // each request in its own goroutine and the underlying DagReader is
174 // not safe for concurrent use.
175 type roFileHandle struct {
176 r uio.DagReader
177 mu sync.Mutex
178 }
179
180 // fillAttr populates a fuse.Attr from this node's UnixFS metadata.
181 // Used by both Getattr and Lookup (to fill EntryOut.Attr so the kernel
182 // doesn't cache zero values for the entry timeout duration).
183 //
184 // Blocks and Blksize are set on every entry because go-fuse's setBlocks
185 // otherwise auto-fills them from Size with a 4 KiB page-based fallback,
186 // which clobbers the UnixFS-derived values set below.
187 func (n *Node) fillAttr(a *fuse.Attr) {
188 a.Blksize = fusemnt.DefaultBlksize
189
190 if rawnd, ok := n.nd.(*mdag.RawNode); ok {
191 a.Mode = uint32(fusemnt.DefaultFileModeRO.Perm())
192 a.Size = uint64(len(rawnd.RawData()))
193 a.Blocks = fusemnt.SizeToStatBlocks(a.Size)
194 return
195 }
196
197 if n.cached == nil {
198 if err := n.loadData(); err != nil {
199 log.Errorf("readonly: loadData() failed: %s", err)
200 return
201 }
202 }
203
204 switch n.cached.Type() {
205 case ft.TDirectory, ft.THAMTShard:
206 a.Mode = uint32(fusemnt.DefaultDirModeRO.Perm())
207 // Nominal 1 block: du sums child leaves, so the directory's
208 // own st_blocks is not arithmetically meaningful, but some
209 // tools treat 0 as "unsupported" and skip the entry.
210 a.Blocks = 1
211 case ft.TFile:
212 a.Mode = uint32(fusemnt.DefaultFileModeRO.Perm())
213 a.Size = n.cached.FileSize()
214 a.Blocks = fusemnt.SizeToStatBlocks(a.Size)
215 case ft.TRaw:
216 a.Mode = uint32(fusemnt.DefaultFileModeRO.Perm())
217 a.Size = uint64(len(n.cached.Data()))
218 a.Blocks = fusemnt.SizeToStatBlocks(a.Size)
219 case ft.TSymlink:
220 a.Mode = uint32(fusemnt.SymlinkMode.Perm())
221 a.Size = uint64(len(n.cached.Data()))
222 a.Blocks = fusemnt.SizeToStatBlocks(a.Size)
223 default:
224 log.Errorf("invalid data type: %s", n.cached.Type())
225 return
226 }
227
228 // Use mode and mtime from UnixFS metadata when present.
229 if m := n.cached.Mode(); m != 0 {
230 a.Mode = files.ModePermsToUnixPerms(m)
231 }
232 if t := n.cached.ModTime(); !t.IsZero() {
233 a.SetTimes(nil, &t, nil)
234 }
235 }
236
237 func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
238 log.Debugf("Lookup '%s'", name)
239 link, _, err := uio.ResolveUnixfsOnce(ctx, n.ipfs.DAG, n.nd, []string{name})
240 switch err {
241 case os.ErrNotExist, mdag.ErrLinkNotFound:
242 return nil, syscall.ENOENT
243 case nil:
244 default:
245 log.Errorf("fuse lookup %q: %s", name, err)
246 return nil, syscall.EIO
247 }
248
249 nd, err := n.ipfs.DAG.Get(ctx, link.Cid)
250 if err != nil && !ipld.IsNotFound(err) {
251 log.Errorf("fuse lookup %q: %s", name, err)
252 return nil, syscall.EIO
253 }
254
255 child := &Node{ipfs: n.ipfs, nd: nd}
256 stable := stableAttrFor(child)
257
258 child.fillAttr(&out.Attr)
259 out.SetEntryTimeout(immutableAttrCacheTime)
260 out.SetAttrTimeout(immutableAttrCacheTime)
261 return n.NewInode(ctx, child, stable), 0
262 }
263
264 func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
265 log.Debug("Node ReadDir")
266 dir, err := uio.NewDirectoryFromNode(n.ipfs.DAG, n.nd)
267 if err != nil {
268 return nil, fusemnt.ReadErrno(err)
269 }
270
271 var entries []fuse.DirEntry
272 err = dir.ForEachLink(ctx, func(lnk *ipld.Link) error {
273 name := lnk.Name
274 if len(name) == 0 {
275 name = lnk.Cid.String()
276 }
277 nd, err := n.ipfs.DAG.Get(ctx, lnk.Cid)
278 if err != nil {
279 log.Warn("error fetching directory child node: ", err)
280 return err
281 }
282
283 var mode uint32
284 switch nd := nd.(type) {
285 case *mdag.RawNode:
286 // regular file (mode 0 = S_IFREG)
287 case *mdag.ProtoNode:
288 if fsn, err := ft.FSNodeFromBytes(nd.Data()); err != nil {
289 log.Warn("failed to unmarshal protonode data field:", err)
290 } else {
291 switch fsn.Type() {
292 case ft.TDirectory, ft.THAMTShard:
293 mode = syscall.S_IFDIR
294 case ft.TFile, ft.TRaw:
295 // regular file
296 case ft.TSymlink:
297 mode = syscall.S_IFLNK
298 case ft.TMetadata:
299 log.Error("metadata object in fuse should contain its wrapped type")
300 default:
301 log.Error("unrecognized protonode data type: ", fsn.Type())
302 }
303 }
304 }
305 entries = append(entries, fuse.DirEntry{Name: name, Mode: mode})
306 return nil
307 })
308 if err != nil {
309 return nil, fusemnt.ReadErrno(err)
310 }
311
312 return fs.NewListDirStream(entries), 0
313 }
314
315 func (n *Node) Listxattr(_ context.Context, dest []byte) (uint32, syscall.Errno) {
316 // Null-terminated list of attribute names.
317 data := []byte(fusemnt.XattrCID + "\x00")
318 if len(dest) == 0 {
319 return uint32(len(data)), 0
320 }
321 if len(dest) < len(data) {
322 return 0, syscall.ERANGE
323 }
324 return uint32(copy(dest, data)), 0
325 }
326
327 func (n *Node) Getxattr(_ context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
328 if attr == fusemnt.XattrCIDDeprecated {
329 log.Errorf("xattr %q is deprecated, use %q instead", fusemnt.XattrCIDDeprecated, fusemnt.XattrCID)
330 attr = fusemnt.XattrCID
331 }
332 if attr != fusemnt.XattrCID {
333 return 0, fs.ENOATTR
334 }
335 data := []byte(n.nd.Cid().String())
336 if len(dest) == 0 {
337 return uint32(len(data)), 0
338 }
339 if len(dest) < len(data) {
340 return 0, syscall.ERANGE
341 }
342 return uint32(copy(dest, data)), 0
343 }
344
345 func (n *Node) Readlink(_ context.Context) ([]byte, syscall.Errno) {
346 if n.cached == nil || n.cached.Type() != ft.TSymlink {
347 return nil, syscall.EINVAL
348 }
349 return n.cached.Data(), 0
350 }
351
352 func (fh *roFileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
353 fh.mu.Lock()
354 defer fh.mu.Unlock()
355
356 if _, err := fh.r.Seek(off, io.SeekStart); err != nil {
357 return nil, fusemnt.ReadErrno(err)
358 }
359 n, err := fh.r.CtxReadFull(ctx, dest)
360 switch err {
361 case nil, io.EOF, io.ErrUnexpectedEOF:
362 default:
363 return nil, fusemnt.ReadErrno(err)
364 }
365 return fuse.ReadResultData(dest[:n]), 0
366 }
367
368 func (fh *roFileHandle) Release(_ context.Context) syscall.Errno {
369 fh.mu.Lock()
370 defer fh.mu.Unlock()
371
372 return fs.ToErrno(fh.r.Close())
373 }
374
375 // stableAttrFor returns the StableAttr (file type bits) for a Node.
376 func stableAttrFor(n *Node) fs.StableAttr {
377 if _, ok := n.nd.(*mdag.RawNode); ok {
378 return fs.StableAttr{} // S_IFREG
379 }
380 if n.cached == nil {
381 _ = n.loadData()
382 }
383 if n.cached != nil {
384 switch n.cached.Type() {
385 case ft.TDirectory, ft.THAMTShard:
386 return fs.StableAttr{Mode: syscall.S_IFDIR}
387 case ft.TSymlink:
388 return fs.StableAttr{Mode: syscall.S_IFLNK}
389 }
390 }
391 return fs.StableAttr{} // S_IFREG
392 }
393
394 // Interface checks.
395 var (
396 _ fs.NodeGetattrer = (*Root)(nil)
397 _ fs.NodeLookuper = (*Root)(nil)
398 _ fs.NodeReaddirer = (*Root)(nil)
399 _ fs.NodeStatfser = (*Root)(nil)
400 _ fs.NodeGetattrer = (*Node)(nil)
401 _ fs.NodeLookuper = (*Node)(nil)
402 _ fs.NodeOpener = (*Node)(nil)
403 _ fs.NodeReaddirer = (*Node)(nil)
404 _ fs.NodeReadlinker = (*Node)(nil)
405 _ fs.NodeGetxattrer = (*Node)(nil)
406 _ fs.NodeListxattrer = (*Node)(nil)
407
408 _ fs.FileReader = (*roFileHandle)(nil)
409 _ fs.FileReleaser = (*roFileHandle)(nil)
410 )