File: node.go

package info (click to toggle)
rclone 1.60.1%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 34,832 kB
  • sloc: sh: 957; xml: 857; python: 655; javascript: 612; makefile: 269; ansic: 101; php: 74
file content (407 lines) | stat: -rw-r--r-- 13,094 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//go:build linux || (darwin && amd64)
// +build linux darwin,amd64

package mount2

import (
	"context"
	"os"
	"path"
	"syscall"

	fusefs "github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
	"github.com/rclone/rclone/cmd/mountlib"
	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/log"
	"github.com/rclone/rclone/vfs"
)

// Node represents a directory or file
type Node struct {
	fusefs.Inode
	node vfs.Node
	fsys *FS
}

// Node types must be InodeEmbedders
var _ fusefs.InodeEmbedder = (*Node)(nil)

// newNode creates a new fusefs.Node from a vfs Node
func newNode(fsys *FS, vfsNode vfs.Node) (node *Node) {
	// Check the vfsNode to see if it has a fuse Node cached
	// We must return the same fuse nodes for vfs Nodes
	node, ok := vfsNode.Sys().(*Node)
	if ok {
		return node
	}
	node = &Node{
		node: vfsNode,
		fsys: fsys,
	}
	// Cache the node for later
	vfsNode.SetSys(node)
	return node
}

// String used for pretty printing.
func (n *Node) String() string {
	return n.node.Path()
}

// lookup a Node in a directory
func (n *Node) lookupVfsNodeInDir(leaf string) (vfsNode vfs.Node, errno syscall.Errno) {
	dir, ok := n.node.(*vfs.Dir)
	if !ok {
		return nil, syscall.ENOTDIR
	}
	vfsNode, err := dir.Stat(leaf)
	return vfsNode, translateError(err)
}

// // lookup a Dir given a path
// func (n *Node) lookupDir(path string) (dir *vfs.Dir, code fuse.Status) {
// 	node, code := fsys.lookupVfsNodeInDir(path)
// 	if !code.Ok() {
// 		return nil, code
// 	}
// 	dir, ok := n.(*vfs.Dir)
// 	if !ok {
// 		return nil, fuse.ENOTDIR
// 	}
// 	return dir, fuse.OK
// }

// // lookup a parent Dir given a path returning the dir and the leaf
// func (n *Node) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, code fuse.Status) {
// 	parentDir, leaf := path.Split(filePath)
// 	dir, code = fsys.lookupDir(parentDir)
// 	return leaf, dir, code
// }

// Statfs implements statistics for the filesystem that holds this
// Inode. If not defined, the `out` argument will zeroed with an OK
// result.  This is because OSX filesystems must Statfs, or the mount
// will not work.
func (n *Node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
	defer log.Trace(n, "")("out=%+v", &out)
	out = new(fuse.StatfsOut)
	const blockSize = 4096
	const fsBlocks = (1 << 50) / blockSize
	out.Blocks = fsBlocks  // Total data blocks in file system.
	out.Bfree = fsBlocks   // Free blocks in file system.
	out.Bavail = fsBlocks  // Free blocks in file system if you're not root.
	out.Files = 1e9        // Total files in file system.
	out.Ffree = 1e9        // Free files in file system.
	out.Bsize = blockSize  // Block size
	out.NameLen = 255      // Maximum file name length?
	out.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
	mountlib.ClipBlocks(&out.Blocks)
	mountlib.ClipBlocks(&out.Bfree)
	mountlib.ClipBlocks(&out.Bavail)
	return 0
}

var _ = (fusefs.NodeStatfser)((*Node)(nil))

// Getattr reads attributes for an Inode. The library will ensure that
// Mode and Ino are set correctly. For files that are not opened with
// FOPEN_DIRECTIO, Size should be set so it can be read correctly.  If
// returning zeroed permissions, the default behavior is to change the
// mode of 0755 (directory) or 0644 (files). This can be switched off
// with the Options.NullPermissions setting. If blksize is unset, 4096
// is assumed, and the 'blocks' field is set accordingly.
func (n *Node) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
	n.fsys.setAttrOut(n.node, out)
	return 0
}

var _ = (fusefs.NodeGetattrer)((*Node)(nil))

// Setattr sets attributes for an Inode.
func (n *Node) Setattr(ctx context.Context, f fusefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
	defer log.Trace(n, "in=%v", in)("out=%#v, errno=%v", &out, &errno)
	var err error
	n.fsys.setAttrOut(n.node, out)
	size, ok := in.GetSize()
	if ok {
		err = n.node.Truncate(int64(size))
		if err != nil {
			return translateError(err)
		}
		out.Attr.Size = size
	}
	mtime, ok := in.GetMTime()
	if ok {
		err = n.node.SetModTime(mtime)
		if err != nil {
			return translateError(err)
		}
		out.Attr.Mtime = uint64(mtime.Unix())
		out.Attr.Mtimensec = uint32(mtime.Nanosecond())
	}
	return 0
}

var _ = (fusefs.NodeSetattrer)((*Node)(nil))

// Open opens an Inode (of regular file type) for reading. It
// is optional but recommended to return a FileHandle.
func (n *Node) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	defer log.Trace(n, "flags=%#o", flags)("errno=%v", &errno)
	// fuse flags are based off syscall flags as are os flags, so
	// should be compatible
	handle, err := n.node.Open(int(flags))
	if err != nil {
		return nil, 0, translateError(err)
	}
	// If size unknown then use direct io to read
	if entry := n.node.DirEntry(); entry != nil && entry.Size() < 0 {
		fuseFlags |= fuse.FOPEN_DIRECT_IO
	}
	return newFileHandle(handle, n.fsys), fuseFlags, 0
}

var _ = (fusefs.NodeOpener)((*Node)(nil))

// Lookup should find a direct child of a directory by the child's name.  If
// the entry does not exist, it should return ENOENT and optionally
// set a NegativeTimeout in `out`. If it does exist, it should return
// attribute data in `out` and return the Inode for the child. A new
// inode can be created using `Inode.NewInode`. The new Inode will be
// added to the FS tree automatically if the return status is OK.
//
// If a directory does not implement NodeLookuper, the library looks
// for an existing child with the given name.
//
// The input to a Lookup is {parent directory, name string}.
//
// Lookup, if successful, must return an *Inode. Once the Inode is
// returned to the kernel, the kernel can issue further operations,
// such as Open or Getxattr on that node.
//
// A successful Lookup also returns an EntryOut. Among others, this
// contains file attributes (mode, size, mtime, etc.).
//
// FUSE supports other operations that modify the namespace. For
// example, the Symlink, Create, Mknod, Link methods all create new
// children in directories. Hence, they also return *Inode and must
// populate their fuse.EntryOut arguments.
func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
	defer log.Trace(n, "name=%q", name)("inode=%v, attr=%v, errno=%v", &inode, &out, &errno)
	vfsNode, errno := n.lookupVfsNodeInDir(name)
	if errno != 0 {
		return nil, errno
	}
	newNode := newNode(n.fsys, vfsNode)

	// FIXME
	// out.SetEntryTimeout(dt time.Duration)
	// out.SetAttrTimeout(dt time.Duration)
	n.fsys.setEntryOut(vfsNode, out)

	return n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}), 0
}

var _ = (fusefs.NodeLookuper)((*Node)(nil))

// Opendir opens a directory Inode for reading its
// contents. The actual reading is driven from Readdir, so
// this method is just for performing sanity/permission
// checks. The default is to return success.
func (n *Node) Opendir(ctx context.Context) syscall.Errno {
	if !n.node.IsDir() {
		return syscall.ENOTDIR
	}
	return 0
}

var _ = (fusefs.NodeOpendirer)((*Node)(nil))

type dirStream struct {
	nodes []os.FileInfo
	i     int
}

// HasNext indicates if there are further entries. HasNext
// might be called on already closed streams.
func (ds *dirStream) HasNext() bool {
	return ds.i < len(ds.nodes)
}

// Next retrieves the next entry. It is only called if HasNext
// has previously returned true.  The Errno return may be used to
// indicate I/O errors
func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
	// defer log.Trace(nil, "")("de=%+v, errno=%v", &de, &errno)
	fi := ds.nodes[ds.i]
	de = fuse.DirEntry{
		// Mode is the file's mode. Only the high bits (e.g. S_IFDIR)
		// are considered.
		Mode: getMode(fi),

		// Name is the basename of the file in the directory.
		Name: path.Base(fi.Name()),

		// Ino is the inode number.
		Ino: 0, // FIXME
	}
	ds.i++
	return de, 0
}

// Close releases resources related to this directory
// stream.
func (ds *dirStream) Close() {
}

var _ fusefs.DirStream = (*dirStream)(nil)

// Readdir opens a stream of directory entries.
//
// Readdir essentially returns a list of strings, and it is allowed
// for Readdir to return different results from Lookup. For example,
// you can return nothing for Readdir ("ls my-fuse-mount" is empty),
// while still implementing Lookup ("ls my-fuse-mount/a-specific-file"
// shows a single file).
//
// If a directory does not implement NodeReaddirer, a list of
// currently known children from the tree is returned. This means that
// static in-memory file systems need not implement NodeReaddirer.
func (n *Node) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.Errno) {
	defer log.Trace(n, "")("ds=%v, errno=%v", &ds, &errno)
	if !n.node.IsDir() {
		return nil, syscall.ENOTDIR
	}
	fh, err := n.node.Open(os.O_RDONLY)
	if err != nil {
		return nil, translateError(err)
	}
	defer func() {
		closeErr := fh.Close()
		if errno == 0 && closeErr != nil {
			errno = translateError(closeErr)
		}
	}()
	items, err := fh.Readdir(-1)
	if err != nil {
		return nil, translateError(err)
	}
	return &dirStream{
		nodes: items,
	}, 0
}

var _ = (fusefs.NodeReaddirer)((*Node)(nil))

// Mkdir is similar to Lookup, but must create a directory entry and Inode.
// Default is to return EROFS.
func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
	defer log.Trace(name, "mode=0%o", mode)("inode=%v, errno=%v", &inode, &errno)
	dir, ok := n.node.(*vfs.Dir)
	if !ok {
		return nil, syscall.ENOTDIR
	}
	newDir, err := dir.Mkdir(name)
	if err != nil {
		return nil, translateError(err)
	}
	newNode := newNode(n.fsys, newDir)
	n.fsys.setEntryOut(newNode.node, out)
	newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
	return newInode, 0
}

var _ = (fusefs.NodeMkdirer)((*Node)(nil))

// Create is similar to Lookup, but should create a new
// child. It typically also returns a FileHandle as a
// reference for future reads/writes.
// Default is to return EROFS.
func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	defer log.Trace(n, "name=%q, flags=%#o, mode=%#o", name, flags, mode)("node=%v, fh=%v, flags=%#o, errno=%v", &node, &fh, &fuseFlags, &errno)
	dir, ok := n.node.(*vfs.Dir)
	if !ok {
		return nil, nil, 0, syscall.ENOTDIR
	}
	// translate the fuse flags to os flags
	osFlags := int(flags) | os.O_CREATE
	file, err := dir.Create(name, osFlags)
	if err != nil {
		return nil, nil, 0, translateError(err)
	}
	handle, err := file.Open(osFlags)
	if err != nil {
		return nil, nil, 0, translateError(err)
	}
	fh = newFileHandle(handle, n.fsys)
	// FIXME
	// fh = &fusefs.WithFlags{
	// 	File: fh,
	// 	//FuseFlags: fuse.FOPEN_NONSEEKABLE,
	// 	OpenFlags: flags,
	// }

	// Find the created node
	vfsNode, errno := n.lookupVfsNodeInDir(name)
	if errno != 0 {
		return nil, nil, 0, errno
	}
	n.fsys.setEntryOut(vfsNode, out)
	newNode := newNode(n.fsys, vfsNode)
	fs.Debugf(nil, "attr=%#v", out.Attr)
	newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
	return newInode, fh, 0, 0
}

var _ = (fusefs.NodeCreater)((*Node)(nil))

// Unlink should remove a child from this directory.  If the
// return status is OK, the Inode is removed as child in the
// FS tree automatically. Default is to return EROFS.
func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
	defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
	vfsNode, errno := n.lookupVfsNodeInDir(name)
	if errno != 0 {
		return errno
	}
	return translateError(vfsNode.Remove())
}

var _ = (fusefs.NodeUnlinker)((*Node)(nil))

// Rmdir is like Unlink but for directories.
// Default is to return EROFS.
func (n *Node) Rmdir(ctx context.Context, name string) (errno syscall.Errno) {
	defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
	vfsNode, errno := n.lookupVfsNodeInDir(name)
	if errno != 0 {
		return errno
	}
	return translateError(vfsNode.Remove())
}

var _ = (fusefs.NodeRmdirer)((*Node)(nil))

// Rename should move a child from one directory to a different
// one. The change is effected in the FS tree if the return status is
// OK. Default is to return EROFS.
func (n *Node) Rename(ctx context.Context, oldName string, newParent fusefs.InodeEmbedder, newName string, flags uint32) (errno syscall.Errno) {
	defer log.Trace(n, "oldName=%q, newParent=%v, newName=%q", oldName, newParent, newName)("errno=%v", &errno)
	oldDir, ok := n.node.(*vfs.Dir)
	if !ok {
		return syscall.ENOTDIR
	}
	newParentNode, ok := newParent.(*Node)
	if !ok {
		fs.Errorf(n, "newParent was not a *Node")
		return syscall.EIO
	}
	newDir, ok := newParentNode.node.(*vfs.Dir)
	if !ok {
		return syscall.ENOTDIR
	}
	return translateError(oldDir.Rename(oldName, newName, newDir))
}

var _ = (fusefs.NodeRenamer)((*Node)(nil))