Initial commit
Proof-of-concept implementation. Bugs will occur.
This commit is contained in:
50
vendor/github.com/hanwen/go-fuse/v2/fs/README.md
generated
vendored
Normal file
50
vendor/github.com/hanwen/go-fuse/v2/fs/README.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
Objective
|
||||
=========
|
||||
|
||||
A high-performance FUSE API that minimizes pitfalls with writing
|
||||
correct filesystems.
|
||||
|
||||
Decisions
|
||||
=========
|
||||
|
||||
* Nodes contain references to their children. This is useful
|
||||
because most filesystems will need to construct tree-like
|
||||
structures.
|
||||
|
||||
* Nodes contain references to their parents. As a result, we can
|
||||
derive the path for each Inode, and there is no need for a
|
||||
separate PathFS.
|
||||
|
||||
* Nodes can be "persistent", meaning their lifetime is not under
|
||||
control of the kernel. This is useful for constructing FS trees
|
||||
in advance, rather than driven by LOOKUP.
|
||||
|
||||
* The NodeID (used for communicating with the kernel, not to be
|
||||
confused with the inode number reported by `ls -i`) is generated
|
||||
internally and immutable for an Inode. This avoids any races
|
||||
between LOOKUP, NOTIFY and FORGET.
|
||||
|
||||
* The mode of an Inode is defined on creation. Files cannot change
|
||||
type during their lifetime. This also prevents the common error
|
||||
of forgetting to return the filetype in Lookup/GetAttr.
|
||||
|
||||
* No global treelock, to ensure scalability.
|
||||
|
||||
* Support for hard links. libfuse doesn't support this in the
|
||||
high-level API. Extra care for race conditions is needed when
|
||||
looking up the same file through different paths.
|
||||
|
||||
* do not issue Notify{Entry,Delete} as part of
|
||||
AddChild/RmChild/MvChild: because NodeIDs are unique and
|
||||
immutable, there is no confusion about which nodes are
|
||||
invalidated, and the notification doesn't have to happen under
|
||||
lock.
|
||||
|
||||
* Directory reading uses the FileHandles as well, the API for read
|
||||
is one DirEntry at a time. FileHandles may implement seeking, and we
|
||||
call the Seek if we see Offsets change in the incoming request.
|
||||
|
||||
* Method names are based on syscall names. Where there is no
|
||||
syscall (eg. "open directory"), we bias towards writing
|
||||
everything together (Opendir)
|
||||
822
vendor/github.com/hanwen/go-fuse/v2/fs/api.go
generated
vendored
Normal file
822
vendor/github.com/hanwen/go-fuse/v2/fs/api.go
generated
vendored
Normal file
@@ -0,0 +1,822 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package fs provides infrastructure to build tree-organized filesystems.
|
||||
//
|
||||
// # Structure of a file system implementation
|
||||
//
|
||||
// To create a file system, you should first define types for the
|
||||
// nodes of the file system tree.
|
||||
//
|
||||
// type myNode struct {
|
||||
// fs.Inode
|
||||
// }
|
||||
//
|
||||
// // Node types must be InodeEmbedders
|
||||
// var _ = (fs.InodeEmbedder)((*myNode)(nil))
|
||||
//
|
||||
// // Node types should implement some file system operations, eg. Lookup
|
||||
// var _ = (fs.NodeLookuper)((*myNode)(nil))
|
||||
//
|
||||
// func (n *myNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
|
||||
// ops := myNode{}
|
||||
// out.Mode = 0755
|
||||
// out.Size = 42
|
||||
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
|
||||
// }
|
||||
//
|
||||
// The method names are inspired on the system call names, so we have
|
||||
// Listxattr rather than ListXAttr.
|
||||
//
|
||||
// the file system is mounted by calling mount on the root of the tree,
|
||||
//
|
||||
// server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{})
|
||||
// ..
|
||||
// // start serving the file system
|
||||
// server.Wait()
|
||||
//
|
||||
// # Error handling
|
||||
//
|
||||
// All error reporting must use the syscall.Errno type. This is an
|
||||
// integer with predefined error codes, where the value 0 (`OK`)
|
||||
// should be used to indicate success.
|
||||
//
|
||||
// # File system concepts
|
||||
//
|
||||
// The FUSE API is very similar to Linux' internal VFS API for
|
||||
// defining file systems in the kernel. It is therefore useful to
|
||||
// understand some terminology.
|
||||
//
|
||||
// File content: the raw bytes that we store inside regular files.
|
||||
//
|
||||
// Path: a /-separated string path that describes location of a node
|
||||
// in the file system tree. For example
|
||||
//
|
||||
// dir1/file
|
||||
//
|
||||
// describes path root → dir1 → file.
|
||||
//
|
||||
// There can be several paths leading from tree root to a particular node,
|
||||
// known as hard-linking, for example
|
||||
//
|
||||
// root
|
||||
// / \
|
||||
// dir1 dir2
|
||||
// \ /
|
||||
// file
|
||||
//
|
||||
// Inode: ("index node") points to the file content, and stores
|
||||
// metadata (size, timestamps) about a file or directory. Each
|
||||
// inode has a type (directory, symlink, regular file, etc.) and
|
||||
// an identity (a 64-bit number, unique to the file
|
||||
// system). Directories can have children.
|
||||
//
|
||||
// The inode in the kernel is represented in Go-FUSE as the Inode
|
||||
// type.
|
||||
//
|
||||
// While common OS APIs are phrased in terms of paths (strings), the
|
||||
// precise semantics of a file system are better described in terms of
|
||||
// Inodes. This allows us to specify what happens in corner cases,
|
||||
// such as writing data to deleted files.
|
||||
//
|
||||
// File descriptor: a handle returned to opening a file. File
|
||||
// descriptors always refer to a single inode.
|
||||
//
|
||||
// Dentry: a dirent maps (parent inode number, name string) tuple to
|
||||
// child inode, thus representing a parent/child relation (or the
|
||||
// absense thereof). Dentries do not have an equivalent type inside
|
||||
// Go-FUSE, but the result of Lookup operation essentially is a
|
||||
// dentry, which the kernel puts in a cache.
|
||||
//
|
||||
// # Kernel caching
|
||||
//
|
||||
// The kernel caches several pieces of information from the FUSE process:
|
||||
//
|
||||
// 1. File contents: enabled with the fuse.FOPEN_KEEP_CACHE return flag
|
||||
// in Open, manipulated with ReadCache and WriteCache, and invalidated
|
||||
// with Inode.NotifyContent
|
||||
//
|
||||
// 2. File Attributes (size, mtime, etc.): controlled with the
|
||||
// attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which
|
||||
// get be populated from Getattr and Lookup
|
||||
//
|
||||
// 3. Dentries (parent/child relations in the FS tree):
|
||||
// controlled with the timeout fields in fuse.EntryOut, and
|
||||
// invalidated with Inode.NotifyEntry and Inode.NotifyDelete.
|
||||
//
|
||||
// Without entry timeouts, every operation on file "a/b/c"
|
||||
// must first do lookups for "a", "a/b" and "a/b/c", which is
|
||||
// expensive because of context switches between the kernel and the
|
||||
// FUSE process.
|
||||
//
|
||||
// Unsuccessful entry lookups can also be cached by setting an entry
|
||||
// timeout when Lookup returns ENOENT.
|
||||
//
|
||||
// The libfuse C library specifies 1 second timeouts for both
|
||||
// attribute and directory entries, but no timeout for negative
|
||||
// entries. by default. This can be achieve in go-fuse by setting
|
||||
// options on mount, eg.
|
||||
//
|
||||
// sec := time.Second
|
||||
// opts := fs.Options{
|
||||
// EntryTimeout: &sec,
|
||||
// AttrTimeout: &sec,
|
||||
// }
|
||||
//
|
||||
// # Interrupts
|
||||
//
|
||||
// If the process accessing a FUSE file system is interrupted, the
|
||||
// kernel sends an interrupt message, which cancels the context passed
|
||||
// to the NodeXxxxx methods. If the file system chooses to honor this
|
||||
// cancellation, the method must return [syscall.EINTR]. All unmasked
|
||||
// signals generate an interrupt. In particular, the SIGURG signal
|
||||
// (which the Go runtime uses for managing goroutine preemption) also
|
||||
// generates an interrupt.
|
||||
//
|
||||
// # Locking
|
||||
//
|
||||
// Locks for networked filesystems are supported through the suite of
|
||||
// Getlk, Setlk and Setlkw methods. They alllow locks on regions of
|
||||
// regular files.
|
||||
//
|
||||
// # Parallelism
|
||||
//
|
||||
// The VFS layer in the kernel is optimized to be highly parallel, and
|
||||
// this parallelism also affects FUSE file systems: many FUSE
|
||||
// operations can run in parallel, and this invites race
|
||||
// conditions. It is strongly recommended to test your FUSE file
|
||||
// system issuing file operations in parallel, and using the race
|
||||
// detector to weed out data races.
|
||||
//
|
||||
// # Deadlocks
|
||||
//
|
||||
// The Go runtime multiplexes Goroutines onto operating system
|
||||
// threads, and makes assumptions that some system calls do not
|
||||
// block. When accessing a file system from the same process that
|
||||
// serves the file system (e.g. in unittests), this can lead to
|
||||
// deadlocks, especially when GOMAXPROCS=1, when the Go runtime
|
||||
// assumes a system call does not block, but actually is served by the
|
||||
// Go-FUSE process.
|
||||
//
|
||||
// The following deadlocks are known:
|
||||
//
|
||||
// 1. Spawning a subprocess uses a fork/exec sequence: the process
|
||||
// forks itself into a parent and child. The parent waits for the
|
||||
// child to signal that the exec failed or succeeded, while the child
|
||||
// prepares for calling exec(). Any setup step in the child that
|
||||
// triggers a FUSE request can cause a deadlock.
|
||||
//
|
||||
// 1a. If the subprocess has a directory specified, the child will
|
||||
// chdir into that directory. This generates an ACCESS operation on
|
||||
// the directory.
|
||||
//
|
||||
// This deadlock can be avoided by disabling the ACCESS
|
||||
// operation: return syscall.ENOSYS in the Access implementation, and
|
||||
// ensure it is triggered called before initiating the subprocess.
|
||||
//
|
||||
// 1b. If the subprocess inherits files, the child process uses dup3()
|
||||
// to remap file descriptors. If the destination fd happens to be
|
||||
// backed by Go-FUSE, the dup3() call will implicitly close the fd,
|
||||
// generating a FLUSH operation, eg.
|
||||
//
|
||||
// f1, err := os.Open("/fusemnt/file1")
|
||||
// // f1.Fd() == 3
|
||||
// f2, err := os.Open("/fusemnt/file1")
|
||||
// // f2.Fd() == 4
|
||||
//
|
||||
// cmd := exec.Command("/bin/true")
|
||||
// cmd.ExtraFiles = []*os.File{f2}
|
||||
// // f2 (fd 4) is moved to fd 3. Deadlocks with GOMAXPROCS=1.
|
||||
// cmd.Start()
|
||||
//
|
||||
// This deadlock can be avoided by ensuring that file descriptors
|
||||
// pointing into FUSE mounts and file descriptors passed into
|
||||
// subprocesses do not overlap, e.g. inserting the following before
|
||||
// the above example:
|
||||
//
|
||||
// for {
|
||||
// f, _ := os.Open("/dev/null")
|
||||
// defer f.Close()
|
||||
// if f.Fd() > 3 {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The library tries to reserve fd 3, because FUSE mounts are created
|
||||
// by calling "fusermount" with an inherited file descriptor, but the
|
||||
// same problem may occur for other file descriptors.
|
||||
//
|
||||
// 1c. If the executable is on the FUSE mount. In this case, the child
|
||||
// calls exec, which reads the file to execute, which triggers an OPEN
|
||||
// opcode. This can be worked around by invoking the subprocess
|
||||
// through a wrapper, eg `bash -c file/on/fuse-mount`.
|
||||
//
|
||||
// 2. The Go runtime uses the epoll system call to understand which
|
||||
// goroutines can respond to I/O. The runtime assumes that epoll does
|
||||
// not block, but if files are on a FUSE filesystem, the kernel will
|
||||
// generate a POLL operation. To prevent this from happening, Go-FUSE
|
||||
// disables the POLL opcode on mount. To ensure this has happened, call
|
||||
// WaitMount.
|
||||
//
|
||||
// 3. Memory mapping a file served by FUSE. Accessing the mapped
|
||||
// memory generates a page fault, which blocks the OS thread running
|
||||
// the goroutine.
|
||||
//
|
||||
// # Dynamically discovered file systems
|
||||
//
|
||||
// File system data usually cannot fit all in RAM, so the kernel must
|
||||
// discover the file system dynamically: as you are entering and list
|
||||
// directory contents, the kernel asks the FUSE server about the files
|
||||
// and directories you are busy reading/writing, and forgets parts of
|
||||
// your file system when it is low on memory.
|
||||
//
|
||||
// The two important operations for dynamic file systems are:
|
||||
// 1. Lookup, part of the NodeLookuper interface for discovering
|
||||
// individual children of directories, and 2. Readdir, part of the
|
||||
// NodeReaddirer interface for listing the contents of a directory.
|
||||
//
|
||||
// # Static in-memory file systems
|
||||
//
|
||||
// For small, read-only file systems, getting the locking mechanics of
|
||||
// Lookup correct is tedious, so Go-FUSE provides a feature to
|
||||
// simplify building such file systems.
|
||||
//
|
||||
// Instead of discovering the FS tree on the fly, you can construct
|
||||
// the entire tree from an OnAdd method. Then, that in-memory tree
|
||||
// structure becomes the source of truth. This means that Go-FUSE must
|
||||
// remember Inodes even if the kernel is no longer interested in
|
||||
// them. This is done by instantiating "persistent" inodes from the
|
||||
// OnAdd method of the root node. See the ZipFS example for a
|
||||
// runnable example of how to do this.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// InodeEmbedder is an interface for structs that embed Inode.
|
||||
//
|
||||
// InodeEmbedder objects usually should implement some of the NodeXxxx
|
||||
// interfaces, to provide user-defined file system behaviors.
|
||||
//
|
||||
// In general, if an InodeEmbedder does not implement specific
|
||||
// filesystem methods, the filesystem will react as if it is a
|
||||
// read-only filesystem with a predefined tree structure.
|
||||
type InodeEmbedder interface {
|
||||
// inode is used internally to link Inode to a Node.
|
||||
//
|
||||
// See Inode() for the public API to retrieve an inode from Node.
|
||||
embed() *Inode
|
||||
|
||||
// EmbeddedInode returns a pointer to the embedded inode.
|
||||
EmbeddedInode() *Inode
|
||||
}
|
||||
|
||||
// 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.
|
||||
type NodeStatfser interface {
|
||||
Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
|
||||
}
|
||||
|
||||
// Access should return if the caller can access the file with the
|
||||
// given mode. This is used for two purposes: to determine if a user
|
||||
// may enter a directory, and to implement the access system
|
||||
// call. In the latter case, the context has data about the real
|
||||
// UID. For example, a root-SUID binary called by user susan gets the
|
||||
// UID and GID for susan here.
|
||||
//
|
||||
// If not defined, a default implementation will check traditional
|
||||
// unix permissions of the Getattr result agains the caller. If access
|
||||
// permissions must be obeyed precisely, the filesystem should return
|
||||
// permissions from GetAttr/Lookup, and set [Options.NullPermissions].
|
||||
// Without [Options.NullPermissions], a missing permission (mode =
|
||||
// 0000) is interpreted as 0755 for directories, and chdir is always
|
||||
// allowed.
|
||||
type NodeAccesser interface {
|
||||
Access(ctx context.Context, mask uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// 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. The 'f'
|
||||
// argument is provided for consistency, however, in practice the
|
||||
// kernel never sends a file handle, even if the Getattr call
|
||||
// originated from an fstat system call.
|
||||
type NodeGetattrer interface {
|
||||
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// SetAttr sets attributes for an Inode. Default is to return ENOTSUP.
|
||||
type NodeSetattrer interface {
|
||||
Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// OnAdd is called when this InodeEmbedder is initialized.
|
||||
type NodeOnAdder interface {
|
||||
OnAdd(ctx context.Context)
|
||||
}
|
||||
|
||||
// Getxattr should read data for the given attribute into
|
||||
// `dest` and return the number of bytes. If `dest` is too
|
||||
// small, it should return ERANGE and the size of the attribute.
|
||||
// If not defined, Getxattr will return ENOATTR.
|
||||
type NodeGetxattrer interface {
|
||||
Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno)
|
||||
}
|
||||
|
||||
// Setxattr should store data for the given attribute. See
|
||||
// setxattr(2) for information about flags.
|
||||
// If not defined, Setxattr will return ENOATTR.
|
||||
type NodeSetxattrer interface {
|
||||
Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Removexattr should delete the given attribute.
|
||||
// If not defined, Removexattr will return ENOATTR.
|
||||
type NodeRemovexattrer interface {
|
||||
Removexattr(ctx context.Context, attr string) syscall.Errno
|
||||
}
|
||||
|
||||
// Listxattr should read all attributes (null terminated) into
|
||||
// `dest`. If the `dest` buffer is too small, it should return ERANGE
|
||||
// and the correct size. If not defined, return an empty list and
|
||||
// success.
|
||||
type NodeListxattrer interface {
|
||||
Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)
|
||||
}
|
||||
|
||||
// Readlink reads the content of a symlink.
|
||||
type NodeReadlinker interface {
|
||||
Readlink(ctx context.Context) ([]byte, syscall.Errno)
|
||||
}
|
||||
|
||||
// Open opens an Inode (of regular file type) for reading. It
|
||||
// is optional but recommended to return a FileHandle.
|
||||
type NodeOpener interface {
|
||||
Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Reads data from a file. The data should be returned as
|
||||
// ReadResult, which may be constructed from the incoming
|
||||
// `dest` buffer. If the file was opened without FileHandle,
|
||||
// the FileHandle argument here is nil. The default
|
||||
// implementation forwards to the FileHandle.
|
||||
type NodeReader interface {
|
||||
Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
|
||||
}
|
||||
|
||||
// Writes the data into the file handle at given offset. After
|
||||
// returning, the data will be reused and may not referenced.
|
||||
// The default implementation forwards to the FileHandle.
|
||||
type NodeWriter interface {
|
||||
Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Fsync is a signal to ensure writes to the Inode are flushed
|
||||
// to stable storage.
|
||||
type NodeFsyncer interface {
|
||||
Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Flush is called for the close(2) call on a file descriptor. In case
|
||||
// of a descriptor that was duplicated using dup(2), it may be called
|
||||
// more than once for the same FileHandle. The default implementation
|
||||
// forwards to the FileHandle, or if the handle does not support
|
||||
// FileFlusher, returns OK.
|
||||
type NodeFlusher interface {
|
||||
Flush(ctx context.Context, f FileHandle) syscall.Errno
|
||||
}
|
||||
|
||||
// This is called to before a FileHandle is forgotten. The
|
||||
// kernel ignores the return value of this method,
|
||||
// so any cleanup that requires specific synchronization or
|
||||
// could fail with I/O errors should happen in Flush instead.
|
||||
// The default implementation forwards to the FileHandle.
|
||||
type NodeReleaser interface {
|
||||
Release(ctx context.Context, f FileHandle) syscall.Errno
|
||||
|
||||
// TODO - what about ReleaseIn?
|
||||
}
|
||||
|
||||
// Allocate preallocates space for future writes, so they will
|
||||
// never encounter ESPACE.
|
||||
type NodeAllocater interface {
|
||||
Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// CopyFileRange copies data between sections of two files,
|
||||
// without the data having to pass through the calling process.
|
||||
type NodeCopyFileRanger interface {
|
||||
CopyFileRange(ctx context.Context, fhIn FileHandle,
|
||||
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
|
||||
len uint64, flags uint64) (uint32, syscall.Errno)
|
||||
|
||||
// Ugh. should have been called Copyfilerange
|
||||
}
|
||||
|
||||
type NodeStatxer interface {
|
||||
Statx(ctx context.Context, f FileHandle, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno
|
||||
}
|
||||
|
||||
// Lseek is used to implement holes: it should return the
|
||||
// first offset beyond `off` where there is data (SEEK_DATA)
|
||||
// or where there is a hole (SEEK_HOLE).
|
||||
type NodeLseeker interface {
|
||||
Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno)
|
||||
}
|
||||
|
||||
// Getlk returns locks that would conflict with the given input
|
||||
// lock. If no locks conflict, the output has type L_UNLCK. See
|
||||
// fcntl(2) for more information.
|
||||
// If not defined, returns ENOTSUP
|
||||
type NodeGetlker interface {
|
||||
Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
|
||||
}
|
||||
|
||||
// Setlk obtains a lock on a file, or fail if the lock could not
|
||||
// obtained. See fcntl(2) for more information. If not defined,
|
||||
// returns ENOTSUP
|
||||
type NodeSetlker interface {
|
||||
Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2)
|
||||
// for more information. If not defined, returns ENOTSUP
|
||||
type NodeSetlkwer interface {
|
||||
Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// Ioctl implements an ioctl on an open file.
|
||||
type NodeIoctler interface {
|
||||
Ioctl(ctx context.Context, f FileHandle, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// OnForget is called when the node becomes unreachable. This can
|
||||
// happen because the kernel issues a FORGET request,
|
||||
// ForgetPersistent() is called on the inode, the last child of the
|
||||
// directory disappears, or (for the root node) unmounting the file
|
||||
// system. Implementers must make sure that the inode cannot be
|
||||
// revived concurrently by a LOOKUP call. Modifying the tree using
|
||||
// RmChild and AddChild can also trigger a spurious OnForget; use
|
||||
// MvChild instead.
|
||||
type NodeOnForgetter interface {
|
||||
OnForget()
|
||||
}
|
||||
|
||||
// DirStream lists directory entries.
|
||||
type DirStream interface {
|
||||
// HasNext indicates if there are further entries. HasNext
|
||||
// might be called on already closed streams.
|
||||
HasNext() bool
|
||||
|
||||
// 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
|
||||
Next() (fuse.DirEntry, syscall.Errno)
|
||||
|
||||
// Close releases resources related to this directory
|
||||
// stream.
|
||||
Close()
|
||||
}
|
||||
|
||||
// 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.
|
||||
type NodeLookuper interface {
|
||||
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// NodeWrapChilder wraps a FS node implementation in another one. If
|
||||
// defined, it is called automatically from NewInode and
|
||||
// NewPersistentInode. Thus, existing file system implementations,
|
||||
// even from other packages, can be customized by wrapping them. The
|
||||
// following example is a loopback file system that forbids deletions.
|
||||
//
|
||||
// type NoDelete struct {
|
||||
// *fs.LoopbackNode
|
||||
// }
|
||||
// func (w *NoDelete) Unlink(ctx context.Context, name string) syscall.Errno {
|
||||
// return syscall.EPERM
|
||||
// }
|
||||
// func (w *NoDelete) WrapChild(ctx context.Context, ops fs.InodeEmbedder) fs.InodeEmbedder {
|
||||
// return &NoDelete{ops.(*LoopbackNode)}
|
||||
// }
|
||||
//
|
||||
// See also the LoopbackReuse example for a more practical
|
||||
// application.
|
||||
type NodeWrapChilder interface {
|
||||
WrapChild(ctx context.Context, ops InodeEmbedder) InodeEmbedder
|
||||
}
|
||||
|
||||
// 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.
|
||||
type NodeOpendirer interface {
|
||||
Opendir(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// Readdir opens a stream of directory entries.
|
||||
//
|
||||
// Readdir essentiallly 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). The DirStream returned must be deterministic;
|
||||
// a randomized result (e.g. due to map iteration) can lead to entries
|
||||
// disappearing if multiple processes read the same directory
|
||||
// concurrently.
|
||||
//
|
||||
// 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.
|
||||
type NodeReaddirer interface {
|
||||
Readdir(ctx context.Context) (DirStream, syscall.Errno)
|
||||
}
|
||||
|
||||
// Mkdir is similar to Lookup, but must create a directory entry and Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeMkdirer interface {
|
||||
Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// Mknod is similar to Lookup, but must create a device entry and Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeMknoder interface {
|
||||
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
|
||||
}
|
||||
|
||||
// Link is similar to Lookup, but must create a new link to an existing Inode.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeLinker interface {
|
||||
Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Symlink is similar to Lookup, but must create a new symbolic link.
|
||||
// Default is to return ENOTSUP.
|
||||
type NodeSymlinker interface {
|
||||
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// 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.
|
||||
type NodeCreater interface {
|
||||
Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// 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 success.
|
||||
type NodeUnlinker interface {
|
||||
Unlink(ctx context.Context, name string) syscall.Errno
|
||||
}
|
||||
|
||||
// Rmdir is like Unlink but for directories.
|
||||
// Default is to return success.
|
||||
type NodeRmdirer interface {
|
||||
Rmdir(ctx context.Context, name string) syscall.Errno
|
||||
}
|
||||
|
||||
// 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 ENOTSUP.
|
||||
type NodeRenamer interface {
|
||||
Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// FileHandle is a resource identifier for opened files. Usually, a
|
||||
// FileHandle should implement some of the FileXxxx interfaces.
|
||||
//
|
||||
// All of the FileXxxx operations can also be implemented at the
|
||||
// InodeEmbedder level, for example, one can implement NodeReader
|
||||
// instead of FileReader.
|
||||
//
|
||||
// FileHandles are useful in two cases: First, if the underlying
|
||||
// storage systems needs a handle for reading/writing. This is the
|
||||
// case with Unix system calls, which need a file descriptor (See also
|
||||
// the function `NewLoopbackFile`). Second, it is useful for
|
||||
// implementing files whose contents are not tied to an inode. For
|
||||
// example, a file like `/proc/interrupts` has no fixed content, but
|
||||
// changes on each open call. This means that each file handle must
|
||||
// have its own view of the content; this view can be tied to a
|
||||
// FileHandle. Files that have such dynamic content should return the
|
||||
// FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go
|
||||
// for an example.
|
||||
type FileHandle interface {
|
||||
}
|
||||
|
||||
// FilePassthroughFder is a file backed by a physical
|
||||
// file. PassthroughFd should return an open file descriptor (and
|
||||
// true), and the kernel will execute read/write operations directly
|
||||
// on the backing file, bypassing the FUSE process. This function will
|
||||
// be called once when processing the Create or Open operation, so
|
||||
// there is no concern about concurrent access to the Fd. If the
|
||||
// function returns false, passthrough will not be used for this file.
|
||||
type FilePassthroughFder interface {
|
||||
PassthroughFd() (int, bool)
|
||||
}
|
||||
|
||||
// See NodeReleaser.
|
||||
type FileReleaser interface {
|
||||
Release(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeGetattrer.
|
||||
type FileGetattrer interface {
|
||||
Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
type FileStatxer interface {
|
||||
Statx(ctx context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeReader.
|
||||
type FileReader interface {
|
||||
Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeWriter.
|
||||
type FileWriter interface {
|
||||
Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeGetlker.
|
||||
type FileGetlker interface {
|
||||
Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeSetlker.
|
||||
type FileSetlker interface {
|
||||
Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeSetlkwer.
|
||||
type FileSetlkwer interface {
|
||||
Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeLseeker.
|
||||
type FileLseeker interface {
|
||||
Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno)
|
||||
}
|
||||
|
||||
// See NodeFlusher.
|
||||
type FileFlusher interface {
|
||||
Flush(ctx context.Context) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeFsync.
|
||||
type FileFsyncer interface {
|
||||
Fsync(ctx context.Context, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeFsync.
|
||||
type FileSetattrer interface {
|
||||
Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeAllocater.
|
||||
type FileAllocater interface {
|
||||
Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// See NodeIoctler.
|
||||
type FileIoctler interface {
|
||||
Ioctl(ctx context.Context, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// Opens a directory. This supersedes NodeOpendirer, allowing to pass
|
||||
// back flags (eg. FOPEN_CACHE_DIR).
|
||||
type NodeOpendirHandler interface {
|
||||
OpendirHandle(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// FileReaddirenter is a directory that supports reading.
|
||||
type FileReaddirenter interface {
|
||||
// Read a single directory entry.
|
||||
Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno)
|
||||
}
|
||||
|
||||
// FileLookuper is a directory handle that supports lookup. If this is
|
||||
// defined, FileLookuper.Lookup on the directory is called for
|
||||
// READDIRPLUS calls, rather than NodeLookuper.Lookup. The name passed
|
||||
// in will always be the last name produced by Readdirent. If a child
|
||||
// with the given name already exists, that should be returned. In
|
||||
// case of directory seeks that straddle response boundaries,
|
||||
// Readdirent may be called without a subsequent Lookup call.
|
||||
type FileLookuper interface {
|
||||
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (child *Inode, errno syscall.Errno)
|
||||
}
|
||||
|
||||
// FileFsyncer is a directory that supports fsyncdir.
|
||||
type FileFsyncdirer interface {
|
||||
Fsyncdir(ctx context.Context, flags uint32) syscall.Errno
|
||||
}
|
||||
|
||||
// FileSeekdirer is directory that supports seeking. `off` is an
|
||||
// opaque uint64 value, where only the value 0 is reserved for the
|
||||
// start of the stream. (See https://lwn.net/Articles/544520/ for
|
||||
// background).
|
||||
type FileSeekdirer interface {
|
||||
Seekdir(ctx context.Context, off uint64) syscall.Errno
|
||||
}
|
||||
|
||||
// FileReleasedirer is a directory that supports a cleanup operation.
|
||||
type FileReleasedirer interface {
|
||||
Releasedir(ctx context.Context, releaseFlags uint32)
|
||||
}
|
||||
|
||||
// Options are options for the entire filesystem.
|
||||
type Options struct {
|
||||
// MountOptions contain the options for mounting the fuse server.
|
||||
fuse.MountOptions
|
||||
|
||||
// EntryTimeout, if non-nil, defines the overall entry timeout
|
||||
// for the file system. See [fuse.EntryOut] for more information.
|
||||
EntryTimeout *time.Duration
|
||||
|
||||
// AttrTimeout, if non-nil, defines the overall attribute
|
||||
// timeout for the file system. See [fuse.AttrOut] for more
|
||||
// information.
|
||||
AttrTimeout *time.Duration
|
||||
|
||||
// NegativeTimeout, if non-nil, defines the overall entry timeout
|
||||
// for failed lookups (fuse.ENOENT). See [fuse.EntryOut] for
|
||||
// more information.
|
||||
NegativeTimeout *time.Duration
|
||||
|
||||
// FirstAutomaticIno is start of the automatic inode numbers that are handed
|
||||
// out sequentially.
|
||||
//
|
||||
// If unset, the default is 2^63.
|
||||
FirstAutomaticIno uint64
|
||||
|
||||
// OnAdd, if non-nil, is an alternative way to specify the OnAdd
|
||||
// functionality of the root node.
|
||||
OnAdd func(ctx context.Context)
|
||||
|
||||
// NullPermissions, if set, leaves null file permissions
|
||||
// alone. Otherwise, they are set to 755 (dirs) or 644 (other
|
||||
// files.), which is necessary for doing a chdir into the FUSE
|
||||
// directories.
|
||||
NullPermissions bool
|
||||
|
||||
// UID, if nonzero, is the default UID to use instead of the
|
||||
// zero (zero) UID.
|
||||
UID uint32
|
||||
|
||||
// GID, if nonzero, is the default GID to use instead of the
|
||||
// zero (zero) GID.
|
||||
GID uint32
|
||||
|
||||
// ServerCallbacks are optional callbacks to stub out notification functions
|
||||
// for testing a filesystem without mounting it.
|
||||
ServerCallbacks ServerCallbacks
|
||||
|
||||
// Logger is a sink for diagnostic messages. Diagnostic
|
||||
// messages are printed under conditions where we cannot
|
||||
// return error, but want to signal something seems off
|
||||
// anyway. If unset, no messages are printed.
|
||||
//
|
||||
// This field shadows (and thus, is distinct) from
|
||||
// MountOptions.Logger.
|
||||
Logger *log.Logger
|
||||
|
||||
// RootStableAttr is an optional way to set e.g. Ino and/or Gen for
|
||||
// the root directory when calling fs.Mount(), Mode is ignored.
|
||||
RootStableAttr *StableAttr
|
||||
}
|
||||
1327
vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go
generated
vendored
Normal file
1327
vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go
generated
vendored
Normal file
60
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_linux.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// see rawBridge.setAttr
|
||||
func (b *rawBridge) setStatx(out *fuse.Statx) {
|
||||
if !b.options.NullPermissions && out.Mode&07777 == 0 {
|
||||
out.Mode |= 0644
|
||||
if out.Mode&syscall.S_IFDIR != 0 {
|
||||
out.Mode |= 0111
|
||||
}
|
||||
}
|
||||
if b.options.UID != 0 && out.Uid == 0 {
|
||||
out.Uid = b.options.UID
|
||||
}
|
||||
if b.options.GID != 0 && out.Gid == 0 {
|
||||
out.Gid = b.options.GID
|
||||
}
|
||||
setStatxBlocks(out)
|
||||
}
|
||||
|
||||
// see rawBridge.setAttrTimeout
|
||||
func (b *rawBridge) setStatxTimeout(out *fuse.StatxOut) {
|
||||
if b.options.AttrTimeout != nil && out.Timeout() == 0 {
|
||||
out.SetTimeout(*b.options.AttrTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *rawBridge) Statx(cancel <-chan struct{}, in *fuse.StatxIn, out *fuse.StatxOut) fuse.Status {
|
||||
n, fe := b.inode(in.NodeId, in.Fh)
|
||||
var fh FileHandle
|
||||
if fe != nil {
|
||||
fh = fe.file
|
||||
}
|
||||
|
||||
ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel}
|
||||
|
||||
errno := syscall.ENOSYS
|
||||
if sx, ok := n.ops.(NodeStatxer); ok {
|
||||
errno = sx.Statx(ctx, fh, in.SxFlags, in.SxMask, out)
|
||||
} else if fsx, ok := n.ops.(FileStatxer); ok {
|
||||
errno = fsx.Statx(ctx, in.SxFlags, in.SxMask, out)
|
||||
}
|
||||
|
||||
if errno == 0 {
|
||||
if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino {
|
||||
b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino)
|
||||
}
|
||||
out.Ino = n.stableAttr.Ino
|
||||
out.Mode = (out.Statx.Mode & 07777) | uint16(n.stableAttr.Mode)
|
||||
b.setStatx(&out.Statx)
|
||||
b.setStatxTimeout(out)
|
||||
}
|
||||
|
||||
return errnoToStatus(errno)
|
||||
}
|
||||
9
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_nonlinux.go
generated
vendored
Normal file
9
vendor/github.com/hanwen/go-fuse/v2/fs/bridge_nonlinux.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux
|
||||
|
||||
package fs
|
||||
|
||||
import "github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
func (b *rawBridge) Statx(cancel <-chan struct{}, in *fuse.StatxIn, out *fuse.StatxOut) fuse.Status {
|
||||
return fuse.ENOSYS
|
||||
}
|
||||
33
vendor/github.com/hanwen/go-fuse/v2/fs/constants.go
generated
vendored
Normal file
33
vendor/github.com/hanwen/go-fuse/v2/fs/constants.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/xattr"
|
||||
)
|
||||
|
||||
// OK is the Errno return value to indicate absense of errors.
|
||||
var OK = syscall.Errno(0)
|
||||
|
||||
// ToErrno exhumes the syscall.Errno error from wrapped error values.
|
||||
func ToErrno(err error) syscall.Errno {
|
||||
s := fuse.ToStatus(err)
|
||||
return syscall.Errno(s)
|
||||
}
|
||||
|
||||
// RENAME_EXCHANGE is a flag argument for renameat2()
|
||||
const RENAME_EXCHANGE = 0x2
|
||||
|
||||
// seek to the next data
|
||||
const _SEEK_DATA = 3
|
||||
|
||||
// seek to the next hole
|
||||
const _SEEK_HOLE = 4
|
||||
|
||||
// ENOATTR indicates that an extended attribute was not present.
|
||||
const ENOATTR = xattr.ENOATTR
|
||||
5
vendor/github.com/hanwen/go-fuse/v2/fs/default.go
generated
vendored
Normal file
5
vendor/github.com/hanwen/go-fuse/v2/fs/default.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
216
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go
generated
vendored
Normal file
216
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type dirArray struct {
|
||||
idx int
|
||||
entries []fuse.DirEntry
|
||||
}
|
||||
|
||||
func (a *dirArray) HasNext() bool {
|
||||
return a.idx < len(a.entries)
|
||||
}
|
||||
|
||||
func (a *dirArray) Next() (fuse.DirEntry, syscall.Errno) {
|
||||
e := a.entries[a.idx]
|
||||
a.idx++
|
||||
e.Off = uint64(a.idx)
|
||||
return e, 0
|
||||
}
|
||||
|
||||
func (a *dirArray) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
idx := int(off)
|
||||
if idx < 0 || idx > len(a.entries) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
a.idx = idx
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a *dirArray) Close() {
|
||||
|
||||
}
|
||||
|
||||
func (a *dirArray) Releasedir(ctx context.Context, releaseFlags uint32) {}
|
||||
|
||||
func (a *dirArray) Readdirent(ctx context.Context) (de *fuse.DirEntry, errno syscall.Errno) {
|
||||
if !a.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
e, errno := a.Next()
|
||||
return &e, errno
|
||||
}
|
||||
|
||||
// NewLoopbackDirStream opens a directory for reading as a DirStream
|
||||
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
|
||||
// TODO: should return concrete type.
|
||||
fd, err := syscall.Open(name, syscall.O_DIRECTORY|syscall.O_CLOEXEC, 0755)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
return NewLoopbackDirStreamFd(fd)
|
||||
}
|
||||
|
||||
// NewListDirStream wraps a slice of DirEntry as a DirStream.
|
||||
func NewListDirStream(list []fuse.DirEntry) DirStream {
|
||||
return &dirArray{entries: list}
|
||||
}
|
||||
|
||||
// implement FileReaddirenter/FileReleasedirer
|
||||
type dirStreamAsFile struct {
|
||||
creator func(context.Context) (DirStream, syscall.Errno)
|
||||
ds DirStream
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Releasedir(ctx context.Context, releaseFlags uint32) {
|
||||
if d.ds != nil {
|
||||
d.ds.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Readdirent(ctx context.Context) (de *fuse.DirEntry, errno syscall.Errno) {
|
||||
if d.ds == nil {
|
||||
d.ds, errno = d.creator(ctx)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
}
|
||||
if !d.ds.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
e, errno := d.ds.Next()
|
||||
return &e, errno
|
||||
}
|
||||
|
||||
func (d *dirStreamAsFile) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
if d.ds == nil {
|
||||
var errno syscall.Errno
|
||||
d.ds, errno = d.creator(ctx)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
if sd, ok := d.ds.(FileSeekdirer); ok {
|
||||
return sd.Seekdir(ctx, off)
|
||||
}
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
type loopbackDirStream struct {
|
||||
buf []byte
|
||||
|
||||
// Protects mutable members
|
||||
mu sync.Mutex
|
||||
|
||||
// mutable
|
||||
todo []byte
|
||||
todoErrno syscall.Errno
|
||||
fd int
|
||||
}
|
||||
|
||||
// NewLoopbackDirStreamFd reads the directory opened at file descriptor fd as
|
||||
// a DirStream
|
||||
func NewLoopbackDirStreamFd(fd int) (DirStream, syscall.Errno) {
|
||||
ds := &loopbackDirStream{
|
||||
buf: make([]byte, 4096),
|
||||
fd: fd,
|
||||
}
|
||||
ds.load()
|
||||
return ds, OK
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) Close() {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
if ds.fd != -1 {
|
||||
syscall.Close(ds.fd)
|
||||
ds.fd = -1
|
||||
}
|
||||
}
|
||||
|
||||
var _ = (FileReleasedirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Releasedir(ctx context.Context, flags uint32) {
|
||||
ds.Close()
|
||||
}
|
||||
|
||||
var _ = (FileSeekdirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Seekdir(ctx context.Context, off uint64) syscall.Errno {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
_, errno := unix.Seek(ds.fd, int64(off), unix.SEEK_SET)
|
||||
if errno != nil {
|
||||
return ToErrno(errno)
|
||||
}
|
||||
|
||||
ds.todo = nil
|
||||
ds.todoErrno = 0
|
||||
ds.load()
|
||||
return 0
|
||||
}
|
||||
|
||||
var _ = (FileFsyncdirer)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
return ToErrno(syscall.Fsync(ds.fd))
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) HasNext() bool {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
return len(ds.todo) > 0 || ds.todoErrno != 0
|
||||
}
|
||||
|
||||
var _ = (FileReaddirenter)((*loopbackDirStream)(nil))
|
||||
|
||||
func (ds *loopbackDirStream) Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) {
|
||||
if !ds.HasNext() {
|
||||
return nil, 0
|
||||
}
|
||||
de, errno := ds.Next()
|
||||
return &de, errno
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
if ds.todoErrno != 0 {
|
||||
return fuse.DirEntry{}, ds.todoErrno
|
||||
}
|
||||
var res fuse.DirEntry
|
||||
n := res.Parse(ds.todo)
|
||||
ds.todo = ds.todo[n:]
|
||||
if len(ds.todo) == 0 {
|
||||
ds.load()
|
||||
}
|
||||
return res, 0
|
||||
}
|
||||
|
||||
func (ds *loopbackDirStream) load() {
|
||||
if len(ds.todo) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := getdents(ds.fd, ds.buf)
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
ds.todo = ds.buf[:n]
|
||||
ds.todoErrno = ToErrno(err)
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getdents(fd int, buf []byte) (int, error) {
|
||||
return unix.Getdirentries(fd, buf, nil)
|
||||
}
|
||||
13
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_unix.go
generated
vendored
Normal file
13
vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_unix.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !darwin
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func getdents(fd int, buf []byte) (int, error) {
|
||||
return unix.Getdents(fd, buf)
|
||||
}
|
||||
277
vendor/github.com/hanwen/go-fuse/v2/fs/files.go
generated
vendored
Normal file
277
vendor/github.com/hanwen/go-fuse/v2/fs/files.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/fallocate"
|
||||
"github.com/hanwen/go-fuse/v2/internal/ioctl"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewLoopbackFile creates a FileHandle out of a file descriptor. All
|
||||
// operations are implemented. When using the Fd from a *os.File, call
|
||||
// syscall.Dup() on the fd, to avoid os.File's finalizer from closing
|
||||
// the file descriptor.
|
||||
func NewLoopbackFile(fd int) FileHandle {
|
||||
return &loopbackFile{fd: fd}
|
||||
}
|
||||
|
||||
type loopbackFile struct {
|
||||
mu sync.Mutex
|
||||
fd int
|
||||
}
|
||||
|
||||
var _ = (FileHandle)((*loopbackFile)(nil))
|
||||
var _ = (FileReleaser)((*loopbackFile)(nil))
|
||||
var _ = (FileGetattrer)((*loopbackFile)(nil))
|
||||
var _ = (FileReader)((*loopbackFile)(nil))
|
||||
var _ = (FileWriter)((*loopbackFile)(nil))
|
||||
var _ = (FileGetlker)((*loopbackFile)(nil))
|
||||
var _ = (FileSetlker)((*loopbackFile)(nil))
|
||||
var _ = (FileSetlkwer)((*loopbackFile)(nil))
|
||||
var _ = (FileLseeker)((*loopbackFile)(nil))
|
||||
var _ = (FileFlusher)((*loopbackFile)(nil))
|
||||
var _ = (FileFsyncer)((*loopbackFile)(nil))
|
||||
var _ = (FileSetattrer)((*loopbackFile)(nil))
|
||||
var _ = (FileAllocater)((*loopbackFile)(nil))
|
||||
var _ = (FilePassthroughFder)((*loopbackFile)(nil))
|
||||
|
||||
func (f *loopbackFile) PassthroughFd() (int, bool) {
|
||||
// This Fd is not accessed concurrently, but lock anyway for uniformity.
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.fd, true
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
|
||||
return r, OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
n, err := syscall.Pwrite(f.fd, data, off)
|
||||
return uint32(n), ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Release(ctx context.Context) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.fd != -1 {
|
||||
err := syscall.Close(f.fd)
|
||||
f.fd = -1
|
||||
return ToErrno(err)
|
||||
}
|
||||
return syscall.EBADF
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
// Since Flush() may be called for each dup'd fd, we don't
|
||||
// want to really close the file, we just want to flush. This
|
||||
// is achieved by closing a dup'd fd.
|
||||
newFd, err := syscall.Dup(f.fd)
|
||||
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
err = syscall.Close(newFd)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
r := ToErrno(syscall.Fsync(f.fd))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
const (
|
||||
_OFD_GETLK = 36
|
||||
_OFD_SETLK = 37
|
||||
_OFD_SETLKW = 38
|
||||
)
|
||||
|
||||
func (f *loopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
flk := syscall.Flock_t{}
|
||||
lk.ToFlockT(&flk)
|
||||
errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk))
|
||||
out.FromFlockT(&flk)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
|
||||
return f.setLock(ctx, owner, lk, flags, false)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) {
|
||||
return f.setLock(ctx, owner, lk, flags, true)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if (flags & fuse.FUSE_LK_FLOCK) != 0 {
|
||||
var op int
|
||||
switch lk.Typ {
|
||||
case syscall.F_RDLCK:
|
||||
op = syscall.LOCK_SH
|
||||
case syscall.F_WRLCK:
|
||||
op = syscall.LOCK_EX
|
||||
case syscall.F_UNLCK:
|
||||
op = syscall.LOCK_UN
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
}
|
||||
if !blocking {
|
||||
op |= syscall.LOCK_NB
|
||||
}
|
||||
return ToErrno(syscall.Flock(f.fd, op))
|
||||
} else {
|
||||
flk := syscall.Flock_t{}
|
||||
lk.ToFlockT(&flk)
|
||||
var op int
|
||||
if blocking {
|
||||
op = _OFD_SETLKW
|
||||
} else {
|
||||
op = _OFD_SETLK
|
||||
}
|
||||
return ToErrno(syscall.FcntlFlock(uintptr(f.fd), op, &flk))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
if errno := f.setAttr(ctx, in); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
return f.Getattr(ctx, out)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) fchmod(mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return ToErrno(syscall.Fchmod(f.fd, mode))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) fchown(uid, gid int) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return ToErrno(syscall.Fchown(f.fd, uid, gid))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) ftruncate(sz uint64) syscall.Errno {
|
||||
return ToErrno(syscall.Ftruncate(f.fd, int64(sz)))
|
||||
}
|
||||
|
||||
func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno {
|
||||
var errno syscall.Errno
|
||||
if mode, ok := in.GetMode(); ok {
|
||||
if errno := f.fchmod(mode); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
uid32, uOk := in.GetUID()
|
||||
gid32, gOk := in.GetGID()
|
||||
if uOk || gOk {
|
||||
uid := -1
|
||||
gid := -1
|
||||
|
||||
if uOk {
|
||||
uid = int(uid32)
|
||||
}
|
||||
if gOk {
|
||||
gid = int(gid32)
|
||||
}
|
||||
if errno := f.fchown(uid, gid); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
mtime, mok := in.GetMTime()
|
||||
atime, aok := in.GetATime()
|
||||
|
||||
if mok || aok {
|
||||
ap := &atime
|
||||
mp := &mtime
|
||||
if !aok {
|
||||
ap = nil
|
||||
}
|
||||
if !mok {
|
||||
mp = nil
|
||||
}
|
||||
errno = f.utimens(ap, mp)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
if errno := f.ftruncate(sz); errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Fstat(f.fd, &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
a.FromStat(&st)
|
||||
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
n, err := unix.Seek(f.fd, int64(off), int(whence))
|
||||
return uint64(n), ToErrno(err)
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
err := fallocate.Fallocate(f.fd, mode, int64(off), int64(sz))
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Ioctl(ctx context.Context, cmd uint32, arg uint64, input []byte, output []byte) (result int32, errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
argWord := uintptr(arg)
|
||||
ioc := ioctl.Command(cmd)
|
||||
if ioc.Read() {
|
||||
argWord = uintptr(unsafe.Pointer(&input[0]))
|
||||
} else if ioc.Write() {
|
||||
argWord = uintptr(unsafe.Pointer(&output[0]))
|
||||
}
|
||||
|
||||
res, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(f.fd), uintptr(cmd), argWord)
|
||||
return int32(res), errno
|
||||
}
|
||||
32
vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go
generated
vendored
Normal file
32
vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/utimens"
|
||||
)
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
}
|
||||
|
||||
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
|
||||
// We emulate using utimes() and extra Getattr() calls.
|
||||
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
|
||||
var attr fuse.AttrOut
|
||||
if a == nil || m == nil {
|
||||
errno := f.Getattr(context.Background(), &attr)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
tv := utimens.Fill(a, m, &attr.Attr)
|
||||
err := syscall.Futimes(int(f.fd), tv)
|
||||
return ToErrno(err)
|
||||
}
|
||||
6
vendor/github.com/hanwen/go-fuse/v2/fs/files_freebsd.go
generated
vendored
Normal file
6
vendor/github.com/hanwen/go-fuse/v2/fs/files_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package fs
|
||||
|
||||
import "github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
}
|
||||
46
vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go
generated
vendored
Normal file
46
vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func setBlocks(out *fuse.Attr) {
|
||||
if out.Blksize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.Blksize = 4096
|
||||
pages := (out.Size + 4095) / 4096
|
||||
out.Blocks = pages * 8
|
||||
}
|
||||
|
||||
func setStatxBlocks(out *fuse.Statx) {
|
||||
if out.Blksize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.Blksize = 4096
|
||||
pages := (out.Size + 4095) / 4096
|
||||
out.Blocks = pages * 8
|
||||
}
|
||||
|
||||
func (f *loopbackFile) Statx(ctx context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
st := unix.Statx_t{}
|
||||
err := unix.Statx(f.fd, "", int(flags), int(mask), &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatx(&st)
|
||||
|
||||
return OK
|
||||
}
|
||||
30
vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go
generated
vendored
Normal file
30
vendor/github.com/hanwen/go-fuse/v2/fs/files_unix.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//go:build !darwin
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// Utimens - file handle based version of loopbackFileSystem.Utimens()
|
||||
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
|
||||
var ts [2]syscall.Timespec
|
||||
ts[0] = fuse.UtimeToTimespec(a)
|
||||
ts[1] = fuse.UtimeToTimespec(m)
|
||||
err := futimens(int(f.fd), &ts)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
|
||||
// "flags" set to zero
|
||||
func futimens(fd int, times *[2]syscall.Timespec) (err error) {
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(fd), 0, uintptr(unsafe.Pointer(times)), uintptr(0), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = syscall.Errno(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
762
vendor/github.com/hanwen/go-fuse/v2/fs/inode.go
generated
vendored
Normal file
762
vendor/github.com/hanwen/go-fuse/v2/fs/inode.go
generated
vendored
Normal file
@@ -0,0 +1,762 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// StableAttr holds immutable attributes of a object in the filesystem.
|
||||
type StableAttr struct {
|
||||
// Each Inode has a type, which does not change over the
|
||||
// lifetime of the inode, for example fuse.S_IFDIR. The default (0)
|
||||
// is interpreted as S_IFREG (regular file).
|
||||
Mode uint32
|
||||
|
||||
// The inode number must be unique among the currently live
|
||||
// objects in the file system. It is used to communicate to
|
||||
// the kernel about this file object. The value uint64(-1)
|
||||
// is reserved. When using Ino==0, a unique, sequential
|
||||
// number is assigned (starting at 2^63 by default) on Inode creation.
|
||||
Ino uint64
|
||||
|
||||
// When reusing a previously used inode number for a new
|
||||
// object, the new object must have a different Gen
|
||||
// number. This is irrelevant if the FS is not exported over
|
||||
// NFS
|
||||
Gen uint64
|
||||
}
|
||||
|
||||
// Reserved returns if the StableAttr is using reserved Inode numbers.
|
||||
func (i *StableAttr) Reserved() bool {
|
||||
return i.Ino == ^uint64(0) // fuse.pollHackInode = ^uint64(0)
|
||||
}
|
||||
|
||||
// Inode is a node in VFS tree. Inodes are one-to-one mapped to
|
||||
// Operations instances, which is the extension interface for file
|
||||
// systems. One can create fully-formed trees of Inodes ahead of time
|
||||
// by creating "persistent" Inodes.
|
||||
//
|
||||
// The Inode struct contains a lock, so it should not be
|
||||
// copied. Inodes should be obtained by calling Inode.NewInode() or
|
||||
// Inode.NewPersistentInode().
|
||||
type Inode struct {
|
||||
stableAttr StableAttr
|
||||
|
||||
ops InodeEmbedder
|
||||
bridge *rawBridge
|
||||
|
||||
// The *Node ID* is an arbitrary uint64 identifier chosen by the FUSE library.
|
||||
// It is used the identify *nodes* (files/directories/symlinks/...) in the
|
||||
// communication between the FUSE library and the Linux kernel.
|
||||
nodeId uint64
|
||||
|
||||
// Following data is mutable.
|
||||
|
||||
// file handles.
|
||||
// protected by bridge.mu
|
||||
openFiles []uint32
|
||||
|
||||
// backing files, protected by bridge.mu
|
||||
backingIDRefcount int
|
||||
backingID int32
|
||||
backingFd int
|
||||
|
||||
// mu protects the following mutable fields. When locking
|
||||
// multiple Inodes, locks must be acquired using
|
||||
// lockNodes/unlockNodes
|
||||
mu sync.Mutex
|
||||
|
||||
// persistent indicates that this node should not be removed
|
||||
// from the tree, even if there are no live references. This
|
||||
// must be set on creation, and can only be changed to false
|
||||
// by calling removeRef.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
persistent bool
|
||||
|
||||
// changeCounter increments every time the mutable state
|
||||
// (lookupCount, persistent, children, parents) protected by
|
||||
// mu is modified.
|
||||
//
|
||||
// This is used in places where we have to relock inode into inode
|
||||
// group lock, and after locking the group we have to check if inode
|
||||
// did not changed, and if it changed - retry the operation.
|
||||
changeCounter uint32
|
||||
|
||||
// Number of kernel refs to this node.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
lookupCount uint64
|
||||
|
||||
// Children of this Inode.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
children inodeChildren
|
||||
|
||||
// Parents of this Inode. Can be more than one due to hard links.
|
||||
// When you change this, you MUST increment changeCounter.
|
||||
parents inodeParents
|
||||
}
|
||||
|
||||
func (n *Inode) IsDir() bool {
|
||||
return n.stableAttr.Mode&syscall.S_IFMT == syscall.S_IFDIR
|
||||
}
|
||||
|
||||
func (n *Inode) embed() *Inode {
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Inode) EmbeddedInode() *Inode {
|
||||
return n
|
||||
}
|
||||
|
||||
func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge, persistent bool, nodeId uint64) {
|
||||
n.ops = ops
|
||||
n.stableAttr = attr
|
||||
n.bridge = bridge
|
||||
n.persistent = persistent
|
||||
n.nodeId = nodeId
|
||||
if attr.Mode == fuse.S_IFDIR {
|
||||
n.children.init()
|
||||
}
|
||||
}
|
||||
|
||||
// Set node ID and mode in EntryOut
|
||||
func (n *Inode) setEntryOut(out *fuse.EntryOut) {
|
||||
out.NodeId = n.nodeId
|
||||
out.Ino = n.stableAttr.Ino
|
||||
out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode
|
||||
}
|
||||
|
||||
// StableAttr returns the (Ino, Gen) tuple for this node.
|
||||
func (n *Inode) StableAttr() StableAttr {
|
||||
return n.stableAttr
|
||||
}
|
||||
|
||||
// Mode returns the filetype
|
||||
func (n *Inode) Mode() uint32 {
|
||||
return n.stableAttr.Mode
|
||||
}
|
||||
|
||||
// Returns the root of the tree
|
||||
func (n *Inode) Root() *Inode {
|
||||
return n.bridge.root
|
||||
}
|
||||
|
||||
// Returns whether this is the root of the tree
|
||||
func (n *Inode) IsRoot() bool {
|
||||
return n.bridge.root == n
|
||||
}
|
||||
|
||||
func modeStr(m uint32) string {
|
||||
return map[uint32]string{
|
||||
syscall.S_IFREG: "reg",
|
||||
syscall.S_IFLNK: "lnk",
|
||||
syscall.S_IFDIR: "dir",
|
||||
syscall.S_IFSOCK: "soc",
|
||||
syscall.S_IFIFO: "pip",
|
||||
syscall.S_IFCHR: "chr",
|
||||
syscall.S_IFBLK: "blk",
|
||||
}[m]
|
||||
}
|
||||
|
||||
func (a StableAttr) String() string {
|
||||
return fmt.Sprintf("i%d g%d (%s)",
|
||||
a.Ino, a.Gen, modeStr(a.Mode))
|
||||
}
|
||||
|
||||
// debugString is used for debugging. Racy.
|
||||
func (n *Inode) String() string {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
return fmt.Sprintf("%s: %s", n.stableAttr.String(), n.children.String())
|
||||
}
|
||||
|
||||
// sortNodes rearranges inode group in consistent order.
|
||||
//
|
||||
// The nodes are ordered by their in-RAM address, which gives consistency
|
||||
// property: for any A and B inodes, sortNodes will either always order A < B,
|
||||
// or always order A > B.
|
||||
//
|
||||
// See lockNodes where this property is used to avoid deadlock when taking
|
||||
// locks on inode group.
|
||||
func sortNodes(ns []*Inode) {
|
||||
sort.Slice(ns, func(i, j int) bool {
|
||||
return nodeLess(ns[i], ns[j])
|
||||
})
|
||||
}
|
||||
|
||||
func nodeLess(a, b *Inode) bool {
|
||||
return uintptr(unsafe.Pointer(a)) < uintptr(unsafe.Pointer(b))
|
||||
}
|
||||
|
||||
// lockNodes locks group of inodes.
|
||||
//
|
||||
// It always lock the inodes in the same order - to avoid deadlocks.
|
||||
// It also avoids locking an inode more than once, if it was specified multiple times.
|
||||
// An example when an inode might be given multiple times is if dir/a and dir/b
|
||||
// are hardlinked to the same inode and the caller needs to take locks on dir children.
|
||||
func lockNodes(ns ...*Inode) {
|
||||
sortNodes(ns)
|
||||
|
||||
// The default value nil prevents trying to lock nil nodes.
|
||||
var nprev *Inode
|
||||
for _, n := range ns {
|
||||
if n != nprev {
|
||||
n.mu.Lock()
|
||||
nprev = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lockNode2 locks a and b in order consistent with lockNodes.
|
||||
func lockNode2(a, b *Inode) {
|
||||
if a == b {
|
||||
a.mu.Lock()
|
||||
} else if nodeLess(a, b) {
|
||||
a.mu.Lock()
|
||||
b.mu.Lock()
|
||||
} else {
|
||||
b.mu.Lock()
|
||||
a.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlockNode2 unlocks a and b
|
||||
func unlockNode2(a, b *Inode) {
|
||||
if a == b {
|
||||
a.mu.Unlock()
|
||||
} else {
|
||||
a.mu.Unlock()
|
||||
b.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlockNodes releases locks taken by lockNodes.
|
||||
func unlockNodes(ns ...*Inode) {
|
||||
// we don't need to unlock in the same order that was used in lockNodes.
|
||||
// however it still helps to have nodes sorted to avoid duplicates.
|
||||
sortNodes(ns)
|
||||
|
||||
var nprev *Inode
|
||||
for _, n := range ns {
|
||||
if n != nprev {
|
||||
n.mu.Unlock()
|
||||
nprev = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forgotten returns true if the kernel holds no references to this
|
||||
// inode. This can be used for background cleanup tasks, since the
|
||||
// kernel has no way of reviving forgotten nodes by its own
|
||||
// initiative.
|
||||
//
|
||||
// Bugs: Forgotten() may momentarily return true in the window between
|
||||
// creation (NewInode) and adding the node into the tree, which
|
||||
// happens after Lookup/Mkdir/etc. return.
|
||||
//
|
||||
// Deprecated: use NodeOnForgetter instead.
|
||||
func (n *Inode) Forgotten() bool {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.lookupCount == 0 && n.parents.count() == 0 && !n.persistent
|
||||
}
|
||||
|
||||
// Operations returns the object implementing the file system
|
||||
// operations.
|
||||
func (n *Inode) Operations() InodeEmbedder {
|
||||
return n.ops
|
||||
}
|
||||
|
||||
// Path returns a path string to the inode relative to `root`.
|
||||
// Pass nil to walk the hierarchy as far up as possible.
|
||||
//
|
||||
// If you set `root`, Path() warns if it finds an orphaned Inode, i.e.
|
||||
// if it does not end up at `root` after walking the hierarchy.
|
||||
func (n *Inode) Path(root *Inode) string {
|
||||
var segments []string
|
||||
p := n
|
||||
for p != nil && p != root {
|
||||
// We don't try to take all locks at the same time, because
|
||||
// the caller won't use the "path" string under lock anyway.
|
||||
p.mu.Lock()
|
||||
// Get last known parent
|
||||
pd := p.parents.get()
|
||||
p.mu.Unlock()
|
||||
if pd == nil {
|
||||
p = nil
|
||||
break
|
||||
}
|
||||
segments = append(segments, pd.name)
|
||||
p = pd.parent
|
||||
}
|
||||
|
||||
if root != nil && root != p {
|
||||
deletedPlaceholder := fmt.Sprintf(".go-fuse.%d/deleted", rand.Uint64())
|
||||
n.bridge.logf("warning: Inode.Path: n%d is orphaned, replacing segment with %q",
|
||||
n.nodeId, deletedPlaceholder)
|
||||
// NOSUBMIT - should replace rather than append?
|
||||
segments = append(segments, deletedPlaceholder)
|
||||
}
|
||||
|
||||
i := 0
|
||||
j := len(segments) - 1
|
||||
|
||||
for i < j {
|
||||
segments[i], segments[j] = segments[j], segments[i]
|
||||
i++
|
||||
j--
|
||||
}
|
||||
|
||||
path := strings.Join(segments, "/")
|
||||
return path
|
||||
}
|
||||
|
||||
// setEntry does `iparent[name] = ichild` linking.
|
||||
//
|
||||
// setEntry must not be called simultaneously for any of iparent or ichild.
|
||||
// This, for example could be satisfied if both iparent and ichild are locked,
|
||||
// but it could be also valid if only iparent is locked and ichild was just
|
||||
// created and only one goroutine keeps referencing it.
|
||||
func (iparent *Inode) setEntry(name string, ichild *Inode) {
|
||||
if ichild.stableAttr.Mode == syscall.S_IFDIR {
|
||||
// Directories cannot have more than one parent. Clear the map.
|
||||
// This special-case is neccessary because ichild may still have a
|
||||
// parent that was forgotten (i.e. removed from bridge.inoMap).
|
||||
ichild.parents.clear()
|
||||
}
|
||||
iparent.children.set(iparent, name, ichild)
|
||||
}
|
||||
|
||||
// NewPersistentInode returns an Inode whose lifetime is not in
|
||||
// control of the kernel.
|
||||
//
|
||||
// When the kernel is short on memory, it will forget cached file
|
||||
// system information (directory entries and inode metadata). This is
|
||||
// announced with FORGET messages. There are no guarantees if or when
|
||||
// this happens. When it happens, these are handled transparently by
|
||||
// go-fuse: all Inodes created with NewInode are released
|
||||
// automatically. NewPersistentInode creates inodes that go-fuse keeps
|
||||
// in memory, even if the kernel is not interested in them. This is
|
||||
// convenient for building static trees up-front.
|
||||
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
|
||||
return n.newInode(ctx, node, id, true)
|
||||
}
|
||||
|
||||
// ForgetPersistent manually marks the node as no longer important. If
|
||||
// it has no children, and if the kernel as no references, the nodes
|
||||
// gets removed from the tree.
|
||||
func (n *Inode) ForgetPersistent() {
|
||||
n.removeRef(0, true)
|
||||
}
|
||||
|
||||
// NewInode returns an inode for the given InodeEmbedder. The mode
|
||||
// should be standard mode argument (eg. S_IFDIR). The inode number in
|
||||
// id.Ino argument is used to implement hard-links. If it is given,
|
||||
// and another node with the same ID is known, the new inode may be
|
||||
// ignored, and the old one used instead. If the parent inode
|
||||
// implements NodeWrapChilder, the returned Inode will have a
|
||||
// different InodeEmbedder from the one passed in.
|
||||
func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
|
||||
return n.newInode(ctx, node, id, false)
|
||||
}
|
||||
|
||||
func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
|
||||
if wc, ok := n.ops.(NodeWrapChilder); ok {
|
||||
ops = wc.WrapChild(ctx, ops)
|
||||
}
|
||||
return n.bridge.newInode(ctx, ops, id, persistent)
|
||||
}
|
||||
|
||||
// removeRef decreases references. Returns if this operation caused
|
||||
// the node to be forgotten (for kernel references), and whether it is
|
||||
// live (ie. was not dropped from the tree)
|
||||
func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (hasLookups, isPersistent, hasChildren bool) {
|
||||
var beforeLookups, beforePersistence, beforeChildren bool
|
||||
var unusedParents []*Inode
|
||||
beforeLookups, hasLookups, beforePersistence, isPersistent, beforeChildren, hasChildren, unusedParents = n.removeRefInner(nlookup, dropPersistence, unusedParents)
|
||||
|
||||
if !hasLookups && !isPersistent && !hasChildren && (beforeChildren || beforeLookups || beforePersistence) {
|
||||
if nf, ok := n.ops.(NodeOnForgetter); ok {
|
||||
nf.OnForget()
|
||||
}
|
||||
}
|
||||
|
||||
for len(unusedParents) > 0 {
|
||||
l := len(unusedParents)
|
||||
p := unusedParents[l-1]
|
||||
unusedParents = unusedParents[:l-1]
|
||||
_, _, _, _, _, _, unusedParents = p.removeRefInner(0, false, unusedParents)
|
||||
|
||||
if nf, ok := p.ops.(NodeOnForgetter); ok {
|
||||
nf.OnForget()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Inode) removeRefInner(nlookup uint64, dropPersistence bool, inputUnusedParents []*Inode) (beforeLookups, hasLookups, beforePersistent, isPersistent, beforeChildren, hasChildren bool, unusedParents []*Inode) {
|
||||
var lockme []*Inode
|
||||
var parents []parentData
|
||||
|
||||
unusedParents = inputUnusedParents
|
||||
|
||||
n.mu.Lock()
|
||||
beforeLookups = n.lookupCount > 0
|
||||
beforePersistent = n.persistent
|
||||
beforeChildren = n.children.len() > 0
|
||||
if nlookup > 0 && dropPersistence {
|
||||
log.Panic("only one allowed")
|
||||
} else if nlookup > n.lookupCount {
|
||||
log.Panicf("n%d lookupCount underflow: lookupCount=%d, decrement=%d", n.nodeId, n.lookupCount, nlookup)
|
||||
} else if nlookup > 0 {
|
||||
n.lookupCount -= nlookup
|
||||
n.changeCounter++
|
||||
} else if dropPersistence && n.persistent {
|
||||
n.persistent = false
|
||||
n.changeCounter++
|
||||
}
|
||||
|
||||
n.bridge.mu.Lock()
|
||||
if n.lookupCount == 0 {
|
||||
// Dropping the node from stableAttrs guarantees that no new references to this node are
|
||||
// handed out to the kernel, hence we can also safely delete it from kernelNodeIds.
|
||||
delete(n.bridge.stableAttrs, n.stableAttr)
|
||||
delete(n.bridge.kernelNodeIds, n.nodeId)
|
||||
}
|
||||
n.bridge.mu.Unlock()
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockme = append(lockme[:0], n)
|
||||
parents = parents[:0]
|
||||
nChange := n.changeCounter
|
||||
hasLookups = n.lookupCount > 0
|
||||
hasChildren = n.children.len() > 0
|
||||
isPersistent = n.persistent
|
||||
for _, p := range n.parents.all() {
|
||||
parents = append(parents, p)
|
||||
lockme = append(lockme, p.parent)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
if hasLookups || hasChildren || isPersistent {
|
||||
return
|
||||
}
|
||||
|
||||
lockNodes(lockme...)
|
||||
if n.changeCounter != nChange {
|
||||
unlockNodes(lockme...)
|
||||
// could avoid unlocking and relocking n here.
|
||||
n.mu.Lock()
|
||||
continue retry
|
||||
}
|
||||
|
||||
for _, p := range parents {
|
||||
parentNode := p.parent
|
||||
if parentNode.children.get(p.name) != n {
|
||||
// another node has replaced us already
|
||||
continue
|
||||
}
|
||||
parentNode.children.del(p.parent, p.name)
|
||||
|
||||
if parentNode.children.len() == 0 && parentNode.lookupCount == 0 && !parentNode.persistent {
|
||||
unusedParents = append(unusedParents, parentNode)
|
||||
}
|
||||
}
|
||||
|
||||
if n.lookupCount != 0 {
|
||||
log.Panicf("n%d %p lookupCount changed: %d", n.nodeId, n, n.lookupCount)
|
||||
}
|
||||
|
||||
unlockNodes(lockme...)
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetChild returns a child node with the given name, or nil if the
|
||||
// directory has no child by that name.
|
||||
func (n *Inode) GetChild(name string) *Inode {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.get(name)
|
||||
}
|
||||
|
||||
// AddChild adds a child to this node. If overwrite is false, fail if
|
||||
// the destination already exists.
|
||||
func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool) {
|
||||
if len(name) == 0 {
|
||||
log.Panic("empty name for inode")
|
||||
}
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockNode2(n, ch)
|
||||
prev := n.children.get(name)
|
||||
parentCounter := n.changeCounter
|
||||
if prev == nil {
|
||||
n.children.set(n, name, ch)
|
||||
unlockNode2(n, ch)
|
||||
return true
|
||||
}
|
||||
unlockNode2(n, ch)
|
||||
if !overwrite {
|
||||
return false
|
||||
}
|
||||
lockme := [3]*Inode{n, ch, prev}
|
||||
|
||||
lockNodes(lockme[:]...)
|
||||
if parentCounter != n.changeCounter {
|
||||
unlockNodes(lockme[:]...)
|
||||
continue retry
|
||||
}
|
||||
|
||||
prev.parents.delete(parentData{name, n})
|
||||
n.children.set(n, name, ch)
|
||||
prev.changeCounter++
|
||||
unlockNodes(lockme[:]...)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Children returns the list of children of this directory Inode.
|
||||
func (n *Inode) Children() map[string]*Inode {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.toMap()
|
||||
}
|
||||
|
||||
// childrenList returns the list of children of this directory Inode.
|
||||
// The result is guaranteed to be stable as long as the directory did
|
||||
// not change.
|
||||
func (n *Inode) childrenList() []childEntry {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.children.list()
|
||||
}
|
||||
|
||||
// Parents returns a parent of this Inode, or nil if this Inode is
|
||||
// deleted or is the root
|
||||
func (n *Inode) Parent() (string, *Inode) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
p := n.parents.get()
|
||||
if p == nil {
|
||||
return "", nil
|
||||
}
|
||||
return p.name, p.parent
|
||||
}
|
||||
|
||||
// RmAllChildren recursively drops a tree, forgetting all persistent
|
||||
// nodes.
|
||||
func (n *Inode) RmAllChildren() {
|
||||
for {
|
||||
chs := n.Children()
|
||||
if len(chs) == 0 {
|
||||
break
|
||||
}
|
||||
for nm, ch := range chs {
|
||||
ch.RmAllChildren()
|
||||
n.RmChild(nm)
|
||||
}
|
||||
}
|
||||
n.removeRef(0, true)
|
||||
}
|
||||
|
||||
// RmChild removes multiple children. Returns whether the removal
|
||||
// succeeded and whether the node is still live afterward. The removal
|
||||
// is transactional: it only succeeds if all names are children, and
|
||||
// if they all were removed successfully. If the removal was
|
||||
// successful, and there are no children left, the node may be removed
|
||||
// from the FS tree. In that case, RmChild returns live==false.
|
||||
func (n *Inode) RmChild(names ...string) (success, live bool) {
|
||||
var lockme []*Inode
|
||||
|
||||
retry:
|
||||
for {
|
||||
n.mu.Lock()
|
||||
lockme = append(lockme[:0], n)
|
||||
nChange := n.changeCounter
|
||||
for _, nm := range names {
|
||||
ch := n.children.get(nm)
|
||||
if ch == nil {
|
||||
n.mu.Unlock()
|
||||
return false, true
|
||||
}
|
||||
lockme = append(lockme, ch)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
lockNodes(lockme...)
|
||||
|
||||
if n.changeCounter != nChange {
|
||||
unlockNodes(lockme...)
|
||||
continue retry
|
||||
}
|
||||
|
||||
for _, nm := range names {
|
||||
n.children.del(n, nm)
|
||||
}
|
||||
|
||||
live = n.lookupCount > 0 || n.children.len() > 0 || n.persistent
|
||||
unlockNodes(lockme...)
|
||||
|
||||
// removal successful
|
||||
break
|
||||
}
|
||||
|
||||
if !live {
|
||||
hasLookups, isPersistent, hasChildren := n.removeRef(0, false)
|
||||
return true, (hasLookups || isPersistent || hasChildren)
|
||||
}
|
||||
|
||||
return true, true
|
||||
}
|
||||
|
||||
// MvChild executes a rename. If overwrite is set, a child at the
|
||||
// destination will be overwritten, should it exist. It returns false
|
||||
// if 'overwrite' is false, and the destination exists.
|
||||
func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool {
|
||||
if len(newName) == 0 {
|
||||
log.Panicf("empty newName for MvChild")
|
||||
}
|
||||
|
||||
retry:
|
||||
for {
|
||||
lockNode2(n, newParent)
|
||||
counter1 := n.changeCounter
|
||||
counter2 := newParent.changeCounter
|
||||
|
||||
oldChild := n.children.get(old)
|
||||
destChild := newParent.children.get(newName)
|
||||
unlockNode2(n, newParent)
|
||||
|
||||
if destChild != nil && !overwrite {
|
||||
return false
|
||||
}
|
||||
|
||||
lockNodes(n, newParent, oldChild, destChild)
|
||||
if counter2 != newParent.changeCounter || counter1 != n.changeCounter {
|
||||
unlockNodes(n, newParent, oldChild, destChild)
|
||||
continue retry
|
||||
}
|
||||
|
||||
if oldChild != nil {
|
||||
n.children.del(n, old)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
// This can cause the child to be slated for
|
||||
// removal; see below
|
||||
newParent.children.del(newParent, newName)
|
||||
}
|
||||
|
||||
if oldChild != nil {
|
||||
newParent.children.set(newParent, newName, oldChild)
|
||||
}
|
||||
|
||||
unlockNodes(n, newParent, oldChild, destChild)
|
||||
|
||||
if destChild != nil {
|
||||
destChild.removeRef(0, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ExchangeChild swaps the entries at (n, oldName) and (newParent,
|
||||
// newName).
|
||||
func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string) {
|
||||
oldParent := n
|
||||
retry:
|
||||
for {
|
||||
lockNode2(oldParent, newParent)
|
||||
counter1 := oldParent.changeCounter
|
||||
counter2 := newParent.changeCounter
|
||||
|
||||
oldChild := oldParent.children.get(oldName)
|
||||
destChild := newParent.children.get(newName)
|
||||
unlockNode2(oldParent, newParent)
|
||||
|
||||
if destChild == oldChild {
|
||||
return
|
||||
}
|
||||
|
||||
lockNodes(oldParent, newParent, oldChild, destChild)
|
||||
if counter2 != newParent.changeCounter || counter1 != oldParent.changeCounter {
|
||||
unlockNodes(oldParent, newParent, oldChild, destChild)
|
||||
continue retry
|
||||
}
|
||||
|
||||
// Detach
|
||||
if oldChild != nil {
|
||||
oldParent.children.del(oldParent, oldName)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
newParent.children.del(newParent, newName)
|
||||
}
|
||||
|
||||
// Attach
|
||||
if oldChild != nil {
|
||||
newParent.children.set(newParent, newName, oldChild)
|
||||
}
|
||||
|
||||
if destChild != nil {
|
||||
oldParent.children.set(oldParent, oldName, destChild)
|
||||
}
|
||||
unlockNodes(oldParent, newParent, oldChild, destChild)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyEntry notifies the kernel that data for a (directory, name)
|
||||
// tuple should be invalidated. On next access, a LOOKUP operation
|
||||
// will be started.
|
||||
func (n *Inode) NotifyEntry(name string) syscall.Errno {
|
||||
status := n.bridge.server.EntryNotify(n.nodeId, name)
|
||||
return syscall.Errno(status)
|
||||
}
|
||||
|
||||
// NotifyDelete notifies the kernel that the given inode was removed
|
||||
// from this directory as entry under the given name. It is equivalent
|
||||
// to NotifyEntry, but also sends an event to inotify watchers.
|
||||
func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno {
|
||||
// XXX arg ordering?
|
||||
return syscall.Errno(n.bridge.server.DeleteNotify(n.nodeId, child.nodeId, name))
|
||||
|
||||
}
|
||||
|
||||
// NotifyContent notifies the kernel that content under the given
|
||||
// inode should be flushed from buffers.
|
||||
func (n *Inode) NotifyContent(off, sz int64) syscall.Errno {
|
||||
// XXX how does this work for directories?
|
||||
return syscall.Errno(n.bridge.server.InodeNotify(n.nodeId, off, sz))
|
||||
}
|
||||
|
||||
// WriteCache stores data in the kernel cache.
|
||||
func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno {
|
||||
return syscall.Errno(n.bridge.server.InodeNotifyStoreCache(n.nodeId, offset, data))
|
||||
}
|
||||
|
||||
// ReadCache reads data from the kernel cache.
|
||||
func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno) {
|
||||
c, s := n.bridge.server.InodeRetrieveCache(n.nodeId, offset, dest)
|
||||
return c, syscall.Errno(s)
|
||||
}
|
||||
133
vendor/github.com/hanwen/go-fuse/v2/fs/inode_children.go
generated
vendored
Normal file
133
vendor/github.com/hanwen/go-fuse/v2/fs/inode_children.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2023 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type childEntry struct {
|
||||
Name string
|
||||
Inode *Inode
|
||||
|
||||
// TODO: store int64 changeCounter of the parent, so we can
|
||||
// use the changeCounter as a directory offset.
|
||||
}
|
||||
|
||||
// inodeChildren is a hashmap with deterministic ordering. It is
|
||||
// important to return the children in a deterministic order for 2
|
||||
// reasons:
|
||||
//
|
||||
// 1. if the ordering is non-deterministic, multiple concurrent
|
||||
// readdirs can lead to cache corruption (see issue #391)
|
||||
//
|
||||
// 2. it simplifies the implementation of directory seeking: the NFS
|
||||
// protocol doesn't open and close directories. Instead, a directory
|
||||
// read must always be continued from a previously handed out offset.
|
||||
//
|
||||
// By storing the entries in insertion order, and marking them with a
|
||||
// int64 logical timestamp, the logical timestamp can serve as readdir
|
||||
// cookie.
|
||||
type inodeChildren struct {
|
||||
// index into children slice.
|
||||
childrenMap map[string]int
|
||||
children []childEntry
|
||||
}
|
||||
|
||||
func (c *inodeChildren) init() {
|
||||
c.childrenMap = make(map[string]int)
|
||||
}
|
||||
|
||||
func (c *inodeChildren) String() string {
|
||||
var ss []string
|
||||
for _, e := range c.children {
|
||||
ch := e.Inode
|
||||
ss = append(ss, fmt.Sprintf("%q=i%d[%s]", e.Name, ch.stableAttr.Ino, modeStr(ch.stableAttr.Mode)))
|
||||
}
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
func (c *inodeChildren) get(name string) *Inode {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.children[idx].Inode
|
||||
}
|
||||
|
||||
func (c *inodeChildren) compact() {
|
||||
nc := make([]childEntry, 0, 2*len(c.childrenMap)+1)
|
||||
nm := make(map[string]int, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode == nil {
|
||||
continue
|
||||
}
|
||||
nm[e.Name] = len(nc)
|
||||
nc = append(nc, e)
|
||||
}
|
||||
|
||||
c.childrenMap = nm
|
||||
c.children = nc
|
||||
}
|
||||
|
||||
func (c *inodeChildren) set(parent *Inode, name string, ch *Inode) {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
if cap(c.children) == len(c.children) {
|
||||
c.compact()
|
||||
}
|
||||
|
||||
idx = len(c.children)
|
||||
c.children = append(c.children, childEntry{})
|
||||
}
|
||||
|
||||
c.childrenMap[name] = idx
|
||||
c.children[idx] = childEntry{Name: name, Inode: ch}
|
||||
parent.changeCounter++
|
||||
|
||||
ch.parents.add(parentData{name, parent})
|
||||
ch.changeCounter++
|
||||
}
|
||||
|
||||
func (c *inodeChildren) len() int {
|
||||
return len(c.childrenMap)
|
||||
}
|
||||
|
||||
func (c *inodeChildren) toMap() map[string]*Inode {
|
||||
r := make(map[string]*Inode, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode != nil {
|
||||
r[e.Name] = e.Inode
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *inodeChildren) del(parent *Inode, name string) {
|
||||
idx, ok := c.childrenMap[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ch := c.children[idx].Inode
|
||||
|
||||
delete(c.childrenMap, name)
|
||||
c.children[idx] = childEntry{}
|
||||
ch.parents.delete(parentData{name, parent})
|
||||
ch.changeCounter++
|
||||
parent.changeCounter++
|
||||
}
|
||||
|
||||
func (c *inodeChildren) list() []childEntry {
|
||||
r := make([]childEntry, 0, len(c.childrenMap))
|
||||
for _, e := range c.children {
|
||||
if e.Inode != nil {
|
||||
r = append(r, e)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
101
vendor/github.com/hanwen/go-fuse/v2/fs/inode_parents.go
generated
vendored
Normal file
101
vendor/github.com/hanwen/go-fuse/v2/fs/inode_parents.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2021 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
// inodeParents stores zero or more parents of an Inode,
|
||||
// remembering which one is the most recent.
|
||||
//
|
||||
// No internal locking: the caller is responsible for preventing
|
||||
// concurrent access.
|
||||
type inodeParents struct {
|
||||
// newest is the most-recently add()'ed parent.
|
||||
// nil when we don't have any parents.
|
||||
newest *parentData
|
||||
// other are parents in addition to the newest.
|
||||
// nil or empty when we have <= 1 parents.
|
||||
other map[parentData]struct{}
|
||||
}
|
||||
|
||||
// add adds a parent to the store.
|
||||
func (p *inodeParents) add(n parentData) {
|
||||
// one and only parent
|
||||
if p.newest == nil {
|
||||
p.newest = &n
|
||||
}
|
||||
// already known as `newest`
|
||||
if *p.newest == n {
|
||||
return
|
||||
}
|
||||
// old `newest` gets displaced into `other`
|
||||
if p.other == nil {
|
||||
p.other = make(map[parentData]struct{})
|
||||
}
|
||||
p.other[*p.newest] = struct{}{}
|
||||
// new parent becomes `newest` (possibly moving up from `other`)
|
||||
delete(p.other, n)
|
||||
p.newest = &n
|
||||
}
|
||||
|
||||
// get returns the most recent parent
|
||||
// or nil if there is no parent at all.
|
||||
func (p *inodeParents) get() *parentData {
|
||||
return p.newest
|
||||
}
|
||||
|
||||
// all returns all known parents
|
||||
// or nil if there is no parent at all.
|
||||
func (p *inodeParents) all() []parentData {
|
||||
count := p.count()
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]parentData, 0, count)
|
||||
out = append(out, *p.newest)
|
||||
for i := range p.other {
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *inodeParents) delete(n parentData) {
|
||||
// We have zero parents, so we can't delete any.
|
||||
if p.newest == nil {
|
||||
return
|
||||
}
|
||||
// If it's not the `newest` it must be in `other` (or nowhere).
|
||||
if *p.newest != n {
|
||||
delete(p.other, n)
|
||||
return
|
||||
}
|
||||
// We want to delete `newest`, but there is no other to replace it.
|
||||
if len(p.other) == 0 {
|
||||
p.newest = nil
|
||||
return
|
||||
}
|
||||
// Move random entry from `other` over `newest`.
|
||||
var i parentData
|
||||
for i = range p.other {
|
||||
p.newest = &i
|
||||
break
|
||||
}
|
||||
delete(p.other, i)
|
||||
}
|
||||
|
||||
func (p *inodeParents) clear() {
|
||||
p.newest = nil
|
||||
p.other = nil
|
||||
}
|
||||
|
||||
func (p *inodeParents) count() int {
|
||||
if p.newest == nil {
|
||||
return 0
|
||||
}
|
||||
return 1 + len(p.other)
|
||||
}
|
||||
|
||||
type parentData struct {
|
||||
name string
|
||||
parent *Inode
|
||||
}
|
||||
549
vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go
generated
vendored
Normal file
549
vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/hanwen/go-fuse/v2/internal/openat"
|
||||
"github.com/hanwen/go-fuse/v2/internal/renameat"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LoopbackRoot holds the parameters for creating a new loopback
|
||||
// filesystem. Loopback filesystem delegate their operations to an
|
||||
// underlying POSIX file system.
|
||||
type LoopbackRoot struct {
|
||||
// The path to the root of the underlying file system.
|
||||
Path string
|
||||
|
||||
// The device on which the Path resides. This must be set if
|
||||
// the underlying filesystem crosses file systems.
|
||||
Dev uint64
|
||||
|
||||
// NewNode returns a new InodeEmbedder to be used to respond
|
||||
// to a LOOKUP/CREATE/MKDIR/MKNOD opcode. If not set, use a
|
||||
// LoopbackNode.
|
||||
//
|
||||
// Deprecated: use NodeWrapChilder instead.
|
||||
NewNode func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder
|
||||
|
||||
// RootNode is the root of the Loopback. This must be set if
|
||||
// the Loopback file system is not the root of the FUSE
|
||||
// mount. It is set automatically by NewLoopbackRoot.
|
||||
RootNode InodeEmbedder
|
||||
}
|
||||
|
||||
func (r *LoopbackRoot) newNode(parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder {
|
||||
if r.NewNode != nil {
|
||||
return r.NewNode(r, parent, name, st)
|
||||
}
|
||||
return &LoopbackNode{
|
||||
RootData: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoopbackRoot) idFromStat(st *syscall.Stat_t) StableAttr {
|
||||
// We compose an inode number by the underlying inode, and
|
||||
// mixing in the device number. In traditional filesystems,
|
||||
// the inode numbers are small. The device numbers are also
|
||||
// small (typically 16 bit). Finally, we mask out the root
|
||||
// device number of the root, so a loopback FS that does not
|
||||
// encompass multiple mounts will reflect the inode numbers of
|
||||
// the underlying filesystem
|
||||
swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32)
|
||||
swappedRootDev := (r.Dev << 32) | (r.Dev >> 32)
|
||||
return StableAttr{
|
||||
Mode: uint32(st.Mode),
|
||||
Gen: 1,
|
||||
// This should work well for traditional backing FSes,
|
||||
// not so much for other go-fuse FS-es
|
||||
Ino: (swapped ^ swappedRootDev) ^ st.Ino,
|
||||
}
|
||||
}
|
||||
|
||||
// LoopbackNode is a filesystem node in a loopback file system. It is
|
||||
// public so it can be used as a basis for other loopback based
|
||||
// filesystems. See NewLoopbackFile or LoopbackRoot for more
|
||||
// information.
|
||||
type LoopbackNode struct {
|
||||
Inode
|
||||
|
||||
// RootData points back to the root of the loopback filesystem.
|
||||
RootData *LoopbackRoot
|
||||
}
|
||||
|
||||
// loopbackNodeEmbedder can only be implemented by the LoopbackNode
|
||||
// concrete type.
|
||||
type loopbackNodeEmbedder interface {
|
||||
loopbackNode() *LoopbackNode
|
||||
}
|
||||
|
||||
func (n *LoopbackNode) loopbackNode() *LoopbackNode {
|
||||
return n
|
||||
}
|
||||
|
||||
var _ = (NodeStatfser)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
|
||||
s := syscall.Statfs_t{}
|
||||
err := syscall.Statfs(n.path(), &s)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatfsT(&s)
|
||||
return OK
|
||||
}
|
||||
|
||||
// path returns the full path to the file in the underlying file
|
||||
// system.
|
||||
func (n *LoopbackNode) root() *Inode {
|
||||
var rootNode *Inode
|
||||
if n.RootData.RootNode != nil {
|
||||
rootNode = n.RootData.RootNode.EmbeddedInode()
|
||||
} else {
|
||||
rootNode = n.Root()
|
||||
}
|
||||
|
||||
return rootNode
|
||||
}
|
||||
|
||||
// relativePath returns the path the node, relative to to the root directory
|
||||
func (n *LoopbackNode) relativePath() string {
|
||||
return n.Path(n.root())
|
||||
}
|
||||
|
||||
// path returns the absolute path to the node
|
||||
func (n *LoopbackNode) path() string {
|
||||
return filepath.Join(n.RootData.Path, n.relativePath())
|
||||
}
|
||||
|
||||
var _ = (NodeLookuper)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Lstat(p, &st)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
// preserveOwner sets uid and gid of `path` according to the caller information
|
||||
// in `ctx`.
|
||||
func (n *LoopbackNode) preserveOwner(ctx context.Context, path string) error {
|
||||
if os.Getuid() != 0 {
|
||||
return nil
|
||||
}
|
||||
caller, ok := fuse.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return syscall.Lchown(path, int(caller.Uid), int(caller.Gid))
|
||||
}
|
||||
|
||||
var _ = (NodeMknoder)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Mknod(p, mode, intDev(rdev))
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Rmdir(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeMkdirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := os.Mkdir(p, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Rmdir(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeRmdirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Rmdir(p)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeUnlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Unlink(p)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeRenamer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno {
|
||||
e2, ok := newParent.(loopbackNodeEmbedder)
|
||||
if !ok {
|
||||
return syscall.EXDEV
|
||||
}
|
||||
|
||||
if e2.loopbackNode().RootData != n.RootData {
|
||||
return syscall.EXDEV
|
||||
}
|
||||
|
||||
if flags != 0 {
|
||||
return n.rename2(name, e2.loopbackNode(), newName, flags)
|
||||
}
|
||||
|
||||
p1 := filepath.Join(n.path(), name)
|
||||
p2 := filepath.Join(e2.loopbackNode().path(), newName)
|
||||
|
||||
err := syscall.Rename(p1, p2)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeCreater)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
flags = flags &^ syscall.O_APPEND
|
||||
fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
|
||||
if err != nil {
|
||||
return nil, nil, 0, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Fstat(fd, &st); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, nil, 0, ToErrno(err)
|
||||
}
|
||||
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
lf := NewLoopbackFile(fd)
|
||||
|
||||
out.FromStat(&st)
|
||||
return ch, lf, 0, 0
|
||||
}
|
||||
|
||||
func (n *LoopbackNode) rename2(name string, newParent *LoopbackNode, newName string, flags uint32) syscall.Errno {
|
||||
fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
defer syscall.Close(fd1)
|
||||
p2 := newParent.path()
|
||||
fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
defer syscall.Close(fd2)
|
||||
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Fstat(fd1, &st); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
// Double check that nodes didn't change from under us.
|
||||
if n.root() != n.EmbeddedInode() && n.Inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
|
||||
return syscall.EBUSY
|
||||
}
|
||||
if err := syscall.Fstat(fd2, &st); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
if (newParent.root() != newParent.EmbeddedInode()) && newParent.Inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
|
||||
return syscall.EBUSY
|
||||
}
|
||||
|
||||
return ToErrno(renameat.Renameat(fd1, name, fd2, newName, uint(flags)))
|
||||
}
|
||||
|
||||
var _ = (NodeSymlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Symlink(target, p)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
n.preserveOwner(ctx, p)
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Unlink(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeLinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
|
||||
|
||||
p := filepath.Join(n.path(), name)
|
||||
err := syscall.Link(filepath.Join(n.RootData.Path, target.EmbeddedInode().Path(nil)), p)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
st := syscall.Stat_t{}
|
||||
if err := syscall.Lstat(p, &st); err != nil {
|
||||
syscall.Unlink(p)
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
node := n.RootData.newNode(n.EmbeddedInode(), name, &st)
|
||||
ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st))
|
||||
|
||||
out.Attr.FromStat(&st)
|
||||
return ch, 0
|
||||
}
|
||||
|
||||
var _ = (NodeReadlinker)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
|
||||
p := n.path()
|
||||
|
||||
for l := 256; ; l *= 2 {
|
||||
buf := make([]byte, l)
|
||||
sz, err := syscall.Readlink(p, buf)
|
||||
if err != nil {
|
||||
return nil, ToErrno(err)
|
||||
}
|
||||
|
||||
if sz < len(buf) {
|
||||
return buf[:sz], 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ = (NodeOpener)((*LoopbackNode)(nil))
|
||||
|
||||
// Symlink-safe through use of OpenSymlinkAware.
|
||||
func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
flags = flags &^ (syscall.O_APPEND | fuse.FMODE_EXEC)
|
||||
|
||||
f, err := openat.OpenSymlinkAware(n.RootData.Path, n.relativePath(), int(flags), 0)
|
||||
if err != nil {
|
||||
return nil, 0, ToErrno(err)
|
||||
}
|
||||
lf := NewLoopbackFile(f)
|
||||
return lf, 0, 0
|
||||
}
|
||||
|
||||
var _ = (NodeOpendirHandler)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) {
|
||||
ds, errno := NewLoopbackDirStream(n.path())
|
||||
if errno != 0 {
|
||||
return nil, 0, errno
|
||||
}
|
||||
return ds, 0, errno
|
||||
}
|
||||
|
||||
var _ = (NodeReaddirer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
|
||||
return NewLoopbackDirStream(n.path())
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
if f != nil {
|
||||
if fga, ok := f.(FileGetattrer); ok {
|
||||
return fga.Getattr(ctx, out)
|
||||
}
|
||||
}
|
||||
|
||||
p := n.path()
|
||||
|
||||
var err error
|
||||
st := syscall.Stat_t{}
|
||||
if &n.Inode == n.Root() {
|
||||
err = syscall.Stat(p, &st)
|
||||
} else {
|
||||
err = syscall.Lstat(p, &st)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStat(&st)
|
||||
return OK
|
||||
}
|
||||
|
||||
var _ = (NodeSetattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
p := n.path()
|
||||
fsa, ok := f.(FileSetattrer)
|
||||
if ok && fsa != nil {
|
||||
fsa.Setattr(ctx, in, out)
|
||||
} else {
|
||||
if m, ok := in.GetMode(); ok {
|
||||
if err := syscall.Chmod(p, m); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
uid, uok := in.GetUID()
|
||||
gid, gok := in.GetGID()
|
||||
if uok || gok {
|
||||
suid := -1
|
||||
sgid := -1
|
||||
if uok {
|
||||
suid = int(uid)
|
||||
}
|
||||
if gok {
|
||||
sgid = int(gid)
|
||||
}
|
||||
if err := syscall.Chown(p, suid, sgid); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
mtime, mok := in.GetMTime()
|
||||
atime, aok := in.GetATime()
|
||||
|
||||
if mok || aok {
|
||||
ta := unix.Timespec{Nsec: unix_UTIME_OMIT}
|
||||
tm := unix.Timespec{Nsec: unix_UTIME_OMIT}
|
||||
var err error
|
||||
if aok {
|
||||
ta, err = unix.TimeToTimespec(atime)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
if mok {
|
||||
tm, err = unix.TimeToTimespec(mtime)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
ts := []unix.Timespec{ta, tm}
|
||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, ts, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
if err := syscall.Truncate(p, int64(sz)); err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fga, ok := f.(FileGetattrer)
|
||||
if ok && fga != nil {
|
||||
fga.Getattr(ctx, out)
|
||||
} else {
|
||||
st := syscall.Stat_t{}
|
||||
err := syscall.Lstat(p, &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStat(&st)
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
|
||||
sz, err := unix.Lgetxattr(n.path(), attr, dest)
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
|
||||
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
|
||||
err := unix.Lremovexattr(n.path(), attr)
|
||||
return ToErrno(err)
|
||||
}
|
||||
|
||||
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
|
||||
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
|
||||
len uint64, flags uint64) (uint32, syscall.Errno) {
|
||||
lfIn, ok := fhIn.(*loopbackFile)
|
||||
if !ok {
|
||||
return 0, unix.ENOTSUP
|
||||
}
|
||||
lfOut, ok := fhOut.(*loopbackFile)
|
||||
if !ok {
|
||||
return 0, unix.ENOTSUP
|
||||
}
|
||||
signedOffIn := int64(offIn)
|
||||
signedOffOut := int64(offOut)
|
||||
doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags))
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
// NewLoopbackRoot returns a root node for a loopback file system whose
|
||||
// root is at the given root. This node implements all NodeXxxxer
|
||||
// operations available.
|
||||
func NewLoopbackRoot(rootPath string) (InodeEmbedder, error) {
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Stat(rootPath, &st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := &LoopbackRoot{
|
||||
Path: rootPath,
|
||||
Dev: uint64(st.Dev),
|
||||
}
|
||||
|
||||
rootNode := root.newNode(nil, "", &st)
|
||||
root.RootNode = rootNode
|
||||
return rootNode, nil
|
||||
}
|
||||
36
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go
generated
vendored
Normal file
36
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = 0x0
|
||||
|
||||
// timeToTimeval - Convert time.Time to syscall.Timeval
|
||||
//
|
||||
// Note: This does not use syscall.NsecToTimespec because
|
||||
// that does not work properly for times before 1970,
|
||||
// see https://github.com/golang/go/issues/12777
|
||||
func timeToTimeval(t *time.Time) syscall.Timeval {
|
||||
var tv syscall.Timeval
|
||||
tv.Usec = int32(t.Nanosecond() / 1000)
|
||||
tv.Sec = t.Unix()
|
||||
return tv
|
||||
}
|
||||
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
return 0, syscall.ENOSYS
|
||||
}
|
||||
|
||||
func intDev(dev uint32) int {
|
||||
return int(dev)
|
||||
}
|
||||
80
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_freebsd.go
generated
vendored
Normal file
80
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/internal/xattr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = unix.UTIME_OMIT
|
||||
|
||||
// FreeBSD has added copy_file_range(2) since FreeBSD 12. However,
|
||||
// golang.org/x/sys/unix hasn't add corresponding syscall constant or
|
||||
// wrap function. Here we define the syscall constant until sys/unix
|
||||
// provides.
|
||||
const sys_COPY_FILE_RANGE = 569
|
||||
|
||||
// TODO: replace the manual syscall when sys/unix provides CopyFileRange
|
||||
// for FreeBSD
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
count, _, errno := unix.Syscall6(sys_COPY_FILE_RANGE,
|
||||
uintptr(fdIn), uintptr(offIn), uintptr(fdOut), uintptr(offOut),
|
||||
uintptr(len), uintptr(flags),
|
||||
)
|
||||
return uint32(count), errno
|
||||
}
|
||||
|
||||
func intDev(dev uint32) uint64 {
|
||||
return uint64(dev)
|
||||
}
|
||||
|
||||
// Since FUSE on FreeBSD expect Linux flavor data format of
|
||||
// listxattr, we should reconstruct it with data returned by
|
||||
// FreeBSD's syscall. And here we have added a "user." prefix
|
||||
// to put them under "user" namespace, which is readable and
|
||||
// writable for normal user, for a userspace implemented FS.
|
||||
func rebuildAttrBuf(attrList [][]byte) []byte {
|
||||
ret := make([]byte, 0)
|
||||
for _, attrName := range attrList {
|
||||
nsAttrName := append([]byte("user."), attrName...)
|
||||
ret = append(ret, nsAttrName...)
|
||||
ret = append(ret, 0x0)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
|
||||
// In order to simulate same data format as Linux does,
|
||||
// and the size of returned buf is required to match, we must
|
||||
// call unix.Llistxattr twice.
|
||||
sz, err := unix.Llistxattr(n.path(), nil)
|
||||
if err != nil {
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
rawBuf := make([]byte, sz)
|
||||
sz, err = unix.Llistxattr(n.path(), rawBuf)
|
||||
if err != nil {
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
attrList := xattr.ParseAttrNames(rawBuf)
|
||||
rebuiltBuf := rebuildAttrBuf(attrList)
|
||||
sz = len(rebuiltBuf)
|
||||
if len(dest) != 0 {
|
||||
// When len(dest) is 0, which means that caller wants to get
|
||||
// the size. If len(dest) is less than len(rebuiltBuf), but greater
|
||||
// than 0 dest will be also filled with data from rebuiltBuf,
|
||||
// but truncated to len(dest). copy() function will do the same.
|
||||
// And this behaviour is same as FreeBSD's syscall extattr_list_file(2).
|
||||
sz = copy(dest, rebuiltBuf)
|
||||
}
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
50
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go
generated
vendored
Normal file
50
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const unix_UTIME_OMIT = unix.UTIME_OMIT
|
||||
|
||||
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
|
||||
len int, flags int) (uint32, syscall.Errno) {
|
||||
count, err := unix.CopyFileRange(fdIn, &offIn, fdOut, &offOut, len, flags)
|
||||
return uint32(count), ToErrno(err)
|
||||
}
|
||||
|
||||
func intDev(dev uint32) int {
|
||||
return int(dev)
|
||||
}
|
||||
|
||||
var _ = (NodeStatxer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Statx(ctx context.Context, f FileHandle,
|
||||
flags uint32, mask uint32,
|
||||
out *fuse.StatxOut) syscall.Errno {
|
||||
if f != nil {
|
||||
if fga, ok := f.(FileStatxer); ok {
|
||||
return fga.Statx(ctx, flags, mask, out)
|
||||
}
|
||||
}
|
||||
|
||||
p := n.path()
|
||||
|
||||
st := unix.Statx_t{}
|
||||
err := unix.Statx(unix.AT_FDCWD, p, int(flags), int(mask), &st)
|
||||
if err != nil {
|
||||
return ToErrno(err)
|
||||
}
|
||||
out.FromStatx(&st)
|
||||
return OK
|
||||
}
|
||||
20
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_unix.go
generated
vendored
Normal file
20
vendor/github.com/hanwen/go-fuse/v2/fs/loopback_unix.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build !freebsd
|
||||
|
||||
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
|
||||
|
||||
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
|
||||
sz, err := unix.Llistxattr(n.path(), dest)
|
||||
return uint32(sz), ToErrno(err)
|
||||
}
|
||||
121
vendor/github.com/hanwen/go-fuse/v2/fs/mem.go
generated
vendored
Normal file
121
vendor/github.com/hanwen/go-fuse/v2/fs/mem.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// MemRegularFile is a filesystem node that holds a data
|
||||
// slice in memory.
|
||||
type MemRegularFile struct {
|
||||
Inode
|
||||
|
||||
mu sync.Mutex
|
||||
Data []byte
|
||||
Attr fuse.Attr
|
||||
}
|
||||
|
||||
var _ = (NodeOpener)((*MemRegularFile)(nil))
|
||||
var _ = (NodeReader)((*MemRegularFile)(nil))
|
||||
var _ = (NodeWriter)((*MemRegularFile)(nil))
|
||||
var _ = (NodeSetattrer)((*MemRegularFile)(nil))
|
||||
var _ = (NodeFlusher)((*MemRegularFile)(nil))
|
||||
var _ = (NodeAllocater)((*MemRegularFile)(nil))
|
||||
|
||||
func (f *MemRegularFile) Allocate(ctx context.Context, fh FileHandle, off uint64, size uint64, mode uint32) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
oldSz := len(f.Data)
|
||||
if uint64(cap(f.Data)) < off+size {
|
||||
n := make([]byte, off+size)
|
||||
copy(n, f.Data)
|
||||
f.Data = n
|
||||
}
|
||||
if keepSizeMode(mode) {
|
||||
f.Data = f.Data[:oldSz]
|
||||
} else if len(f.Data) < int(off+size) {
|
||||
f.Data = f.Data[:off+size]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
return nil, fuse.FOPEN_KEEP_CACHE, OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
end := int64(len(data)) + off
|
||||
if int64(len(f.Data)) < end {
|
||||
n := make([]byte, end)
|
||||
copy(n, f.Data)
|
||||
f.Data = n
|
||||
}
|
||||
|
||||
copy(f.Data[off:off+int64(len(data))], data)
|
||||
|
||||
return uint32(len(data)), 0
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*MemRegularFile)(nil))
|
||||
|
||||
func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out.Attr = f.Attr
|
||||
out.Attr.Size = uint64(len(f.Data))
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if sz, ok := in.GetSize(); ok {
|
||||
f.Data = f.Data[:sz]
|
||||
}
|
||||
out.Attr = f.Attr
|
||||
out.Size = uint64(len(f.Data))
|
||||
return OK
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
end := int(off) + len(dest)
|
||||
if end > len(f.Data) {
|
||||
end = len(f.Data)
|
||||
}
|
||||
return fuse.ReadResultData(f.Data[off:end]), OK
|
||||
}
|
||||
|
||||
// MemSymlink is an inode holding a symlink in memory.
|
||||
type MemSymlink struct {
|
||||
Inode
|
||||
Attr fuse.Attr
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var _ = (NodeReadlinker)((*MemSymlink)(nil))
|
||||
|
||||
func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
|
||||
return l.Data, OK
|
||||
}
|
||||
|
||||
var _ = (NodeGetattrer)((*MemSymlink)(nil))
|
||||
|
||||
func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
out.Attr = l.Attr
|
||||
return OK
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_linux.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_linux.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func keepSizeMode(mode uint32) bool {
|
||||
return mode&unix.FALLOC_FL_KEEP_SIZE != 0
|
||||
}
|
||||
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_unix.go
generated
vendored
Normal file
11
vendor/github.com/hanwen/go-fuse/v2/fs/mem_unix.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !linux
|
||||
|
||||
// Copyright 2025 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
func keepSizeMode(mode uint32) bool {
|
||||
return false
|
||||
}
|
||||
40
vendor/github.com/hanwen/go-fuse/v2/fs/mount.go
generated
vendored
Normal file
40
vendor/github.com/hanwen/go-fuse/v2/fs/mount.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
// Mount mounts the given NodeFS on the directory, and starts serving
|
||||
// requests. This is a convenience wrapper around NewNodeFS and
|
||||
// fuse.NewServer. If nil is given as options, default settings are
|
||||
// applied, which are 1 second entry and attribute timeout.
|
||||
func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error) {
|
||||
if options == nil {
|
||||
oneSec := time.Second
|
||||
options = &Options{
|
||||
EntryTimeout: &oneSec,
|
||||
AttrTimeout: &oneSec,
|
||||
}
|
||||
}
|
||||
|
||||
rawFS := NewNodeFS(root, options)
|
||||
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go server.Serve()
|
||||
if err := server.WaitMount(); err != nil {
|
||||
// we don't shutdown the serve loop. If the mount does
|
||||
// not succeed, the loop won't work and exit.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
Reference in New Issue
Block a user