1
+//go:build (linux || darwin || freebsd || netbsd || openbsd) && !nofuse
2
+// +build linux darwin freebsd netbsd openbsd
3
+// +build !nofuse
4
+
5
+package mfs
6
+
7
+import (
8
+ "context"
9
+ "io"
10
+ "os"
11
+ "sync"
12
+ "syscall"
13
+ "time"
14
+
15
+ "bazil.org/fuse"
16
+ "bazil.org/fuse/fs"
17
+
18
+ dag "github.com/ipfs/boxo/ipld/merkledag"
19
+ ft "github.com/ipfs/boxo/ipld/unixfs"
20
+ "github.com/ipfs/boxo/mfs"
21
+ "github.com/ipfs/kubo/core"
22
+)
23
+
24
+const (
25
+ ipfsCIDXattr = "ipfs_cid"
26
+ mfsDirMode = os.ModeDir | 0755
27
+ mfsFileMode = 0644
28
+ blockSize = 512
29
+ dirSize = 8
30
+)
31
+
32
+// FUSE filesystem mounted at /mfs.
33
+type FileSystem struct {
34
+ root Dir
35
+}
36
+
37
+// Get filesystem root.
38
+func (fs *FileSystem) Root() (fs.Node, error) {
39
+ return &fs.root, nil
40
+}
41
+
42
+// FUSE Adapter for MFS directories.
43
+type Dir struct {
44
+ mfsDir *mfs.Directory
45
+}
46
+
47
+// Directory attributes (stat).
48
+func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error {
49
+ attr.Mode = mfsDirMode
50
+ attr.Size = dirSize * blockSize
51
+ attr.Blocks = dirSize
52
+ return nil
53
+}
54
+
55
+// Access files in a directory.
56
+func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
57
+ mfsNode, err := dir.mfsDir.Child(req.Name)
58
+ switch err {
59
+ case os.ErrNotExist:
60
+ return nil, syscall.Errno(syscall.ENOENT)
61
+ case nil:
62
+ default:
63
+ return nil, err
64
+ }
65
+
66
+ switch mfsNode.Type() {
67
+ case mfs.TDir:
68
+ result := Dir{
69
+ mfsDir: mfsNode.(*mfs.Directory),
70
+ }
71
+ return &result, nil
72
+ case mfs.TFile:
73
+ result := File{
74
+ mfsFile: mfsNode.(*mfs.File),
75
+ }
76
+ return &result, nil
77
+ }
78
+
79
+ return nil, syscall.Errno(syscall.ENOENT)
80
+}
81
+
82
+// List (ls) MFS directory.
83
+func (dir *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
84
+ var res []fuse.Dirent
85
+ nodes, err := dir.mfsDir.List(ctx)
86
+ if err != nil {
87
+ return nil, err
88
+ }
89
+
90
+ for _, node := range nodes {
91
+ nodeType := fuse.DT_File
92
+ if node.Type == 1 {
93
+ nodeType = fuse.DT_Dir
94
+ }
95
+ res = append(res, fuse.Dirent{
96
+ Type: nodeType,
97
+ Name: node.Name,
98
+ })
99
+ }
100
+ return res, nil
101
+}
102
+
103
+// Mkdir (mkdir) in MFS.
104
+func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
105
+ mfsDir, err := dir.mfsDir.Mkdir(req.Name)
106
+ if err != nil {
107
+ return nil, err
108
+ }
109
+ return &Dir{
110
+ mfsDir: mfsDir,
111
+ }, nil
112
+}
113
+
114
+// Remove (rm/rmdir) an MFS file.
115
+func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
116
+ // Check for empty directory.
117
+ if req.Dir {
118
+ targetNode, err := dir.mfsDir.Child(req.Name)
119
+ if err != nil {
120
+ return err
121
+ }
122
+ target := targetNode.(*mfs.Directory)
123
+
124
+ children, err := target.ListNames(ctx)
125
+ if err != nil {
126
+ return err
127
+ }
128
+ if len(children) > 0 {
129
+ return os.ErrExist
130
+ }
131
+ }
132
+ err := dir.mfsDir.Unlink(req.Name)
133
+ if err != nil {
134
+ return err
135
+ }
136
+ return dir.mfsDir.Flush()
137
+}
138
+
139
+// Move (mv) an MFS file.
140
+func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
141
+ file, err := dir.mfsDir.Child(req.OldName)
142
+ if err != nil {
143
+ return err
144
+ }
145
+ node, err := file.GetNode()
146
+ if err != nil {
147
+ return err
148
+ }
149
+ targetDir := newDir.(*Dir)
150
+
151
+ // Remove file if exists
152
+ err = targetDir.mfsDir.Unlink(req.NewName)
153
+ if err != nil && err != os.ErrNotExist {
154
+ return err
155
+ }
156
+
157
+ err = targetDir.mfsDir.AddChild(req.NewName, node)
158
+ if err != nil {
159
+ return err
160
+ }
161
+
162
+ err = dir.mfsDir.Unlink(req.OldName)
163
+ if err != nil {
164
+ return err
165
+ }
166
+
167
+ return dir.mfsDir.Flush()
168
+}
169
+
170
+// Create (touch) an MFS file.
171
+func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
172
+ node := dag.NodeWithData(ft.FilePBData(nil, 0))
173
+ if err := node.SetCidBuilder(dir.mfsDir.GetCidBuilder()); err != nil {
174
+ return nil, nil, err
175
+ }
176
+
177
+ if err := dir.mfsDir.AddChild(req.Name, node); err != nil {
178
+ return nil, nil, err
179
+ }
180
+
181
+ if err := dir.mfsDir.Flush(); err != nil {
182
+ return nil, nil, err
183
+ }
184
+
185
+ mfsNode, err := dir.mfsDir.Child(req.Name)
186
+ if err != nil {
187
+ return nil, nil, err
188
+ }
189
+ if err := mfsNode.SetModTime(time.Now()); err != nil {
190
+ return nil, nil, err
191
+ }
192
+
193
+ mfsFile := mfsNode.(*mfs.File)
194
+
195
+ file := File{
196
+ mfsFile: mfsFile,
197
+ }
198
+
199
+ // Read access flags and create a handler.
200
+ accessMode := req.Flags & fuse.OpenAccessModeMask
201
+ flags := mfs.Flags{
202
+ Read: accessMode == fuse.OpenReadOnly || accessMode == fuse.OpenReadWrite,
203
+ Write: accessMode == fuse.OpenWriteOnly || accessMode == fuse.OpenReadWrite,
204
+ Sync: req.Flags|fuse.OpenSync > 0,
205
+ }
206
+
207
+ fd, err := mfsFile.Open(flags)
208
+ if err != nil {
209
+ return nil, nil, err
210
+ }
211
+ handler := FileHandler{
212
+ mfsFD: fd,
213
+ }
214
+
215
+ return &file, &handler, nil
216
+}
217
+
218
+// List dir xattr.
219
+func (dir *Dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
220
+ resp.Append(ipfsCIDXattr)
221
+ return nil
222
+}
223
+
224
+// Get dir xattr.
225
+func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
226
+ switch req.Name {
227
+ case ipfsCIDXattr:
228
+ node, err := dir.mfsDir.GetNode()
229
+ if err != nil {
230
+ return err
231
+ }
232
+ resp.Xattr = []byte(node.Cid().String())
233
+ return nil
234
+ default:
235
+ return fuse.ErrNoXattr
236
+ }
237
+}
238
+
239
+// FUSE adapter for MFS files.
240
+type File struct {
241
+ mfsFile *mfs.File
242
+}
243
+
244
+// File attributes.
245
+func (file *File) Attr(ctx context.Context, attr *fuse.Attr) error {
246
+ size, _ := file.mfsFile.Size()
247
+
248
+ attr.Size = uint64(size)
249
+ if size%blockSize == 0 {
250
+ attr.Blocks = uint64(size / blockSize)
251
+ } else {
252
+ attr.Blocks = uint64(size/blockSize + 1)
253
+ }
254
+
255
+ mtime, _ := file.mfsFile.ModTime()
256
+ attr.Mtime = mtime
257
+
258
+ attr.Mode = mfsFileMode
259
+ return nil
260
+}
261
+
262
+// Open an MFS file.
263
+func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
264
+ accessMode := req.Flags & fuse.OpenAccessModeMask
265
+ flags := mfs.Flags{
266
+ Read: accessMode == fuse.OpenReadOnly || accessMode == fuse.OpenReadWrite,
267
+ Write: accessMode == fuse.OpenWriteOnly || accessMode == fuse.OpenReadWrite,
268
+ Sync: req.Flags|fuse.OpenSync > 0,
269
+ }
270
+ fd, err := file.mfsFile.Open(flags)
271
+ if err != nil {
272
+ return nil, err
273
+ }
274
+
275
+ if flags.Write {
276
+ if err := file.mfsFile.SetModTime(time.Now()); err != nil {
277
+ return nil, err
278
+ }
279
+ }
280
+
281
+ return &FileHandler{
282
+ mfsFD: fd,
283
+ }, nil
284
+}
285
+
286
+// Sync the file's contents to MFS.
287
+func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
288
+ return file.mfsFile.Sync()
289
+}
290
+
291
+// List file xattr.
292
+func (file *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
293
+ resp.Append(ipfsCIDXattr)
294
+ return nil
295
+}
296
+
297
+// Get file xattr.
298
+func (file *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
299
+ switch req.Name {
300
+ case ipfsCIDXattr:
301
+ node, err := file.mfsFile.GetNode()
302
+ if err != nil {
303
+ return err
304
+ }
305
+ resp.Xattr = []byte(node.Cid().String())
306
+ return nil
307
+ default:
308
+ return fuse.ErrNoXattr
309
+ }
310
+}
311
+
312
+// Wrapper for MFS's file descriptor that conforms to the FUSE fs.Handler
313
+// interface.
314
+type FileHandler struct {
315
+ mfsFD mfs.FileDescriptor
316
+ mu sync.Mutex
317
+}
318
+
319
+// Read a opened MFS file.
320
+func (fh *FileHandler) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
321
+ fh.mu.Lock()
322
+ defer fh.mu.Unlock()
323
+
324
+ _, err := fh.mfsFD.Seek(req.Offset, io.SeekStart)
325
+ if err != nil {
326
+ return err
327
+ }
328
+
329
+ buf := make([]byte, req.Size)
330
+ l, err := fh.mfsFD.Read(buf)
331
+
332
+ resp.Data = buf[:l]
333
+
334
+ switch err {
335
+ case nil, io.EOF, io.ErrUnexpectedEOF:
336
+ return nil
337
+ default:
338
+ return err
339
+ }
340
+}
341
+
342
+// Write writes to an opened MFS file.
343
+func (fh *FileHandler) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
344
+ fh.mu.Lock()
345
+ defer fh.mu.Unlock()
346
+
347
+ l, err := fh.mfsFD.WriteAt(req.Data, req.Offset)
348
+ if err != nil {
349
+ return err
350
+ }
351
+ resp.Size = l
352
+
353
+ return nil
354
+}
355
+
356
+// Flushes the file's buffer.
357
+func (fh *FileHandler) Flush(ctx context.Context, req *fuse.FlushRequest) error {
358
+ fh.mu.Lock()
359
+ defer fh.mu.Unlock()
360
+
361
+ return fh.mfsFD.Flush()
362
+}
363
+
364
+// Closes the file.
365
+func (fh *FileHandler) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
366
+ fh.mu.Lock()
367
+ defer fh.mu.Unlock()
368
+
369
+ return fh.mfsFD.Close()
370
+}
371
+
372
+// Create new filesystem.
373
+func NewFileSystem(ipfs *core.IpfsNode) fs.FS {
374
+ return &FileSystem{
375
+ root: Dir{
376
+ mfsDir: ipfs.FilesRoot.GetDirectory(),
377
+ },
378
+ }
379
+}
380
+
381
+// Check that our structs implement all the interfaces we want.
382
+type mfsDir interface {
383
+ fs.Node
384
+ fs.NodeGetxattrer
385
+ fs.NodeListxattrer
386
+ fs.HandleReadDirAller
387
+ fs.NodeRequestLookuper
388
+ fs.NodeMkdirer
389
+ fs.NodeRenamer
390
+ fs.NodeRemover
391
+ fs.NodeCreater
392
+}
393
+
394
+var _ mfsDir = (*Dir)(nil)
395
+
396
+type mfsFile interface {
397
+ fs.Node
398
+ fs.NodeGetxattrer
399
+ fs.NodeListxattrer
400
+ fs.NodeOpener
401
+ fs.NodeFsyncer
402
+}
403
+
404
+var _ mfsFile = (*File)(nil)
405
+
406
+type mfsHandler interface {
407
+ fs.Handle
408
+ fs.HandleReader
409
+ fs.HandleWriter
410
+ fs.HandleFlusher
411
+ fs.HandleReleaser
412
+}
413
+
414
+var _ mfsHandler = (*FileHandler)(nil)