Initial commit

Proof-of-concept implementation. Bugs will occur.
This commit is contained in:
2026-02-12 01:18:46 +03:00
commit 13ac06c14b
553 changed files with 253003 additions and 0 deletions

75
vendor/github.com/hanwen/go-fuse/v2/AUTHORS generated vendored Normal file
View File

@@ -0,0 +1,75 @@
Adam Goode <agoode@google.com>
Adam H. Leventhal <adam.leventhal@gmail.com>
Alex Fishman <alex@fuse-t.org>
Amir Hardon <ahardon@gmail.com>
Andrew Chambers <ac@acha.ninja>
Brandon Duffany <brandon@buildbuddy.io>
C.U <github@wmchris.de>
Chris Marget <cmarget@mutualink.net>
Daniel Martí <mvdan@mvdan.cc>
Dmitriy Smotrov <dsxack@gmail.com>
Dustin Oprea <myselfasunder@gmail.com>
Ed Schouten <ed.schouten@prodrive-technologies.com>
Eliot Courtney <edcourtney@google.com>
Fazlul Shahriar <fshahriar@gmail.com>
Frederick Akalin <akalin@gmail.com>
Garret Kelly <gdk@google.com>
Glonee <glonee@foxmail.com>
Google Inc.
Grant Monroe <grant@tnarg.com>
Haitao Li <lihaitao@gmail.com>
Han-Wen Nienhuys <hanwenn@gmail.com>
Henry Wang <henwang@amazon.com>
Ivan Krasin <imkrasin@gmail.com>
Ivan Volosyuk <ivan.volosyuk@gmail.com>
Jakob Unterwurzacher <jakobunt@gmail.com>
James D. Nurmi <james@abneptis.com>
Jan Pfeifer <janpf@google.com>
Jeff <leterip@me.com>
Jeff Hodges <jeff@somethingsimilar.com>
Jille Timmermans <jille@quis.cx>
Johannes Brüderl <johannes.bruederl@gmail.com>
Jonathon Reinhart <Jonathon.Reinhart@gmail.com>
Kaoet Ibe <kaoet.ibe@outlook.com>
Kirill Smelkov <kirr@nexedi.com>
Kohei Tokunaga <ktokunaga.mail@gmail.com>
Levin Zimmermann <levin.zimmermann@nexedi.com>
Logan Hanks <logan@bitcasa.com>
Lucas Manning <lucas.manning21@gmail.com>
M. J. Fromberger <michael.j.fromberger@gmail.com>
Manuel Klimek <klimek@google.com>
Maria Shaldibina <mshaldibina@pivotal.io>
Mark Karpeles <magicaltux@gmail.com>
Mike Gray <mike@mikegray.org>
Natalie Fioretti <naadl.93+github@gmail.com>
Nick Cooper <gh@smoogle.org>
Nick Craig-Wood <nick@craig-wood.com>
OneOfOne <oneofone@gmail.com>
Orivej Desh <orivej@gmx.fr>
Patrick Crosby <pcrosby@gmail.com>
Paul Jolly <paul@myitcv.org.uk>
Paul Warren <paul.warren@emc.com>
Rueian <rueiancsie@gmail.com>
Ryan Guest <ryanguest@gmail.com>
Ryan Lamore <rlamore@salesforce.com>
Sebastien Binet <binet@cern.ch>
Shayan Pooya <shayan@arista.com>
Stavros Panakakis <stavrospanakakis@gmail.com>
Tamas Kerecsen <kerecsen@gmail.com>
Tiziano Santoro <tzn@google.com>
Tommy Lindgren <tommy.lindgren@gmail.com>
Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
Valient Gough <vgough@pobox.com>
WeidiDeng <weidi_deng@icloud.com>
Xiaoyi <ashi009@users.noreply.github.com>
Yasin Turan <turyasin@amazon.com>
Yongwoo Park <nnnlife@gmail.com>
Yufeng Cheng <chengyufeng@megvii.com>
ZheNing Hu <adlternative@gmail.com>
Zoey Greer <zoey@buildbuddy.io>
abitduck <abitduck@hotmail.com>
companycy <companycy@gmail.com>
hotaery <626910647@qq.com>
lch <lchopn@gmail.com>
midchildan <git@midchildan.org>
sunjiapeng <782615313@qq.com>

30
vendor/github.com/hanwen/go-fuse/v2/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,30 @@
New BSD License
Copyright (c) 2010 the Go-FUSE Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Ivan Krasin nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

50
vendor/github.com/hanwen/go-fuse/v2/fs/README.md generated vendored Normal file
View 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
View 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

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
View 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)
}

View 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
View 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
View 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
View 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)
}

View 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)
}

View 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
View 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
View 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)
}

View 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
View 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
View 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
View 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)
}

View 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
View 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
View 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
}

View 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)
}

View 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)
}

View 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
}

View 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
View 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
View 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
View 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
View 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
}

1
vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
version.gen.go

478
vendor/github.com/hanwen/go-fuse/v2/fuse/api.go generated vendored Normal file
View File

@@ -0,0 +1,478 @@
// Copyright 2016 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 fuse provides APIs to implement filesystems in
// userspace in terms of raw FUSE protocol.
//
// A filesystem is implemented by implementing its server that provides a
// RawFileSystem interface. Typically the server embeds
// NewDefaultRawFileSystem() and implements only subset of filesystem methods:
//
// type MyFS struct {
// fuse.RawFileSystem
// ...
// }
//
// func NewMyFS() *MyFS {
// return &MyFS{
// RawFileSystem: fuse.NewDefaultRawFileSystem(),
// ...
// }
// }
//
// // Mkdir implements "mkdir" request handler.
// //
// // For other requests - not explicitly implemented by MyFS - ENOSYS
// // will be typically returned to client.
// func (fs *MyFS) Mkdir(...) {
// ...
// }
//
// Then the filesystem can be mounted and served to a client (typically OS
// kernel) by creating Server:
//
// fs := NewMyFS() // implements RawFileSystem
// fssrv, err := fuse.NewServer(fs, mountpoint, &fuse.MountOptions{...})
// if err != nil {
// ...
// }
//
// and letting the server do its work:
//
// // either synchronously - .Serve() blocks until the filesystem is unmounted.
// fssrv.Serve()
//
// // or in the background - .Serve() is spawned in another goroutine, but
// // before interacting with fssrv from current context we have to wait
// // until the filesystem mounting is complete.
// go fssrv.Serve()
// err = fssrv.WaitMount()
// if err != nil {
// ...
// }
//
// The server will serve clients by dispatching their requests to the
// filesystem implementation and conveying responses back. For example "mkdir"
// FUSE request dispatches to call
//
// fs.Mkdir(*MkdirIn, ..., *EntryOut)
//
// "stat" to call
//
// fs.GetAttr(*GetAttrIn, *AttrOut)
//
// etc. Please refer to RawFileSystem documentation for details.
//
// Typically, each call of the API happens in its own
// goroutine, so take care to make the file system thread-safe.
//
// Be careful when you access the FUSE mount from the same process. An access can
// tie up two OS threads (one on the request side and one on the FUSE server side).
// This can deadlock if there is no free thread to handle the FUSE server side.
// Run your program with GOMAXPROCS=1 to make the problem easier to reproduce,
// see https://github.com/hanwen/go-fuse/issues/261 for an example of that
// problem.
//
// # Higher level interfaces
//
// As said above this packages provides way to implement filesystems in terms of
// raw FUSE protocol.
//
// Package github.com/hanwen/go-fuse/v2/fs provides way to implement
// filesystems in terms of paths and/or inodes.
//
// # Mount styles
//
// The NewServer() handles mounting the filesystem, which
// involves opening `/dev/fuse` and calling the
// `mount(2)` syscall. The latter needs root permissions.
// This is handled in one of three ways:
//
// 1) go-fuse opens `/dev/fuse` and executes the `fusermount`
// setuid-root helper to call `mount(2)` for us. This is the default.
// Does not need root permissions but needs `fusermount` installed.
//
// 2) If `MountOptions.DirectMount` is set, go-fuse calls `mount(2)` itself.
// Needs root permissions, but works without `fusermount`.
//
// 3) If `mountPoint` has the magic `/dev/fd/N` syntax, it means that that a
// privileged parent process:
//
// * Opened /dev/fuse
//
// * Called mount(2) on a real mountpoint directory that we don't know about
//
// * Inherited the fd to /dev/fuse to us
//
// * Informs us about the fd number via /dev/fd/N
//
// This magic syntax originates from libfuse [1] and allows the FUSE server to
// run without any privileges and without needing `fusermount`, as the parent
// process performs all privileged operations.
//
// The "privileged parent" is usually a container manager like Singularity [2],
// but for testing, it can also be the `mount.fuse3` helper with the
// `drop_privileges,setuid=$USER` flags. Example below for gocryptfs:
//
// $ sudo mount.fuse3 "/usr/local/bin/gocryptfs#/tmp/cipher" /tmp/mnt -o drop_privileges,setuid=$USER
//
// [1] https://github.com/libfuse/libfuse/commit/64e11073b9347fcf9c6d1eea143763ba9e946f70
//
// [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts
//
// # Aborting a file system
//
// A caller that has an open file in a buggy or crashed FUSE
// filesystem will be hung. The easiest way to clean up this situation
// is through the fusectl filesystem. By writing into
// /sys/fs/fuse/connection/$ID/abort, reads from the FUSE device fail,
// and all callers receive ENOTCONN (transport endpoint not connected)
// on their pending syscalls. The FUSE connection ID can be found as
// the Dev field in the Stat_t result for a file in the mount.
package fuse
import "log"
// Types for users to implement.
// The result of Read is an array of bytes, but for performance
// reasons, we can also return data as a file-descriptor/offset/size
// tuple. If the backing store for a file is another filesystem, this
// reduces the amount of copying between the kernel and the FUSE
// server. The ReadResult interface captures both cases.
type ReadResult interface {
// Returns the raw bytes for the read, possibly using the
// passed buffer. The buffer should be larger than the return
// value from Size.
Bytes(buf []byte) ([]byte, Status)
// Size returns how many bytes this return value takes at most.
Size() int
// Done() is called after sending the data to the kernel.
Done()
}
type MountOptions struct {
AllowOther bool
// Options are the options passed as -o string to fusermount.
Options []string
// MaxBackground controls the maximum number of allowed backgruond
// asynchronous I/O requests.
//
// If unset, the default is _DEFAULT_BACKGROUND_TASKS, 12.
// Concurrency for synchronous I/O is not limited.
MaxBackground int
// MaxWrite is the max size for read and write requests. If 0, use
// go-fuse default (currently 64 kiB).
// This number is internally capped at MAX_KERNEL_WRITE (higher values don't make
// sense).
//
// Non-direct-io reads are mostly served via kernel readahead, which is
// additionally subject to the MaxReadAhead limit.
//
// Implementation notes:
//
// There's four values the Linux kernel looks at when deciding the request size:
// * MaxWrite, passed via InitOut.MaxWrite. Limits the WRITE size.
// * max_read, passed via a string mount option. Limits the READ size.
// go-fuse sets max_read equal to MaxWrite.
// You can see the current max_read value in /proc/self/mounts .
// * MaxPages, passed via InitOut.MaxPages. In Linux 4.20 and later, the value
// can go up to 1 MiB and go-fuse calculates the MaxPages value acc.
// to MaxWrite, rounding up.
// On older kernels, the value is fixed at 128 kiB and the
// passed value is ignored. No request can be larger than MaxPages, so
// READ and WRITE are effectively capped at MaxPages.
// * MaxReadAhead, passed via InitOut.MaxReadAhead.
MaxWrite int
// MaxReadAhead is the max read ahead size to use. It controls how much data the
// kernel reads in advance to satisfy future read requests from applications.
// How much exactly is subject to clever heuristics in the kernel
// (see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/readahead.c?h=v6.2-rc5#n375
// if you are brave) and hence also depends on the kernel version.
//
// If 0, use kernel default. This number is capped at the kernel maximum
// (128 kiB on Linux) and cannot be larger than MaxWrite.
//
// MaxReadAhead only affects buffered reads (=non-direct-io), but even then, the
// kernel can and does send larger reads to satisfy read reqests from applications
// (up to MaxWrite or VM_READAHEAD_PAGES=128 kiB, whichever is less).
MaxReadAhead int
// IgnoreSecurityLabels, if set, makes security related xattr
// requests return NO_DATA without passing through the
// user defined filesystem. You should only set this if you
// file system implements extended attributes, and you are not
// interested in security labels.
IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option.
// RememberInodes, if set, makes go-fuse never forget inodes.
// This may be useful for NFS.
RememberInodes bool
// FsName is the name of the filesystem, shown in "df -T"
// and friends (as the first column, "Filesystem").
FsName string
// Name is the "fuse.<name>" suffix, shown in "df -T" and friends
// (as the second column, "Type")
Name string
// SingleThreaded, if set, wraps the file system in a single-threaded
// locking wrapper.
SingleThreaded bool
// DisableXAttrs, if set, returns ENOSYS for Getxattr calls, so the kernel
// does not issue any Xattr operations at all.
DisableXAttrs bool
// Debug, if set, enables verbose debugging information.
Debug bool
// Logger, if set, is an alternate log sink for debug statements.
//
// To increase signal/noise ratio Go-FUSE uses abbreviations in its debug log
// output. Here is how to read it:
//
// - `iX` means `inode X`;
// - `gX` means `generation X`;
// - `tA` and `tE` means timeout for attributes and directory entry correspondingly;
// - `[<off> +<size>)` means data range from `<off>` inclusive till `<off>+<size>` exclusive;
// - `Xb` means `X bytes`.
// - `pX` means the request originated from PID `x`. 0 means the request originated from the kernel.
//
// Every line is prefixed with either `rx <unique>` (receive from kernel) or `tx <unique>` (send to kernel)
//
// Example debug log output:
//
// rx 2: LOOKUP i1 [".wcfs"] 6b p5874
// tx 2: OK, {i3 g2 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:3 A 0.000000 M 0.000000 C 0.000000}}
// rx 3: LOOKUP i3 ["zurl"] 5b p5874
// tx 3: OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
// rx 4: OPEN i4 {O_RDONLY,0x8000} p5874
// tx 4: 38=function not implemented, {Fh 0 }
// rx 5: READ i4 {Fh 0 [0 +4096) L 0 RDONLY,0x8000} p5874
// tx 5: OK, 33b data "file:///"...
// rx 6: GETATTR i4 {Fh 0} p5874
// tx 6: OK, {tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
// rx 7: FLUSH i4 {Fh 0} p5874
// tx 7: OK
// rx 8: LOOKUP i1 ["head"] 5b p5874
// tx 8: OK, {i5 g4 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:5 A 0.000000 M 0.000000 C 0.000000}}
// rx 9: LOOKUP i5 ["bigfile"] 8b p5874
// tx 9: OK, {i6 g5 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:6 A 0.000000 M 0.000000 C 0.000000}}
// rx 10: FLUSH i4 {Fh 0} p5874
// tx 10: OK
// rx 11: GETATTR i1 {Fh 0} p5874
// tx 11: OK, {tA=1s {M040755 SZ=0 L=1 1000:1000 B0*0 i0:1 A 0.000000 M 0.000000 C 0.000000}}
Logger *log.Logger
// EnableLocks, if set, asks the kernel to forward file locks to FUSE
// When used, you must implement the GetLk/SetLk/SetLkw methods.
EnableLocks bool
// EnableSymlinkCaching, if set, makes the kernel cache all Readlink return values.
// The filesystem must use content notification to force the
// kernel to issue a new Readlink call.
EnableSymlinkCaching bool
// ExplicitDataCacheControl, if set, asks the kernel not to do automatic
// data cache invalidation. The filesystem is fully responsible for
// invalidating data cache.
ExplicitDataCacheControl bool
// SyncRead, if set, makes go-fuse enable the
// FUSE_CAP_ASYNC_READ capability.
// The kernel then submits multiple concurrent reads to service
// userspace requests and kernel readahead.
//
// Setting SyncRead disables the FUSE_CAP_ASYNC_READ capability.
// The kernel then only sends one read request per file handle at a time,
// and orders the requests by offset.
//
// This is useful if reading out of order or concurrently is expensive for
// (example: Amazon Cloud Drive).
//
// See the comment to FUSE_CAP_ASYNC_READ in
// https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// for more details.
SyncRead bool
// DirectMount, if set, makes go-fuse first attempt to use syscall.Mount instead of
// fusermount to mount the filesystem. This will not update /etc/mtab
// but might be needed if fusermount is not available.
// Also, Server.Unmount will attempt syscall.Unmount before calling
// fusermount.
DirectMount bool
// DirectMountStrict, if set, is like DirectMount but no fallback to fusermount is
// performed. If both DirectMount and DirectMountStrict are set,
// DirectMountStrict wins.
DirectMountStrict bool
// DirectMountFlags are the mountflags passed to syscall.Mount. If zero, the
// default value used by fusermount are used: syscall.MS_NOSUID|syscall.MS_NODEV.
//
// If you actually *want* zero flags, pass syscall.MS_MGC_VAL, which is ignored
// by the kernel. See `man 2 mount` for details about MS_MGC_VAL.
DirectMountFlags uintptr
// EnableAcl, if set, enables kernel ACL support.
//
// See the comments to FUSE_CAP_POSIX_ACL
// in https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// for details.
EnableAcl bool
// DisableReadDirPlus, if set, disables the ReadDirPlus capability so
// ReadDir is used instead. Simple directory queries (i.e. 'ls' without
// '-l') can be faster with ReadDir, as no per-file stat calls are needed.
DisableReadDirPlus bool
// DisableSplice, if set, disables splicing from files to the FUSE device.
DisableSplice bool
// MaxStackDepth is the maximum stacking depth for passthrough files.
// If unset, the default is 1.
MaxStackDepth int
// RawFileSystem, if set, enables an ID-mapped mount if the Kernel supports
// it.
//
// An ID-mapped mount allows the device to be mounted on the system with the
// IDs remapped (via mount_setattr, move_mount syscalls) to those of the
// user on the local system.
//
// Enabling this flag automatically sets the "default_permissions" mount
// option. This is required by FUSE to delegate the UID/GID-based permission
// checks to the kernel. For requests that create new inodes, FUSE will send
// the mapped UID/GIDs. For all other requests, FUSE will send "-1".
IDMappedMount bool
}
// RawFileSystem is an interface close to the FUSE wire protocol.
//
// Unless you really know what you are doing, you should not implement
// this, but rather the interfaces associated with
// fs.InodeEmbedder. The details of getting interactions with open
// files, renames, and threading right etc. are somewhat tricky and
// not very interesting.
//
// Each FUSE request results in a corresponding method called by Server.
// Several calls may be made simultaneously, because the server typically calls
// each method in separate goroutine.
//
// A null implementation is provided by NewDefaultRawFileSystem.
//
// After a successful FUSE API call returns, you may not read input or
// write output data: for performance reasons, memory is reused for
// following requests, and reading/writing the request data will lead
// to race conditions. If you spawn a background routine from a FUSE
// API call, any incoming request data it wants to reference should be
// copied over.
//
// If a FS operation is interrupted, the `cancel` channel is
// closed. The fileystem can honor this request by returning EINTR. In
// this case, the outstanding request data is not reused. Interrupts
// occur if the process accessing the file system receives any signal
// that is not ignored. In particular, the Go runtime uses signals to
// manage goroutine preemption, so Go programs under load naturally
// generate interupt opcodes when they access a FUSE filesystem.
type RawFileSystem interface {
String() string
// If called, provide debug output through the log package.
SetDebug(debug bool)
// Lookup is called by the kernel when the VFS wants to know
// about a file inside a directory. Many lookup calls can
// occur in parallel, but only one call happens for each (dir,
// name) pair.
Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (status Status)
// Forget is called when the kernel discards entries from its
// dentry cache. This happens on unmount, and when the kernel
// is short on memory. Since it is not guaranteed to occur at
// any moment, and since there is no return value, Forget
// should not do I/O, as there is no channel to report back
// I/O errors.
Forget(nodeid, nlookup uint64)
// Attributes.
GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status)
SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status)
// Modifying structure.
Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status)
Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status)
Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status)
Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status)
Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status)
Link(cancel <-chan struct{}, input *LinkIn, filename string, out *EntryOut) (code Status)
Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status)
Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status)
Access(cancel <-chan struct{}, input *AccessIn) (code Status)
// Extended attributes.
// GetXAttr reads an extended attribute, and should return the
// number of bytes. If the buffer is too small, return ERANGE,
// with the required buffer size.
GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (sz uint32, code Status)
// ListXAttr lists extended attributes as '\0' delimited byte
// slice, and return the number of bytes. If the buffer is too
// small, return ERANGE, with the required buffer size.
ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (uint32, Status)
// SetAttr writes an extended attribute.
SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status
// RemoveXAttr removes an extended attribute.
RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) (code Status)
// File handling.
Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status)
Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status)
Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status)
Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status
// File locking
GetLk(cancel <-chan struct{}, input *LkIn, out *LkOut) (code Status)
SetLk(cancel <-chan struct{}, input *LkIn) (code Status)
SetLkw(cancel <-chan struct{}, input *LkIn) (code Status)
Release(cancel <-chan struct{}, input *ReleaseIn)
Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status)
CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status)
Ioctl(cancel <-chan struct{}, input *IoctlIn, inbuf []byte, output *IoctlOut, outbuf []byte) (code Status)
Flush(cancel <-chan struct{}, input *FlushIn) Status
Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status)
Fallocate(cancel <-chan struct{}, input *FallocateIn) (code Status)
// Directory handling
OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status)
ReadDir(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status
ReadDirPlus(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status
ReleaseDir(input *ReleaseIn)
FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status)
StatFs(cancel <-chan struct{}, input *InHeader, out *StatfsOut) (code Status)
Statx(cancel <-chan struct{}, input *StatxIn, out *StatxOut) (code Status)
// This is called on processing the first request. The
// filesystem implementation can use the server argument to
// talk back to the kernel (through notify methods).
Init(*Server)
// Called after processing the last request.
OnUnmount()
}

79
vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go generated vendored Normal file
View File

@@ -0,0 +1,79 @@
// Copyright 2016 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 fuse
import (
"os"
"syscall"
"time"
)
func (a *Attr) IsFifo() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFIFO }
// IsChar reports whether the FileInfo describes a character special file.
func (a *Attr) IsChar() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFCHR }
// IsDir reports whether the FileInfo describes a directory.
func (a *Attr) IsDir() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFDIR }
// IsBlock reports whether the FileInfo describes a block special file.
func (a *Attr) IsBlock() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFBLK }
// IsRegular reports whether the FileInfo describes a regular file.
func (a *Attr) IsRegular() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFREG }
// IsSymlink reports whether the FileInfo describes a symbolic link.
func (a *Attr) IsSymlink() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFLNK }
// IsSocket reports whether the FileInfo describes a socket.
func (a *Attr) IsSocket() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFSOCK }
func (a *Attr) SetTimes(access *time.Time, mod *time.Time, chstatus *time.Time) {
if access != nil {
a.Atime = uint64(access.Unix())
a.Atimensec = uint32(access.Nanosecond())
}
if mod != nil {
a.Mtime = uint64(mod.Unix())
a.Mtimensec = uint32(mod.Nanosecond())
}
if chstatus != nil {
a.Ctime = uint64(chstatus.Unix())
a.Ctimensec = uint32(chstatus.Nanosecond())
}
}
func (a *Attr) ChangeTime() time.Time {
return time.Unix(int64(a.Ctime), int64(a.Ctimensec))
}
func (a *Attr) AccessTime() time.Time {
return time.Unix(int64(a.Atime), int64(a.Atimensec))
}
func (a *Attr) ModTime() time.Time {
return time.Unix(int64(a.Mtime), int64(a.Mtimensec))
}
func ToStatT(f os.FileInfo) *syscall.Stat_t {
s, _ := f.Sys().(*syscall.Stat_t)
if s != nil {
return s
}
return nil
}
func ToAttr(f os.FileInfo) *Attr {
if f == nil {
return nil
}
s := ToStatT(f)
if s != nil {
a := &Attr{}
a.FromStat(s)
return a
}
return nil
}

51
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
// Copyright 2016 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 fuse
import (
"syscall"
"golang.org/x/sys/unix"
)
func (a *Attr) FromStat(s *syscall.Stat_t) {
a.Ino = uint64(s.Ino)
a.Size = uint64(s.Size)
a.Blocks = uint64(s.Blocks)
a.Atime = uint64(s.Atim.Sec)
a.Atimensec = uint32(s.Atim.Nsec)
a.Mtime = uint64(s.Mtim.Sec)
a.Mtimensec = uint32(s.Mtim.Nsec)
a.Ctime = uint64(s.Ctim.Sec)
a.Ctimensec = uint32(s.Ctim.Nsec)
a.Mode = s.Mode
a.Nlink = uint32(s.Nlink)
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Rdev = uint32(s.Rdev)
a.Blksize = uint32(s.Blksize)
}
func (a *Statx) FromStatx(s *unix.Statx_t) {
a.Ino = uint64(s.Ino)
a.Size = uint64(s.Size)
a.Blocks = uint64(s.Blocks)
a.Atime.FromStatxTimestamp(&s.Atime)
a.Btime.FromStatxTimestamp(&s.Btime)
a.Ctime.FromStatxTimestamp(&s.Ctime)
a.Mtime.FromStatxTimestamp(&s.Mtime)
a.Mode = s.Mode
a.Nlink = uint32(s.Nlink)
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Blksize = uint32(s.Blksize)
a.AttributesMask = s.Attributes_mask
a.Mask = s.Mask
a.Attributes = s.Attributes
a.RdevMinor = s.Rdev_minor
a.RdevMajor = s.Rdev_major
a.DevMajor = s.Dev_major
a.DevMinor = s.Dev_minor
}

29
vendor/github.com/hanwen/go-fuse/v2/fuse/attr_unix.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
//go:build !linux
// 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 fuse
import (
"syscall"
)
func (a *Attr) FromStat(s *syscall.Stat_t) {
a.Ino = uint64(s.Ino)
a.Size = uint64(s.Size)
a.Blocks = uint64(s.Blocks)
a.Atime = uint64(s.Atimespec.Sec)
a.Atimensec = uint32(s.Atimespec.Nsec)
a.Mtime = uint64(s.Mtimespec.Sec)
a.Mtimensec = uint32(s.Mtimespec.Nsec)
a.Ctime = uint64(s.Ctimespec.Sec)
a.Ctimensec = uint32(s.Ctimespec.Nsec)
a.Mode = uint32(s.Mode)
a.Nlink = uint32(s.Nlink)
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Rdev = uint32(s.Rdev)
a.Blksize = uint32(s.Blksize)
}

82
vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go generated vendored Normal file
View File

@@ -0,0 +1,82 @@
// Copyright 2016 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 fuse
import (
"os"
"sync"
)
// bufferPool implements explicit memory management. It is used for
// minimizing the GC overhead of communicating with the kernel.
type bufferPool struct {
lock sync.Mutex
// For each page size multiple a list of slice pointers.
buffersBySize []*sync.Pool
// Number of outstanding allocations. Used for testing.
countersBySize []int
}
var pageSize = os.Getpagesize()
func (p *bufferPool) counters() []int {
p.lock.Lock()
defer p.lock.Unlock()
d := make([]int, len(p.countersBySize))
copy(d, p.countersBySize)
return d
}
func (p *bufferPool) getPool(pageCount int, delta int) *sync.Pool {
p.lock.Lock()
defer p.lock.Unlock()
for len(p.buffersBySize) <= pageCount {
p.buffersBySize = append(p.buffersBySize, nil)
p.countersBySize = append(p.countersBySize, 0)
}
if p.buffersBySize[pageCount] == nil {
p.buffersBySize[pageCount] = &sync.Pool{
New: func() interface{} { return make([]byte, pageSize*pageCount) },
}
}
p.countersBySize[pageCount] += delta
return p.buffersBySize[pageCount]
}
// AllocBuffer creates a buffer of at least the given size. After use,
// it should be deallocated with FreeBuffer().
func (p *bufferPool) AllocBuffer(size uint32) []byte {
sz := int(size)
if sz < pageSize {
sz = pageSize
}
if sz%pageSize != 0 {
sz += pageSize
}
pages := sz / pageSize
b := p.getPool(pages, 1).Get().([]byte)
return b[:size]
}
// FreeBuffer takes back a buffer if it was allocated through
// AllocBuffer. It is not an error to call FreeBuffer() on a slice
// obtained elsewhere.
func (p *bufferPool) FreeBuffer(slice []byte) {
if slice == nil {
return
}
if cap(slice)%pageSize != 0 || cap(slice) == 0 {
return
}
pages := cap(slice) / pageSize
slice = slice[:cap(slice)]
p.getPool(pages, -1).Put(slice)
}

44
vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2016 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 fuse
import (
"os"
"syscall"
)
const (
FUSE_ROOT_ID = 1
FUSE_UNKNOWN_INO = 0xffffffff
CUSE_UNRESTRICTED_IOCTL = (1 << 0)
FUSE_LK_FLOCK = (1 << 0)
FUSE_RELEASE_FLUSH = (1 << 0)
FUSE_RELEASE_FLOCK_UNLOCK = (1 << 1)
FUSE_IOCTL_MAX_IOV = 256
FUSE_POLL_SCHEDULE_NOTIFY = (1 << 0)
CUSE_INIT_INFO_MAX = 4096
S_IFDIR = syscall.S_IFDIR
S_IFREG = syscall.S_IFREG
S_IFLNK = syscall.S_IFLNK
S_IFIFO = syscall.S_IFIFO
CUSE_INIT = 4096
O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC)
// FMODE_EXEC is a passed to OPEN requests if the file is
// being executed.
FMODE_EXEC = 0x20
logicalBlockSize = 512
)

View File

@@ -0,0 +1,9 @@
// Copyright 2016 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 fuse
// arbitrary values
const syscall_O_LARGEFILE = 1 << 29
const syscall_O_NOATIME = 1 << 30

View File

@@ -0,0 +1,12 @@
// Copyright 2016 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 fuse
import (
"syscall"
)
const syscall_O_LARGEFILE = syscall.O_LARGEFILE
const syscall_O_NOATIME = syscall.O_NOATIME

61
vendor/github.com/hanwen/go-fuse/v2/fuse/context.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// Copyright 2016 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 fuse
import (
"context"
"time"
)
// Context passes along cancelation signal and request data (PID, GID,
// UID). The name of this class predates the standard "context"
// package from Go, but it does implement the context.Context
// interface.
//
// When a FUSE request is canceled, and the file system chooses to honor
// the cancellation, the response should be EINTR.
type Context struct {
Caller
Cancel <-chan struct{}
}
func (c *Context) Deadline() (time.Time, bool) {
return time.Time{}, false
}
func (c *Context) Done() <-chan struct{} {
return c.Cancel
}
func (c *Context) Err() error {
select {
case <-c.Cancel:
return context.Canceled
default:
return nil
}
}
type callerKeyType struct{}
var callerKey callerKeyType
func FromContext(ctx context.Context) (*Caller, bool) {
v, ok := ctx.Value(callerKey).(*Caller)
return v, ok
}
func NewContext(ctx context.Context, caller *Caller) context.Context {
return context.WithValue(ctx, callerKey, caller)
}
func (c *Context) Value(key interface{}) interface{} {
if key == callerKey {
return &c.Caller
}
return nil
}
var _ = context.Context((*Context)(nil))

180
vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go generated vendored Normal file
View File

@@ -0,0 +1,180 @@
// Copyright 2016 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 fuse
import (
"os"
)
// NewDefaultRawFileSystem returns ENOSYS (not implemented) for all
// operations.
func NewDefaultRawFileSystem() RawFileSystem {
return (*defaultRawFileSystem)(nil)
}
type defaultRawFileSystem struct{}
func (fs *defaultRawFileSystem) Init(*Server) {
}
func (fs *defaultRawFileSystem) OnUnmount() {
}
func (fs *defaultRawFileSystem) String() string {
return os.Args[0]
}
func (fs *defaultRawFileSystem) SetDebug(dbg bool) {
}
func (fs *defaultRawFileSystem) StatFs(cancel <-chan struct{}, header *InHeader, out *StatfsOut) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Forget(nodeID, nlookup uint64) {
}
func (fs *defaultRawFileSystem) GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) {
return OK
}
func (fs *defaultRawFileSystem) SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) {
return nil, ENOSYS
}
func (fs *defaultRawFileSystem) Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Link(cancel <-chan struct{}, input *LinkIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (size uint32, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (n uint32, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) Access(cancel <-chan struct{}, input *AccessIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) {
return nil, ENOSYS
}
func (fs *defaultRawFileSystem) GetLk(cancel <-chan struct{}, in *LkIn, out *LkOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) SetLk(cancel <-chan struct{}, in *LkIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) SetLkw(cancel <-chan struct{}, in *LkIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Release(cancel <-chan struct{}, input *ReleaseIn) {
}
func (fs *defaultRawFileSystem) Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) Flush(cancel <-chan struct{}, input *FlushIn) Status {
return OK
}
func (fs *defaultRawFileSystem) Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReadDir(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReadDirPlus(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReleaseDir(input *ReleaseIn) {
}
func (fs *defaultRawFileSystem) FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Fallocate(cancel <-chan struct{}, in *FallocateIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) Ioctl(cancel <-chan struct{}, input *IoctlIn, inbuf []byte, output *IoctlOut, outbuf []byte) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) Statx(cancel <-chan struct{}, input *StatxIn, out *StatxOut) (code Status) {
return ENOSYS
}

185
vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go generated vendored Normal file
View File

@@ -0,0 +1,185 @@
// Copyright 2016 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 fuse
import (
"bytes"
"fmt"
"unsafe"
)
const direntSize = int(unsafe.Sizeof(_Dirent{}))
// DirEntry is a type for PathFileSystem and NodeFileSystem to return
// directory contents in.
type DirEntry struct {
// Mode is the file's mode. Only the high bits (eg. S_IFDIR)
// are considered.
Mode uint32
// Name is the basename of the file in the directory.
Name string
// Ino is the inode number.
Ino uint64
// Off is the offset in the directory stream. The offset is
// thought to be after the entry.
Off uint64
}
func (d *DirEntry) String() string {
return fmt.Sprintf("%d: %q ino=%d (%o)", d.Off, d.Name, d.Ino, d.Mode)
}
// Parse reads an entry from getdents(2) buffer. It returns the number
// of bytes consumed.
func (d *DirEntry) Parse(buf []byte) int {
// We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic
// "converted pointer straddles multiple allocations"
de := (*dirent)(unsafe.Pointer(&buf[0]))
off := unsafe.Offsetof(dirent{}.Name)
nameBytes := buf[off : off+uintptr(de.nameLength())]
n := de.Reclen
l := bytes.IndexByte(nameBytes, 0)
if l >= 0 {
nameBytes = nameBytes[:l]
}
*d = DirEntry{
Ino: de.Ino,
Mode: (uint32(de.Type) << 12),
Name: string(nameBytes),
Off: uint64(de.Off),
}
return int(n)
}
// DirEntryList holds the return value for READDIR and READDIRPLUS
// opcodes.
type DirEntryList struct {
buf []byte
// capacity of the underlying buffer
size int
// Offset holds the offset for the next entry to be added. It
// is the offset supplied at construction time, or the Offset
// of the last DirEntry that was added.
Offset uint64
// pointer to the last serialized _Dirent. Used by FixMode().
lastDirent *_Dirent
}
// NewDirEntryList creates a DirEntryList with the given data buffer
// and offset.
func NewDirEntryList(data []byte, off uint64) *DirEntryList {
return &DirEntryList{
buf: data[:0],
size: len(data),
Offset: off,
}
}
// AddDirEntry tries to add an entry, and reports whether it
// succeeded. If adding a 0 offset entry, the offset is taken to be
// the last offset + 1.
func (l *DirEntryList) AddDirEntry(e DirEntry) bool {
// TODO: take pointer arg, merge with AddDirLookupEntry.
return l.addDirEntry(&e, 0)
}
func (l *DirEntryList) addDirEntry(e *DirEntry, prefix int) bool {
if e.Ino == 0 {
e.Ino = FUSE_UNKNOWN_INO
}
if e.Off == 0 {
e.Off = l.Offset + 1
}
padding := (8 - len(e.Name)&7) & 7
delta := padding + direntSize + len(e.Name) + prefix
oldLen := len(l.buf)
newLen := delta + oldLen
if newLen > l.size {
return false
}
l.buf = l.buf[:newLen]
oldLen += prefix
dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen]))
dirent.Off = e.Off
dirent.Ino = e.Ino
dirent.NameLen = uint32(len(e.Name))
dirent.Typ = modeToType(e.Mode)
oldLen += direntSize
copy(l.buf[oldLen:], e.Name)
oldLen += len(e.Name)
if padding > 0 {
l.buf[oldLen] = 0
}
l.Offset = dirent.Off
return true
}
// Add adds a direntry to the DirEntryList, returning wheither it
// succeeded. Prefix is the amount of padding to add before the DirEntry.
//
// Deprecated: use AddDirLookupEntry or AddDirEntry.
func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) bool {
// TODO: remove.
e := DirEntry{
Name: name,
Mode: mode,
Off: l.Offset + 1,
Ino: inode,
}
return l.addDirEntry(&e, prefix)
}
// AddDirLookupEntry is used for ReadDirPlus. If reserves and zeroes
// space for an EntryOut struct and serializes the DirEntry. If adding
// a 0 offset entry, the offset is taken to be the last offset + 1.
// If the entry does not fit, it returns nil.
func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut {
// The resulting READDIRPLUS output buffer looks like this in memory:
// 1) EntryOut{}
// 2) _Dirent{}
// 3) Name (null-terminated)
// 4) Padding to align to 8 bytes
// [repeat]
// TODO: should take pointer as argument.
const entryOutSize = int(unsafe.Sizeof(EntryOut{}))
oldLen := len(l.buf)
ok := l.addDirEntry(&e, entryOutSize)
if !ok {
return nil
}
l.lastDirent = (*_Dirent)(unsafe.Pointer(&l.buf[oldLen+entryOutSize]))
entryOut := (*EntryOut)(unsafe.Pointer(&l.buf[oldLen]))
*entryOut = EntryOut{}
return entryOut
}
// modeToType converts a file *mode* (as used in syscall.Stat_t.Mode)
// to a file *type* (as used in _Dirent.Typ).
// Equivalent to IFTODT() in libc (see man 5 dirent).
func modeToType(mode uint32) uint32 {
return (mode & 0170000) >> 12
}
// FixMode overrides the file mode of the last direntry that was added. This can
// be needed when a directory changes while READDIRPLUS is running.
// Only the file type bits of mode are considered, the rest is masked out.
func (l *DirEntryList) FixMode(mode uint32) {
l.lastDirent.Typ = modeToType(mode)
}
func (l *DirEntryList) bytes() []byte {
return l.buf
}

View File

@@ -0,0 +1,19 @@
// 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 fuse
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Namlen uint16
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func (de *dirent) nameLength() int {
return int(de.Namlen)
}

View File

@@ -0,0 +1,20 @@
// 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 fuse
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Type uint8
Pad0 uint8
Namlen uint16
Pad1 uint16
Name [1]int8
}
func (de *dirent) nameLength() int {
return int(de.Namlen)
}

View File

@@ -0,0 +1,22 @@
// 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 fuse
import "unsafe"
// Like syscall.Dirent, but without the [256]byte name. This is
// equivalent to the linux_dirent64 struct, returned from the
// getdents64 syscall.
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func (de *dirent) nameLength() int {
return int(de.Reclen) - int(unsafe.Offsetof(dirent{}.Name))
}

89
vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2016 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.
// Random odds and ends.
package fuse
import (
"fmt"
"log"
"os"
"syscall"
"time"
)
func (code Status) String() string {
if code <= 0 {
return []string{
"OK",
"NOTIFY_POLL",
"NOTIFY_INVAL_INODE",
"NOTIFY_INVAL_ENTRY",
"NOTIFY_STORE_CACHE",
"NOTIFY_RETRIEVE_CACHE",
"NOTIFY_DELETE",
}[-code]
}
return fmt.Sprintf("%d=%v", int(code), syscall.Errno(code))
}
func (code Status) Ok() bool {
return code == OK
}
// ToStatus extracts an errno number from Go error objects. If it
// fails, it logs an error and returns ENOSYS.
func ToStatus(err error) Status {
switch err {
case nil:
return OK
case os.ErrPermission:
return EPERM
case os.ErrExist:
return Status(syscall.EEXIST)
case os.ErrNotExist:
return ENOENT
case os.ErrInvalid:
return EINVAL
}
switch t := err.(type) {
case syscall.Errno:
return Status(t)
case *os.SyscallError:
return Status(t.Err.(syscall.Errno))
case *os.PathError:
return ToStatus(t.Err)
case *os.LinkError:
return ToStatus(t.Err)
}
log.Println("can't convert error type:", err)
return ENOSYS
}
func CurrentOwner() *Owner {
return &Owner{
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}
}
// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a
// "Timespec" that can be passed to the utimensat syscall.
// A nil pointer is converted to the special UTIME_OMIT value.
//
// Deprecated: use unix.TimeToTimespec from the x/sys/unix package instead.
func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) {
if t == nil {
ts.Nsec = _UTIME_OMIT
} else {
ts = syscall.NsecToTimespec(t.UnixNano())
// Go bug https://github.com/golang/go/issues/12777
if ts.Nsec < 0 {
ts.Nsec = 0
}
}
return ts
}

View File

@@ -0,0 +1,4 @@
package fuse
// Ref: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/stat.h#L576
const _UTIME_OMIT = -2

View File

@@ -0,0 +1,7 @@
//go:build !darwin
package fuse
import "golang.org/x/sys/unix"
const _UTIME_OMIT = unix.UTIME_OMIT

72
vendor/github.com/hanwen/go-fuse/v2/fuse/mount.go generated vendored Normal file
View File

@@ -0,0 +1,72 @@
package fuse
import (
"fmt"
"net"
"os"
"syscall"
)
var reservedFDs []*os.File
func init() {
// Both Darwin and Linux invoke a subprocess with one
// inherited file descriptor to create the mount. To protect
// against deadlock, we must ensure that file descriptor 3
// never points to a FUSE filesystem. We do this by simply
// grabbing fd 3 and never releasing it. (This is not
// completely foolproof: a preceding init routine could grab fd 3,
// and then release it later.)
for {
r, w, err := os.Pipe()
if err != nil {
panic(fmt.Sprintf("os.Pipe(): %v", err))
}
w.Close()
if r.Fd() > 3 {
r.Close()
break
}
reservedFDs = append(reservedFDs, r)
}
}
func getConnection(local *os.File) (int, error) {
conn, err := net.FileConn(local)
if err != nil {
return 0, err
}
defer conn.Close()
unixConn := conn.(*net.UnixConn)
var data [4]byte
control := make([]byte, 4*256)
_, oobn, _, _, err := unixConn.ReadMsgUnix(data[:], control[:])
if err != nil {
return 0, err
}
messages, err := syscall.ParseSocketControlMessage(control[:oobn])
if err != nil {
return 0, err
}
if len(messages) != 1 {
return 0, fmt.Errorf("getConnection: expect 1 control message, got %#v", messages)
}
message := messages[0]
fds, err := syscall.ParseUnixRights(&message)
if err != nil {
return 0, err
}
if len(fds) != 1 {
return 0, fmt.Errorf("getConnection: expect 1 fd, got %#v", fds)
}
fd := fds[0]
if fd < 0 {
return 0, fmt.Errorf("getConnection: fd < 0: %d", fd)
}
return fd, nil
}

View File

@@ -0,0 +1,114 @@
// Copyright 2016 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 fuse
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
func getMaxWrite() int {
return 1 << 20
}
func unixgramSocketpair() (l, r *os.File, err error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, os.NewSyscallError("socketpair",
err.(syscall.Errno))
}
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
return
}
// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
local, remote, err := unixgramSocketpair()
if err != nil {
return
}
defer local.Close()
defer remote.Close()
bin, err := fusermountBinary()
if err != nil {
return 0, err
}
cmd := exec.Command(bin,
"-o", strings.Join(opts.optionsStrings(), ","),
"-o", fmt.Sprintf("iosize=%d", opts.MaxWrite),
mountPoint)
cmd.ExtraFiles = []*os.File{remote} // fd would be (index + 3)
cmd.Env = append(os.Environ(),
"_FUSE_CALL_BY_LIB=",
"_FUSE_DAEMON_PATH="+os.Args[0],
"_FUSE_COMMFD=3",
"_FUSE_COMMVERS=2",
"MOUNT_OSXFUSE_CALL_BY_LIB=",
"MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0])
var out, errOut bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &errOut
if err = cmd.Start(); err != nil {
return
}
fd, err = getConnection(local)
if err != nil {
return -1, err
}
go func() {
// On macos, mount_osxfuse is not just a suid
// wrapper. It interacts with the FS setup, and will
// not exit until the filesystem has successfully
// responded to INIT and STATFS. This means we can
// only wait on the mount_osxfuse process after the
// server has fully started
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s",
err, errOut.String(), out.String())
}
ready <- err
close(ready)
}()
// golang sets CLOEXEC on file descriptors when they are
// acquired through normal operations (e.g. open).
// Buf for fd, we have to set CLOEXEC manually
syscall.CloseOnExec(fd)
return fd, err
}
func unmount(dir string, opts *MountOptions) error {
return syscall.Unmount(dir, 0)
}
func fusermountBinary() (string, error) {
binPaths := []string{
"/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
"/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
}
for _, path := range binPaths {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("no FUSE mount utility found")
}

View File

@@ -0,0 +1,146 @@
package fuse
import (
"fmt"
"os"
"strings"
"syscall"
)
func getMaxWrite() int {
return 1 << 20
}
func callMountFuseFs(mountPoint string, opts *MountOptions) (devFuseFd int, err error) {
bin, err := fusermountBinary()
if err != nil {
return -1, err
}
// Convenience for cleaning up open FDs in the event of an error.
closeFd := func(fd int) {
if err != nil {
syscall.Close(fd)
}
}
// Use syscall.Open instead of os.OpenFile to avoid Go garbage collecting an
// [os.File], which will close the FD later, but we need to keep it open for
// the life of the FUSE mount.
const devFuse = "/dev/fuse"
devFuseFd, err = syscall.Open(devFuse, syscall.O_RDWR, 0)
if err != nil {
return -1, fmt.Errorf(
"failed to get a RDWR file descriptor for %s: %w",
devFuse, err)
}
defer closeFd(devFuseFd)
// We will also defensively direct the forked process's stdin to the null
// device to prevent any unintended side effects.
devNullFd, err := syscall.Open(os.DevNull, syscall.O_RDONLY, 0)
if err != nil {
return -1, fmt.Errorf(
"failed to get a RDONLY file descriptor for %s: %w",
os.DevNull, err)
}
defer closeFd(devNullFd)
// Use syscall.ForkExec directly instead of exec.Command because we need to
// pass raw file descriptors to the child, rather than a garbage collected
// os.File.
env := []string{"MOUNT_FUSEFS_CALL_BY_LIB=1"}
argv := []string{
bin,
"--safe",
"-o", strings.Join(opts.optionsStrings(), ","),
"3",
mountPoint,
}
fds := []uintptr{
uintptr(devNullFd),
uintptr(os.Stdout.Fd()),
uintptr(os.Stderr.Fd()),
uintptr(devFuseFd),
}
pid, err := syscall.ForkExec(bin, argv, &syscall.ProcAttr{
Env: env,
Files: fds,
})
if err != nil {
return -1, fmt.Errorf("failed to fork mount_fusefs: %w", err)
}
var ws syscall.WaitStatus
_, err = syscall.Wait4(pid, &ws, 0, nil)
if err != nil {
return -1, fmt.Errorf(
"failed to wait for exit status of mount_fusefs: %w", err)
}
if ws.ExitStatus() != 0 {
return -1, fmt.Errorf(
"mount_fusefs: exited with status %d", ws.ExitStatus())
}
return devFuseFd, nil
}
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
// Note: opts.DirectMount is not supported in FreeBSD, but the intended
// behavior is to *attempt* a direct mount when it's set, not to return an
// error. So in this case, we just ignore it and use the binary from
// fusermountBinary().
// Using the same logic from libfuse to prevent chaos
for {
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0o000)
if err != nil {
return -1, err
}
if f.Fd() > 2 {
f.Close()
break
}
}
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
// works.
fd = parseFuseFd(mountPoint)
if fd >= 0 {
if opts.Debug {
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
}
} else {
// Usual case: mount via the `fusermount` suid helper
fd, err = callMountFuseFs(mountPoint, opts)
if err != nil {
return
}
}
// golang sets CLOEXEC on file descriptors when they are
// acquired through normal operations (e.g. open).
// However, for raw FDs, we have to set CLOEXEC manually.
syscall.CloseOnExec(fd)
close(ready)
return fd, err
}
func unmount(mountPoint string, opts *MountOptions) (err error) {
_ = opts
return syscall.Unmount(mountPoint, 0)
}
func fusermountBinary() (string, error) {
binPaths := []string{
"/sbin/mount_fusefs",
}
for _, path := range binPaths {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("no FUSE mount utility found")
}

279
vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go generated vendored Normal file
View File

@@ -0,0 +1,279 @@
// Copyright 2016 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 fuse
import (
"bytes"
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
)
func unixgramSocketpair() (l, r *os.File, err error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
if err != nil {
return nil, nil, os.NewSyscallError("socketpair",
err.(syscall.Errno))
}
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
return
}
// Create a FUSE FS on the specified mount point without using
// fusermount.
func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd
if err != nil {
return
}
// managed to open dev/fuse, attempt to mount
source := opts.FsName
if source == "" {
source = opts.Name
}
var flags uintptr = syscall.MS_NOSUID | syscall.MS_NODEV
if opts.DirectMountFlags != 0 {
flags = opts.DirectMountFlags
}
var st syscall.Stat_t
err = syscall.Stat(mountPoint, &st)
if err != nil {
return
}
// some values we need to pass to mount - we do as fusermount does.
// override possible since opts.Options comes after.
//
// Only the options listed below are understood by the kernel:
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fuse/inode.c#L772
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fs_context.c#L41
// https://elixir.bootlin.com/linux/v6.14.2/source/fs/fs_context.c#L50
// Everything else will cause an EINVAL error from syscall.Mount() and
// a corresponding error message in the kernel logs.
var r = []string{
fmt.Sprintf("fd=%d", fd),
fmt.Sprintf("rootmode=%o", st.Mode&syscall.S_IFMT),
fmt.Sprintf("user_id=%d", os.Geteuid()),
fmt.Sprintf("group_id=%d", os.Getegid()),
// match what we do with fusermount
fmt.Sprintf("max_read=%d", opts.MaxWrite),
}
// In syscall.Mount(), [no]dev/suid/exec must be
// passed as bits in the flags parameter, not as strings.
for _, o := range opts.Options {
switch o {
case "nodev":
flags |= syscall.MS_NODEV
case "dev":
flags &^= syscall.MS_NODEV
case "nosuid":
flags |= syscall.MS_NOSUID
case "suid":
flags &^= syscall.MS_NOSUID
case "noexec":
flags |= syscall.MS_NOEXEC
case "exec":
flags &^= syscall.MS_NOEXEC
default:
r = append(r, o)
}
}
if opts.AllowOther {
r = append(r, "allow_other")
}
if opts.IDMappedMount && !opts.containsOption("default_permissions") {
r = append(r, "default_permissions")
}
if opts.Debug {
opts.Logger.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)",
source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
}
err = syscall.Mount(source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
if err != nil {
syscall.Close(fd)
return
}
// success
close(ready)
return
}
// callFusermount calls the `fusermount` suid helper with the right options so
// that it:
// * opens `/dev/fuse`
// * mount()s this file descriptor to `mountPoint`
// * passes this file descriptor back to us via a unix domain socket
// This file descriptor is returned as `fd`.
func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) {
local, remote, err := unixgramSocketpair()
if err != nil {
return
}
defer local.Close()
defer remote.Close()
bin, err := fusermountBinary()
if err != nil {
return 0, err
}
cmd := []string{bin, mountPoint}
if s := opts.optionsStrings(); len(s) > 0 {
cmd = append(cmd, "-o", strings.Join(s, ","))
}
if opts.Debug {
opts.Logger.Printf("callFusermount: executing %q", cmd)
}
proc, err := os.StartProcess(bin,
cmd,
&os.ProcAttr{
Env: []string{"_FUSE_COMMFD=3"},
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr, remote}})
if err != nil {
return
}
w, err := proc.Wait()
if err != nil {
return
}
if !w.Success() {
err = fmt.Errorf("fusermount exited with code %v\n", w.Sys())
return
}
fd, err = getConnection(local)
if err != nil {
return -1, err
}
return
}
// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
if opts.DirectMount || opts.DirectMountStrict {
fd, err := mountDirect(mountPoint, opts, ready)
if err == nil {
return fd, nil
} else if opts.Debug {
opts.Logger.Printf("mount: failed to do direct mount: %s", err)
}
if opts.DirectMountStrict {
return -1, err
}
}
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
// works.
fd = parseFuseFd(mountPoint)
if fd >= 0 {
if opts.Debug {
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
}
} else {
// Usual case: mount via the `fusermount` suid helper
fd, err = callFusermount(mountPoint, opts)
if err != nil {
return
}
}
// golang sets CLOEXEC on file descriptors when they are
// acquired through normal operations (e.g. open).
// Buf for fd, we have to set CLOEXEC manually
syscall.CloseOnExec(fd)
close(ready)
return fd, err
}
func unmount(mountPoint string, opts *MountOptions) (err error) {
if opts.DirectMount || opts.DirectMountStrict {
// Attempt to directly unmount, if fails fallback to fusermount method
err := syscall.Unmount(mountPoint, 0)
if err == nil {
return nil
}
if opts.DirectMountStrict {
return err
}
}
bin, err := fusermountBinary()
if err != nil {
return err
}
errBuf := bytes.Buffer{}
cmd := exec.Command(bin, "-u", mountPoint)
cmd.Stderr = &errBuf
if opts.Debug {
opts.Logger.Printf("unmount: executing %q", cmd.Args)
}
err = cmd.Run()
if errBuf.Len() > 0 {
return fmt.Errorf("%s (code %v)\n",
errBuf.String(), err)
}
return err
}
// lookPathFallback - search binary in PATH and, if that fails,
// in fallbackDir. This is useful if PATH is possible empty.
func lookPathFallback(file string, fallbackDir string) (string, error) {
binPath, err := exec.LookPath(file)
if err == nil {
return binPath, nil
}
abs := path.Join(fallbackDir, file)
return exec.LookPath(abs)
}
// fusermountBinary returns the path to the `fusermount3` binary, or, if not
// found, the `fusermount` binary.
func fusermountBinary() (string, error) {
if path, err := lookPathFallback("fusermount3", "/bin"); err == nil {
return path, nil
}
return lookPathFallback("fusermount", "/bin")
}
func umountBinary() (string, error) {
return lookPathFallback("umount", "/bin")
}
func getMaxWrite() int {
return maxPageLimit() * syscall.Getpagesize()
}
func maxPageLimit() (lim int) {
lim = 256
d, err := os.ReadFile("/proc/sys/fs/fuse/max_pages_limit")
if err != nil {
return lim
}
newLim, err := strconv.Atoi(string(d))
if err != nil {
return lim
}
return newLim
}

755
vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go generated vendored Normal file
View File

@@ -0,0 +1,755 @@
// Copyright 2016 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 fuse
import (
"bytes"
"fmt"
"log"
"runtime"
"syscall"
"unsafe"
)
const (
_OP_LOOKUP = uint32(1)
_OP_FORGET = uint32(2)
_OP_GETATTR = uint32(3)
_OP_SETATTR = uint32(4)
_OP_READLINK = uint32(5)
_OP_SYMLINK = uint32(6)
_OP_MKNOD = uint32(8)
_OP_MKDIR = uint32(9)
_OP_UNLINK = uint32(10)
_OP_RMDIR = uint32(11)
_OP_RENAME = uint32(12)
_OP_LINK = uint32(13)
_OP_OPEN = uint32(14)
_OP_READ = uint32(15)
_OP_WRITE = uint32(16)
_OP_STATFS = uint32(17)
_OP_RELEASE = uint32(18)
_OP_FSYNC = uint32(20)
_OP_SETXATTR = uint32(21)
_OP_GETXATTR = uint32(22)
_OP_LISTXATTR = uint32(23)
_OP_REMOVEXATTR = uint32(24)
_OP_FLUSH = uint32(25)
_OP_INIT = uint32(26)
_OP_OPENDIR = uint32(27)
_OP_READDIR = uint32(28)
_OP_RELEASEDIR = uint32(29)
_OP_FSYNCDIR = uint32(30)
_OP_GETLK = uint32(31)
_OP_SETLK = uint32(32)
_OP_SETLKW = uint32(33)
_OP_ACCESS = uint32(34)
_OP_CREATE = uint32(35)
_OP_INTERRUPT = uint32(36)
_OP_BMAP = uint32(37)
_OP_DESTROY = uint32(38)
_OP_IOCTL = uint32(39)
_OP_POLL = uint32(40)
_OP_NOTIFY_REPLY = uint32(41)
_OP_BATCH_FORGET = uint32(42)
_OP_FALLOCATE = uint32(43) // protocol version 19.
_OP_READDIRPLUS = uint32(44) // protocol version 21.
_OP_RENAME2 = uint32(45) // protocol version 23.
_OP_LSEEK = uint32(46) // protocol version 24
_OP_COPY_FILE_RANGE = uint32(47) // protocol version 28.
_OP_SETUPMAPPING = 48
_OP_REMOVEMAPPING = 49
_OP_SYNCFS = 50
_OP_TMPFILE = 51
_OP_STATX = 52
// The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_INVAL_ENTRY = uint32(100)
_OP_NOTIFY_INVAL_INODE = uint32(101)
_OP_NOTIFY_STORE_CACHE = uint32(102)
_OP_NOTIFY_RETRIEVE_CACHE = uint32(103)
_OP_NOTIFY_DELETE = uint32(104) // protocol version 18
_OPCODE_COUNT = uint32(105)
// Constants from Linux kernel fs/fuse/fuse_i.h
// Default MaxPages value in all kernel versions
_FUSE_DEFAULT_MAX_PAGES_PER_REQ = 32
// Upper MaxPages limit in Linux v4.20+ (v4.19 and older: 32)
_FUSE_MAX_MAX_PAGES = 256
)
////////////////////////////////////////////////////////////////
func doInit(server *protocolServer, req *request) {
input := (*InitIn)(req.inData())
if input.Major != _FUSE_KERNEL_VERSION {
log.Printf("Major versions does not match. Given %d, want %d\n", input.Major, _FUSE_KERNEL_VERSION)
req.status = EIO
return
}
if input.Minor < _MINIMUM_MINOR_VERSION {
log.Printf("Minor version is less than we support. Given %d, want at least %d\n", input.Minor, _MINIMUM_MINOR_VERSION)
req.status = EIO
return
}
kernelFlags := input.Flags64()
server.kernelSettings = *input
kernelFlags &= (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES | CAP_RENAME_SWAP | CAP_PASSTHROUGH | CAP_ALLOW_IDMAP)
if server.opts.EnableLocks {
kernelFlags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
}
if server.opts.EnableSymlinkCaching {
kernelFlags |= CAP_CACHE_SYMLINKS
}
if server.opts.EnableAcl {
kernelFlags |= CAP_POSIX_ACL
}
if server.opts.SyncRead {
// Clear CAP_ASYNC_READ
kernelFlags &= ^uint64(CAP_ASYNC_READ)
}
if server.opts.DisableReadDirPlus {
// Clear CAP_READDIRPLUS
kernelFlags &= ^uint64(CAP_READDIRPLUS)
}
if !server.opts.IDMappedMount {
// Clear CAP_ALLOW_IDMAP
kernelFlags &= ^uint64(CAP_ALLOW_IDMAP)
}
if server.opts.ExplicitDataCacheControl {
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
kernelFlags |= input.Flags64() & CAP_EXPLICIT_INVAL_DATA
} else {
kernelFlags |= input.Flags64() & CAP_AUTO_INVAL_DATA
}
// maxPages is the maximum request size we want the kernel to use, in units of
// memory pages (usually 4kiB). Linux v4.19 and older ignore this and always use
// 128kiB.
maxPages := (server.opts.MaxWrite-1)/syscall.Getpagesize() + 1 // Round up
out := (*InitOut)(req.outData())
*out = InitOut{
Major: _FUSE_KERNEL_VERSION,
Minor: _OUR_MINOR_VERSION,
MaxReadAhead: input.MaxReadAhead,
MaxWrite: uint32(server.opts.MaxWrite),
CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4),
MaxBackground: uint16(server.opts.MaxBackground),
MaxPages: uint16(maxPages),
MaxStackDepth: uint32(server.opts.MaxStackDepth),
}
out.setFlags(kernelFlags)
if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead {
out.MaxReadAhead = uint32(server.opts.MaxReadAhead)
}
if out.Minor > input.Minor {
out.Minor = input.Minor
}
req.status = OK
}
func doOpen(server *protocolServer, req *request) {
out := (*OpenOut)(req.outData())
status := server.fileSystem.Open(req.cancel, (*OpenIn)(req.inData()), out)
req.status = status
if status != OK {
return
}
}
func doCreate(server *protocolServer, req *request) {
out := (*CreateOut)(req.outData())
status := server.fileSystem.Create(req.cancel, (*CreateIn)(req.inData()), req.filename(), out)
req.status = status
}
func doReadDir(server *protocolServer, req *request) {
in := (*ReadIn)(req.inData())
out := NewDirEntryList(req.outPayload, uint64(in.Offset))
code := server.fileSystem.ReadDir(req.cancel, in, out)
req.outPayload = out.bytes()
req.status = code
}
func doReadDirPlus(server *protocolServer, req *request) {
in := (*ReadIn)(req.inData())
out := NewDirEntryList(req.outPayload, uint64(in.Offset))
code := server.fileSystem.ReadDirPlus(req.cancel, in, out)
req.outPayload = out.bytes()
req.status = code
}
func doOpenDir(server *protocolServer, req *request) {
out := (*OpenOut)(req.outData())
status := server.fileSystem.OpenDir(req.cancel, (*OpenIn)(req.inData()), out)
req.status = status
}
func doSetattr(server *protocolServer, req *request) {
out := (*AttrOut)(req.outData())
req.status = server.fileSystem.SetAttr(req.cancel, (*SetAttrIn)(req.inData()), out)
}
func doWrite(server *protocolServer, req *request) {
n, status := server.fileSystem.Write(req.cancel, (*WriteIn)(req.inData()), req.inPayload)
o := (*WriteOut)(req.outData())
o.Size = n
req.status = status
}
func doNotifyReply(server *protocolServer, req *request) {
reply := (*NotifyRetrieveIn)(req.inData())
server.retrieveMu.Lock()
reading := server.retrieveTab[reply.Unique]
delete(server.retrieveTab, reply.Unique)
server.retrieveMu.Unlock()
badf := func(format string, argv ...interface{}) {
server.opts.Logger.Printf("notify reply: "+format, argv...)
}
if reading == nil {
badf("unexpected unique - ignoring")
return
}
reading.n = 0
reading.st = EIO
defer close(reading.ready)
if reading.nodeid != reply.NodeId {
badf("inode mismatch: expected %s, got %s", reading.nodeid, reply.NodeId)
return
}
if reading.offset != reply.Offset {
badf("offset mismatch: expected @%d, got @%d", reading.offset, reply.Offset)
return
}
if len(reading.dest) < len(req.inPayload) {
badf("too much data: requested %db, got %db (will use only %db)", len(reading.dest), len(req.inPayload), len(reading.dest))
}
reading.n = copy(reading.dest, req.inPayload)
reading.st = OK
}
const _SECURITY_CAPABILITY = "security.capability"
const _SECURITY_ACL = "system.posix_acl_access"
const _SECURITY_ACL_DEFAULT = "system.posix_acl_default"
func doGetXAttr(server *protocolServer, req *request) {
if server.opts.DisableXAttrs {
req.status = ENOSYS
return
}
if server.opts.IgnoreSecurityLabels && req.inHeader().Opcode == _OP_GETXATTR {
fn := req.filename()
if fn == _SECURITY_CAPABILITY || fn == _SECURITY_ACL_DEFAULT ||
fn == _SECURITY_ACL {
req.status = ENOATTR
return
}
}
input := (*GetXAttrIn)(req.inData())
out := (*GetXAttrOut)(req.outData())
var n uint32
switch req.inHeader().Opcode {
case _OP_GETXATTR:
n, req.status = server.fileSystem.GetXAttr(req.cancel, req.inHeader(), req.filename(), req.outPayload)
case _OP_LISTXATTR:
n, req.status = server.fileSystem.ListXAttr(req.cancel, req.inHeader(), req.outPayload)
default:
req.status = ENOSYS
}
if input.Size == 0 && req.status == ERANGE {
// For input.size==0, returning ERANGE is an error.
req.status = OK
out.Size = n
} else if req.status.Ok() {
// ListXAttr called with an empty buffer returns the current size of
// the list but does not touch the buffer (see man 2 listxattr).
if len(req.outPayload) > 0 {
req.outPayload = req.outPayload[:n]
}
out.Size = n
} else {
req.outPayload = req.outPayload[:0]
}
}
func doGetAttr(server *protocolServer, req *request) {
out := (*AttrOut)(req.outData())
s := server.fileSystem.GetAttr(req.cancel, (*GetAttrIn)(req.inData()), out)
req.status = s
}
// doForget - forget one NodeId
func doForget(server *protocolServer, req *request) {
if !server.opts.RememberInodes {
server.fileSystem.Forget(req.inHeader().NodeId, (*ForgetIn)(req.inData()).Nlookup)
}
}
// doBatchForget - forget a list of NodeIds
func doBatchForget(server *protocolServer, req *request) {
in := (*_BatchForgetIn)(req.inData())
wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{})
if uintptr(len(req.inPayload)) < wantBytes {
// We have no return value to complain, so log an error.
server.opts.Logger.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)",
len(req.inPayload), wantBytes, in.Count)
}
forgets := unsafe.Slice((*_ForgetOne)(unsafe.Pointer(&req.inPayload[0])), in.Count)
for i, f := range forgets {
if server.opts.Debug {
server.opts.Logger.Printf("doBatchForget: rx %d %d/%d: FORGET n%d {Nlookup=%d}",
req.inHeader().Unique, i+1, len(forgets), f.NodeId, f.Nlookup)
}
if f.NodeId == pollHackInode {
continue
}
server.fileSystem.Forget(f.NodeId, f.Nlookup)
}
}
func doReadlink(server *protocolServer, req *request) {
req.outPayload, req.status = server.fileSystem.Readlink(req.cancel, req.inHeader())
}
func doLookup(server *protocolServer, req *request) {
out := (*EntryOut)(req.outData())
req.status = server.fileSystem.Lookup(req.cancel, req.inHeader(), req.filename(), out)
}
func doMknod(server *protocolServer, req *request) {
out := (*EntryOut)(req.outData())
req.status = server.fileSystem.Mknod(req.cancel, (*MknodIn)(req.inData()), req.filename(), out)
}
func doMkdir(server *protocolServer, req *request) {
out := (*EntryOut)(req.outData())
req.status = server.fileSystem.Mkdir(req.cancel, (*MkdirIn)(req.inData()), req.filename(), out)
}
func doUnlink(server *protocolServer, req *request) {
req.status = server.fileSystem.Unlink(req.cancel, req.inHeader(), req.filename())
}
func doRmdir(server *protocolServer, req *request) {
req.status = server.fileSystem.Rmdir(req.cancel, req.inHeader(), req.filename())
}
func doLink(server *protocolServer, req *request) {
out := (*EntryOut)(req.outData())
req.status = server.fileSystem.Link(req.cancel, (*LinkIn)(req.inData()), req.filename(), out)
}
func doRead(server *protocolServer, req *request) {
in := (*ReadIn)(req.inData())
req.readResult, req.status = server.fileSystem.Read(req.cancel, in, req.outPayload)
if fd, ok := req.readResult.(*readResultFd); ok {
req.fdData = fd
} else if req.readResult != nil && req.status.Ok() {
req.outPayload, req.status = req.readResult.Bytes(req.outPayload)
}
}
func doFlush(server *protocolServer, req *request) {
req.status = server.fileSystem.Flush(req.cancel, (*FlushIn)(req.inData()))
}
func doRelease(server *protocolServer, req *request) {
server.fileSystem.Release(req.cancel, (*ReleaseIn)(req.inData()))
}
func doFsync(server *protocolServer, req *request) {
req.status = server.fileSystem.Fsync(req.cancel, (*FsyncIn)(req.inData()))
}
func doReleaseDir(server *protocolServer, req *request) {
server.fileSystem.ReleaseDir((*ReleaseIn)(req.inData()))
}
func doFsyncDir(server *protocolServer, req *request) {
req.status = server.fileSystem.FsyncDir(req.cancel, (*FsyncIn)(req.inData()))
}
func doSetXAttr(server *protocolServer, req *request) {
i := bytes.IndexByte(req.inPayload, 0)
req.status = server.fileSystem.SetXAttr(req.cancel, (*SetXAttrIn)(req.inData()), string(req.inPayload[:i]), req.inPayload[i+1:])
}
func doRemoveXAttr(server *protocolServer, req *request) {
req.status = server.fileSystem.RemoveXAttr(req.cancel, req.inHeader(), req.filename())
}
func doAccess(server *protocolServer, req *request) {
req.status = server.fileSystem.Access(req.cancel, (*AccessIn)(req.inData()))
}
func doSymlink(server *protocolServer, req *request) {
out := (*EntryOut)(req.outData())
n1, n2 := req.filenames()
req.status = server.fileSystem.Symlink(req.cancel, req.inHeader(), n2, n1, out)
}
func doRename(server *protocolServer, req *request) {
if server.kernelSettings.supportsRenameSwap() {
doRename2(server, req)
return
}
in1 := (*Rename1In)(req.inData())
in := RenameIn{
InHeader: in1.InHeader,
Newdir: in1.Newdir,
}
n1, n2 := req.filenames()
req.status = server.fileSystem.Rename(req.cancel, &in, n1, n2)
}
func doRename2(server *protocolServer, req *request) {
n1, n2 := req.filenames()
req.status = server.fileSystem.Rename(req.cancel, (*RenameIn)(req.inData()), n1, n2)
}
func doStatFs(server *protocolServer, req *request) {
out := (*StatfsOut)(req.outData())
req.status = server.fileSystem.StatFs(req.cancel, req.inHeader(), out)
if req.status == ENOSYS && runtime.GOOS == "darwin" {
// OSX FUSE requires Statfs to be implemented for the
// mount to succeed.
*out = StatfsOut{}
req.status = OK
}
}
func doIoctl(server *protocolServer, req *request) {
req.status = server.fileSystem.Ioctl(req.cancel, (*IoctlIn)(req.inData()), req.inPayload, (*IoctlOut)(req.outData()),
req.outPayload)
}
func doDestroy(server *protocolServer, req *request) {
req.status = OK
}
func doFallocate(server *protocolServer, req *request) {
req.status = server.fileSystem.Fallocate(req.cancel, (*FallocateIn)(req.inData()))
}
func doGetLk(server *protocolServer, req *request) {
req.status = server.fileSystem.GetLk(req.cancel, (*LkIn)(req.inData()), (*LkOut)(req.outData()))
}
func doSetLk(server *protocolServer, req *request) {
req.status = server.fileSystem.SetLk(req.cancel, (*LkIn)(req.inData()))
}
func doSetLkw(server *protocolServer, req *request) {
req.status = server.fileSystem.SetLkw(req.cancel, (*LkIn)(req.inData()))
}
func doLseek(server *protocolServer, req *request) {
in := (*LseekIn)(req.inData())
out := (*LseekOut)(req.outData())
req.status = server.fileSystem.Lseek(req.cancel, in, out)
}
func doCopyFileRange(server *protocolServer, req *request) {
in := (*CopyFileRangeIn)(req.inData())
out := (*WriteOut)(req.outData())
out.Size, req.status = server.fileSystem.CopyFileRange(req.cancel, in)
}
func doInterrupt(server *protocolServer, req *request) {
input := (*InterruptIn)(req.inData())
req.status = server.interruptRequest(input.Unique)
}
////////////////////////////////////////////////////////////////
type operationFunc func(*protocolServer, *request)
type castPointerFunc func(unsafe.Pointer) interface{}
type operationHandler struct {
Name string
Func operationFunc
InputSize uintptr
OutputSize uintptr
InType interface{}
OutType interface{}
FileNames int
FileNameOut bool
}
var operationHandlers []*operationHandler
func operationName(op uint32) string {
h := getHandler(op)
if h == nil {
return "unknown"
}
return h.Name
}
func getHandler(o uint32) *operationHandler {
if o >= _OPCODE_COUNT {
return nil
}
return operationHandlers[o]
}
// maximum size of all input headers
var maxInputSize uintptr
func init() {
operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
for i := range operationHandlers {
operationHandlers[i] = &operationHandler{Name: fmt.Sprintf("OPCODE-%d", i)}
}
fileOps := []uint32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE}
for _, op := range fileOps {
operationHandlers[op].FileNameOut = true
}
for op, v := range map[uint32]string{
_OP_LOOKUP: "LOOKUP",
_OP_FORGET: "FORGET",
_OP_BATCH_FORGET: "BATCH_FORGET",
_OP_GETATTR: "GETATTR",
_OP_SETATTR: "SETATTR",
_OP_READLINK: "READLINK",
_OP_SYMLINK: "SYMLINK",
_OP_MKNOD: "MKNOD",
_OP_MKDIR: "MKDIR",
_OP_UNLINK: "UNLINK",
_OP_RMDIR: "RMDIR",
_OP_RENAME: "RENAME",
_OP_LINK: "LINK",
_OP_OPEN: "OPEN",
_OP_READ: "READ",
_OP_WRITE: "WRITE",
_OP_STATFS: "STATFS",
_OP_RELEASE: "RELEASE",
_OP_FSYNC: "FSYNC",
_OP_SETXATTR: "SETXATTR",
_OP_GETXATTR: "GETXATTR",
_OP_LISTXATTR: "LISTXATTR",
_OP_REMOVEXATTR: "REMOVEXATTR",
_OP_FLUSH: "FLUSH",
_OP_INIT: "INIT",
_OP_OPENDIR: "OPENDIR",
_OP_READDIR: "READDIR",
_OP_RELEASEDIR: "RELEASEDIR",
_OP_FSYNCDIR: "FSYNCDIR",
_OP_GETLK: "GETLK",
_OP_SETLK: "SETLK",
_OP_SETLKW: "SETLKW",
_OP_ACCESS: "ACCESS",
_OP_CREATE: "CREATE",
_OP_INTERRUPT: "INTERRUPT",
_OP_BMAP: "BMAP",
_OP_DESTROY: "DESTROY",
_OP_IOCTL: "IOCTL",
_OP_POLL: "POLL",
_OP_NOTIFY_REPLY: "NOTIFY_REPLY",
_OP_NOTIFY_INVAL_ENTRY: "NOTIFY_INVAL_ENTRY",
_OP_NOTIFY_INVAL_INODE: "NOTIFY_INVAL_INODE",
_OP_NOTIFY_STORE_CACHE: "NOTIFY_STORE",
_OP_NOTIFY_RETRIEVE_CACHE: "NOTIFY_RETRIEVE",
_OP_NOTIFY_DELETE: "NOTIFY_DELETE",
_OP_FALLOCATE: "FALLOCATE",
_OP_READDIRPLUS: "READDIRPLUS",
_OP_RENAME2: "RENAME2",
_OP_LSEEK: "LSEEK",
_OP_COPY_FILE_RANGE: "COPY_FILE_RANGE",
_OP_SETUPMAPPING: "SETUPMAPPING",
_OP_REMOVEMAPPING: "REMOVEMAPPING",
_OP_SYNCFS: "SYNCFS",
_OP_TMPFILE: "TMPFILE",
} {
operationHandlers[op].Name = v
}
for op, v := range map[uint32]operationFunc{
_OP_OPEN: doOpen,
_OP_READDIR: doReadDir,
_OP_WRITE: doWrite,
_OP_OPENDIR: doOpenDir,
_OP_CREATE: doCreate,
_OP_SETATTR: doSetattr,
_OP_GETXATTR: doGetXAttr,
_OP_LISTXATTR: doGetXAttr,
_OP_GETATTR: doGetAttr,
_OP_FORGET: doForget,
_OP_BATCH_FORGET: doBatchForget,
_OP_READLINK: doReadlink,
_OP_INIT: doInit,
_OP_LOOKUP: doLookup,
_OP_MKNOD: doMknod,
_OP_MKDIR: doMkdir,
_OP_UNLINK: doUnlink,
_OP_RMDIR: doRmdir,
_OP_LINK: doLink,
_OP_READ: doRead,
_OP_FLUSH: doFlush,
_OP_RELEASE: doRelease,
_OP_FSYNC: doFsync,
_OP_RELEASEDIR: doReleaseDir,
_OP_FSYNCDIR: doFsyncDir,
_OP_SETXATTR: doSetXAttr,
_OP_REMOVEXATTR: doRemoveXAttr,
_OP_GETLK: doGetLk,
_OP_SETLK: doSetLk,
_OP_SETLKW: doSetLkw,
_OP_ACCESS: doAccess,
_OP_SYMLINK: doSymlink,
_OP_RENAME: doRename,
_OP_STATFS: doStatFs,
_OP_IOCTL: doIoctl,
_OP_DESTROY: doDestroy,
_OP_NOTIFY_REPLY: doNotifyReply,
_OP_FALLOCATE: doFallocate,
_OP_READDIRPLUS: doReadDirPlus,
_OP_RENAME2: doRename2,
_OP_INTERRUPT: doInterrupt,
_OP_COPY_FILE_RANGE: doCopyFileRange,
_OP_LSEEK: doLseek,
} {
operationHandlers[op].Func = v
}
// Outputs.
for op, f := range map[uint32]interface{}{
_OP_BMAP: _BmapOut{},
_OP_COPY_FILE_RANGE: WriteOut{},
_OP_CREATE: CreateOut{},
_OP_GETATTR: AttrOut{},
_OP_GETLK: LkOut{},
_OP_GETXATTR: GetXAttrOut{},
_OP_INIT: InitOut{},
_OP_IOCTL: IoctlOut{},
_OP_LINK: EntryOut{},
_OP_LISTXATTR: GetXAttrOut{},
_OP_LOOKUP: EntryOut{},
_OP_LSEEK: LseekOut{},
_OP_MKDIR: EntryOut{},
_OP_MKNOD: EntryOut{},
_OP_NOTIFY_DELETE: NotifyInvalDeleteOut{},
_OP_NOTIFY_INVAL_ENTRY: NotifyInvalEntryOut{},
_OP_NOTIFY_INVAL_INODE: NotifyInvalInodeOut{},
_OP_NOTIFY_RETRIEVE_CACHE: NotifyRetrieveOut{},
_OP_NOTIFY_STORE_CACHE: NotifyStoreOut{},
_OP_OPEN: OpenOut{},
_OP_OPENDIR: OpenOut{},
_OP_POLL: _PollOut{},
_OP_SETATTR: AttrOut{},
_OP_STATFS: StatfsOut{},
_OP_SYMLINK: EntryOut{},
_OP_WRITE: WriteOut{},
} {
operationHandlers[op].OutType = f
operationHandlers[op].OutputSize = typSize(f)
}
// Inputs.
for op, f := range map[uint32]interface{}{
_OP_ACCESS: AccessIn{},
_OP_BATCH_FORGET: _BatchForgetIn{},
_OP_BMAP: _BmapIn{},
_OP_COPY_FILE_RANGE: CopyFileRangeIn{},
_OP_CREATE: CreateIn{},
_OP_FALLOCATE: FallocateIn{},
_OP_FLUSH: FlushIn{},
_OP_FORGET: ForgetIn{},
_OP_FSYNC: FsyncIn{},
_OP_FSYNCDIR: FsyncIn{},
_OP_GETATTR: GetAttrIn{},
_OP_GETLK: LkIn{},
_OP_GETXATTR: GetXAttrIn{},
_OP_INIT: InitIn{},
_OP_INTERRUPT: InterruptIn{},
_OP_IOCTL: IoctlIn{},
_OP_LINK: LinkIn{},
_OP_LISTXATTR: GetXAttrIn{},
_OP_LSEEK: LseekIn{},
_OP_MKDIR: MkdirIn{},
_OP_MKNOD: MknodIn{},
_OP_NOTIFY_REPLY: NotifyRetrieveIn{},
_OP_OPEN: OpenIn{},
_OP_OPENDIR: OpenIn{},
_OP_POLL: _PollIn{},
_OP_READ: ReadIn{},
_OP_READDIR: ReadIn{},
_OP_READDIRPLUS: ReadIn{},
_OP_RELEASE: ReleaseIn{},
_OP_RELEASEDIR: ReleaseIn{},
_OP_RENAME2: RenameIn{},
_OP_RENAME: Rename1In{},
_OP_SETATTR: SetAttrIn{},
_OP_SETLK: LkIn{},
_OP_SETLKW: LkIn{},
_OP_SETXATTR: SetXAttrIn{},
_OP_WRITE: WriteIn{},
} {
operationHandlers[op].InType = f
sz := typSize(f)
operationHandlers[op].InputSize = sz
if maxInputSize < sz {
maxInputSize = sz
}
}
// File name args.
for op, count := range map[uint32]int{
_OP_CREATE: 1,
_OP_SETXATTR: 1,
_OP_GETXATTR: 1,
_OP_LINK: 1,
_OP_LOOKUP: 1,
_OP_MKDIR: 1,
_OP_MKNOD: 1,
_OP_REMOVEXATTR: 1,
_OP_RENAME: 2,
_OP_RENAME2: 2,
_OP_RMDIR: 1,
_OP_SYMLINK: 2,
_OP_UNLINK: 1,
} {
operationHandlers[op].FileNames = count
}
checkFixedBufferSize()
}
func checkFixedBufferSize() {
var r requestAlloc
sizeOfOutHeader := unsafe.Sizeof(OutHeader{})
for code, h := range operationHandlers {
if h.OutputSize+sizeOfOutHeader > unsafe.Sizeof(r.outBuf) {
log.Panicf("request output buffer too small: code %v, sz %d + %d %v", code, h.OutputSize, sizeOfOutHeader, h)
}
}
}

View File

@@ -0,0 +1,26 @@
// 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 fuse
import "unsafe"
func doStatx(server *protocolServer, req *request) {
in := (*StatxIn)(req.inData())
out := (*StatxOut)(req.outData())
req.status = server.fileSystem.Statx(req.cancel, in, out)
}
func init() {
operationHandlers[_OP_STATX] = &operationHandler{
Name: "STATX",
Func: doStatx,
InType: StatxIn{},
OutType: StatxOut{},
InputSize: unsafe.Sizeof(StatxIn{}),
OutputSize: unsafe.Sizeof(StatxOut{}),
}
checkFixedBufferSize()
}

View File

@@ -0,0 +1,46 @@
// 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 fuse
import (
"syscall"
"unsafe"
)
const (
_DEV_IOC_BACKING_OPEN = 0x4010e501
_DEV_IOC_BACKING_CLOSE = 0x4004e502
)
// RegisterBackingFd registers the given file descriptor in the
// kernel, so the kernel can bypass FUSE and access the backing file
// directly for read and write calls. On success a backing ID is
// returned. The backing ID should unregistered using
// UnregisterBackingFd() once the file is released. Within the
// kernel, an inode can only have a single backing file, so multiple
// Open/Create calls should coordinate to return a consistent backing
// ID.
func (ms *Server) RegisterBackingFd(m *BackingMap) (int32, syscall.Errno) {
ms.writeMu.Lock()
id, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_OPEN), uintptr(unsafe.Pointer(m)))
ms.writeMu.Unlock()
if ms.opts.Debug {
ms.opts.Logger.Printf("ioctl: BACKING_OPEN %v: id %d (%v)", m.string(), id, errno)
}
return int32(id), errno
}
// UnregisterBackingFd unregisters the given ID in the kernel. The ID
// should have been acquired before using RegisterBackingFd.
func (ms *Server) UnregisterBackingFd(id int32) syscall.Errno {
ms.writeMu.Lock()
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_CLOSE), uintptr(unsafe.Pointer(&id)))
ms.writeMu.Unlock()
if ms.opts.Debug {
ms.opts.Logger.Printf("ioctl: BACKING_CLOSE id %d: %v", id, errno)
}
return errno
}

52
vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package fuse
// Go 1.9 introduces polling for file I/O. The implementation causes
// the runtime's epoll to take up the last GOMAXPROCS slot, and if
// that happens, we won't have any threads left to service FUSE's
// _OP_POLL request. Prevent this by forcing _OP_POLL to happen, so we
// can say ENOSYS and prevent further _OP_POLL requests.
const pollHackName = ".go-fuse-epoll-hack"
const pollHackInode = ^uint64(0)
func doPollHackLookup(ms *protocolServer, req *request) {
attr := Attr{
Ino: pollHackInode,
Mode: S_IFREG | 0644,
Nlink: 1,
}
switch req.inHeader().Opcode {
case _OP_LOOKUP:
out := (*EntryOut)(req.outData())
*out = EntryOut{
NodeId: pollHackInode,
Attr: attr,
}
req.status = OK
case _OP_OPEN:
out := (*OpenOut)(req.outData())
*out = OpenOut{
Fh: pollHackInode,
}
req.status = OK
case _OP_GETATTR, _OP_SETATTR:
out := (*AttrOut)(req.outData())
out.Attr = attr
req.status = OK
case _OP_GETXATTR:
// Kernel will try to read acl xattrs. Pretend we don't have any.
req.status = ENODATA
case _OP_POLL:
req.status = ENOSYS
case _OP_ACCESS, _OP_FLUSH, _OP_RELEASE:
// Avoid upsetting the OSX mount process.
req.status = OK
default:
// We want to avoid switching off features through our
// poll hack, so don't use ENOSYS. It would be nice if
// we could transmit no error code at all, but for
// some opcodes, we'd have to invent credible data to
// return as well.
req.status = ERANGE
}
}

View File

@@ -0,0 +1,56 @@
package fuse
import (
"path/filepath"
"syscall"
"unsafe"
)
type pollFd struct {
Fd int32
Events int16
Revents int16
}
func sysPoll(fds []pollFd, timeout int) (n int, err error) {
r0, _, e1 := syscall.Syscall(syscall.SYS_POLL, uintptr(unsafe.Pointer(&fds[0])),
uintptr(len(fds)), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = syscall.Errno(e1)
}
return n, err
}
func pollHack(mountPoint string) error {
const (
POLLIN = 0x1
POLLPRI = 0x2
POLLOUT = 0x4
POLLRDHUP = 0x2000
POLLERR = 0x8
POLLHUP = 0x10
)
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
if err == syscall.EPERM {
// This can happen due to macos sandboxing, see
// https://github.com/hanwen/go-fuse/issues/572#issuecomment-2904052441
// If we don't have access to access our own mount
// point, we also can't deadlock ourselves with poll, so this is fine.
return nil
}
if err != nil {
return err
}
pollData := []pollFd{{
Fd: int32(fd),
Events: POLLIN | POLLPRI | POLLOUT,
}}
// Trigger _OP_POLL, so we can say ENOSYS. We don't care about
// the return value.
sysPoll(pollData, 0)
syscall.Close(fd)
return nil
}

27
vendor/github.com/hanwen/go-fuse/v2/fuse/poll_unix.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
//go:build !darwin
package fuse
import (
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
)
func pollHack(mountPoint string) error {
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
if err != nil {
return err
}
pollData := []unix.PollFd{{
Fd: int32(fd),
Events: unix.POLLIN | unix.POLLPRI | unix.POLLOUT,
}}
// Trigger _OP_POLL, so we can say ENOSYS. We don't care about
// the return value.
unix.Poll(pollData, 0)
syscall.Close(fd)
return nil
}

415
vendor/github.com/hanwen/go-fuse/v2/fuse/print.go generated vendored Normal file
View File

@@ -0,0 +1,415 @@
// Copyright 2016 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 fuse
import (
"fmt"
"os"
"strings"
"syscall"
)
var (
isTest bool
writeFlagNames = newFlagNames([]flagNameEntry{
{WRITE_CACHE, "CACHE"},
{WRITE_LOCKOWNER, "LOCKOWNER"},
})
readFlagNames = newFlagNames([]flagNameEntry{
{READ_LOCKOWNER, "LOCKOWNER"},
})
initFlagNames = newFlagNames([]flagNameEntry{
{CAP_ASYNC_READ, "ASYNC_READ"},
{CAP_POSIX_LOCKS, "POSIX_LOCKS"},
{CAP_FILE_OPS, "FILE_OPS"},
{CAP_ATOMIC_O_TRUNC, "ATOMIC_O_TRUNC"},
{CAP_EXPORT_SUPPORT, "EXPORT_SUPPORT"},
{CAP_BIG_WRITES, "BIG_WRITES"},
{CAP_DONT_MASK, "DONT_MASK"},
{CAP_SPLICE_WRITE, "SPLICE_WRITE"},
{CAP_SPLICE_MOVE, "SPLICE_MOVE"},
{CAP_SPLICE_READ, "SPLICE_READ"},
{CAP_FLOCK_LOCKS, "FLOCK_LOCKS"},
{CAP_IOCTL_DIR, "IOCTL_DIR"},
{CAP_AUTO_INVAL_DATA, "AUTO_INVAL_DATA"},
{CAP_READDIRPLUS, "READDIRPLUS"},
{CAP_READDIRPLUS_AUTO, "READDIRPLUS_AUTO"},
{CAP_ASYNC_DIO, "ASYNC_DIO"},
{CAP_WRITEBACK_CACHE, "WRITEBACK_CACHE"},
{CAP_NO_OPEN_SUPPORT, "NO_OPEN_SUPPORT"},
{CAP_PARALLEL_DIROPS, "PARALLEL_DIROPS"},
{CAP_POSIX_ACL, "POSIX_ACL"},
{CAP_HANDLE_KILLPRIV, "HANDLE_KILLPRIV"},
{CAP_ABORT_ERROR, "ABORT_ERROR"},
{CAP_MAX_PAGES, "MAX_PAGES"},
{CAP_CACHE_SYMLINKS, "CACHE_SYMLINKS"},
{CAP_SECURITY_CTX, "SECURITY_CTX"},
{CAP_HAS_INODE_DAX, "HAS_INODE_DAX"},
{CAP_CREATE_SUPP_GROUP, "CREATE_SUPP_GROUP"},
{CAP_HAS_EXPIRE_ONLY, "HAS_EXPIRE_ONLY"},
{CAP_DIRECT_IO_ALLOW_MMAP, "DIRECT_IO_ALLOW_MMAP"},
{CAP_PASSTHROUGH, "PASSTHROUGH"},
{CAP_NO_EXPORT_SUPPORT, "NO_EXPORT_SUPPORT"},
{CAP_HAS_RESEND, "HAS_RESEND"},
{CAP_ALLOW_IDMAP, "ALLOW_IDMAP"},
{CAP_OVER_IO_URING, "IO_URING"},
{CAP_REQUEST_TIMEOUT, "REQUEST_TIMEOUT"},
})
releaseFlagNames = newFlagNames([]flagNameEntry{
{RELEASE_FLUSH, "FLUSH"},
})
openFlagNames = newFlagNames([]flagNameEntry{
{int64(os.O_WRONLY), "WRONLY"},
{int64(os.O_RDWR), "RDWR"},
{int64(os.O_APPEND), "APPEND"},
{int64(syscall.O_ASYNC), "ASYNC"},
{int64(os.O_CREATE), "CREAT"},
{int64(os.O_EXCL), "EXCL"},
{int64(syscall.O_NOCTTY), "NOCTTY"},
{int64(syscall.O_NONBLOCK), "NONBLOCK"},
{int64(os.O_SYNC), "SYNC"},
{int64(os.O_TRUNC), "TRUNC"},
{int64(syscall.O_CLOEXEC), "CLOEXEC"},
{int64(syscall.O_DIRECTORY), "DIRECTORY"},
})
fuseOpenFlagNames = newFlagNames([]flagNameEntry{
{FOPEN_DIRECT_IO, "DIRECT"},
{FOPEN_KEEP_CACHE, "CACHE"},
{FOPEN_NONSEEKABLE, "NONSEEK"},
{FOPEN_CACHE_DIR, "CACHE_DIR"},
{FOPEN_STREAM, "STREAM"},
{FOPEN_NOFLUSH, "NOFLUSH"},
{FOPEN_PARALLEL_DIRECT_WRITES, "PARALLEL_DIRECT_WRITES"},
{FOPEN_PASSTHROUGH, "PASSTHROUGH"},
})
ioctlFlagNames = newFlagNames([]flagNameEntry{
{IOCTL_COMPAT, "COMPAT"},
{IOCTL_UNRESTRICTED, "UNRESTRICTED"},
{IOCTL_RETRY, "RETRY"},
{IOCTL_DIR, "DIR"},
})
accessFlagName = newFlagNames([]flagNameEntry{
{X_OK, "x"},
{W_OK, "w"},
{R_OK, "r"},
})
getAttrFlagNames = newFlagNames([]flagNameEntry{
{FUSE_GETATTR_FH, "FH"},
})
renameFlagNames = newFlagNames([]flagNameEntry{
{1, "NOREPLACE"},
{2, "EXCHANGE"},
{4, "WHITEOUT"},
})
)
// flagNames associate flag bits to their names.
type flagNames [64]flagNameEntry
// flagNameEntry describes one flag value.
//
// Usually a flag constitues only one bit, but, for example at least O_SYNC and
// O_TMPFILE are represented by a value with two bits set. To handle such
// situations we map all bits of a flag to the same flagNameEntry.
type flagNameEntry struct {
bits int64
name string
}
// newFlagNames creates flagNames from flag->name map.
func newFlagNames(names []flagNameEntry) *flagNames {
var v flagNames
for _, e := range names {
v.set(e.bits, e.name)
}
return &v
}
// set associates flag value with name.
func (names *flagNames) set(flag int64, name string) {
entry := flagNameEntry{bits: flag, name: name}
for i := 0; i < 64; i++ {
if flag&(1<<i) != 0 {
if ie := names[i]; ie.bits != 0 && isTest {
panic(fmt.Sprintf("%s (%x) overlaps with %s (%x)", name, flag, ie.name, ie.bits))
}
names[i] = entry
}
}
}
func flagString(names *flagNames, fl int64, def string) string {
s := []string{}
// emit flags in their numeric order
for i := range names {
entry := &names[i]
if entry.bits == 0 {
continue
}
if fl&entry.bits == entry.bits {
s = append(s, entry.name)
fl ^= entry.bits
if fl == 0 {
break
}
}
}
if fl != 0 {
s = append(s, fmt.Sprintf("0x%x", fl))
}
if len(s) == 0 && def != "" {
return def
}
return strings.Join(s, ",")
}
func (in *ForgetIn) string() string {
return fmt.Sprintf("{Nlookup=%d}", in.Nlookup)
}
func (in *_BatchForgetIn) string() string {
return fmt.Sprintf("{Count=%d}", in.Count)
}
func (in *MkdirIn) string() string {
return fmt.Sprintf("{0%o (mask 0%o)}", in.Mode, in.Umask)
}
func (in *Rename1In) string() string {
return fmt.Sprintf("{i%d}", in.Newdir)
}
func (in *RenameIn) string() string {
return fmt.Sprintf("{i%d %s}", in.Newdir, flagString(renameFlagNames, int64(in.Flags), "0"))
}
func (in *SetAttrIn) string() string {
s := []string{}
if in.Valid&FATTR_MODE != 0 {
s = append(s, fmt.Sprintf("mode 0%o", in.Mode))
}
if in.Valid&FATTR_UID != 0 {
s = append(s, fmt.Sprintf("uid %d", in.Uid))
}
if in.Valid&FATTR_GID != 0 {
s = append(s, fmt.Sprintf("gid %d", in.Gid))
}
if in.Valid&FATTR_SIZE != 0 {
s = append(s, fmt.Sprintf("size %d", in.Size))
}
if in.Valid&FATTR_ATIME != 0 {
s = append(s, fmt.Sprintf("atime %d.%09d", in.Atime, in.Atimensec))
}
if in.Valid&FATTR_MTIME != 0 {
s = append(s, fmt.Sprintf("mtime %d.%09d", in.Mtime, in.Mtimensec))
}
if in.Valid&FATTR_FH != 0 {
s = append(s, fmt.Sprintf("fh %d", in.Fh))
}
// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
return fmt.Sprintf("{%s}", strings.Join(s, ", "))
}
func (in *ReleaseIn) string() string {
return fmt.Sprintf("{Fh %d %s %s L%d}",
in.Fh, flagString(openFlagNames, int64(in.Flags), ""),
flagString(releaseFlagNames, int64(in.ReleaseFlags), ""),
in.LockOwner)
}
func (in *OpenIn) string() string {
return fmt.Sprintf("{%s}", flagString(openFlagNames, int64(in.Flags), "O_RDONLY"))
}
func (in *OpenOut) string() string {
backing := ""
if in.BackingID != 0 {
backing = fmt.Sprintf("backing=%d ", in.BackingID)
}
return fmt.Sprintf("{Fh %d %s%s}", in.Fh, backing,
flagString(fuseOpenFlagNames, int64(in.OpenFlags), ""))
}
func (in *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s}",
in.Major, in.Minor, in.MaxReadAhead,
flagString(initFlagNames, int64(in.Flags64()), ""))
}
func (o *InitOut) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s %d/%d Wr %d Tg %d MaxPages %d MaxStack %d}",
o.Major, o.Minor, o.MaxReadAhead,
flagString(initFlagNames, int64(o.Flags64()), ""),
o.CongestionThreshold, o.MaxBackground, o.MaxWrite,
o.TimeGran, o.MaxPages, o.MaxStackDepth)
}
func (s *FsyncIn) string() string {
return fmt.Sprintf("{Fh %d Flags %x}", s.Fh, s.FsyncFlags)
}
func (in *SetXAttrIn) string() string {
return fmt.Sprintf("{sz %d f%o}", in.Size, in.Flags)
}
func (in *GetXAttrIn) string() string {
return fmt.Sprintf("{sz %d}", in.Size)
}
func (o *GetXAttrOut) string() string {
return fmt.Sprintf("{sz %d}", o.Size)
}
func (in *AccessIn) string() string {
return fmt.Sprintf("{u=%d g=%d %s}",
in.Uid,
in.Gid,
flagString(accessFlagName, int64(in.Mask), ""))
}
func (in *FlushIn) string() string {
return fmt.Sprintf("{Fh %d}", in.Fh)
}
func (o *AttrOut) string() string {
return fmt.Sprintf(
"{tA=%gs %v}",
ft(o.AttrValid, o.AttrValidNsec), &o.Attr)
}
// ft converts (seconds , nanoseconds) -> float(seconds)
func ft(tsec uint64, tnsec uint32) float64 {
return float64(tsec) + float64(tnsec)*1e-9
}
// Returned by LOOKUP
func (o *EntryOut) string() string {
return fmt.Sprintf("{n%d g%d tE=%gs tA=%gs %v}",
o.NodeId, o.Generation, ft(o.EntryValid, o.EntryValidNsec),
ft(o.AttrValid, o.AttrValidNsec), &o.Attr)
}
func (o *CreateOut) string() string {
return fmt.Sprintf("{n%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, o.OpenOut.string())
}
func (o *StatfsOut) string() string {
return fmt.Sprintf(
"{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}",
o.Bfree, o.Bavail, o.Blocks, o.Ffree, o.Files,
o.Bsize, o.NameLen, o.Frsize)
}
func (o *NotifyInvalEntryOut) string() string {
return fmt.Sprintf("{parent i%d sz %d}", o.Parent, o.NameLen)
}
func (o *NotifyInvalInodeOut) string() string {
return fmt.Sprintf("{i%d [%d +%d)}", o.Ino, o.Off, o.Length)
}
func (o *NotifyInvalDeleteOut) string() string {
return fmt.Sprintf("{parent i%d ch i%d sz %d}", o.Parent, o.Child, o.NameLen)
}
func (o *NotifyStoreOut) string() string {
return fmt.Sprintf("{n%d [%d +%d)}", o.Nodeid, o.Offset, o.Size)
}
func (o *NotifyRetrieveOut) string() string {
return fmt.Sprintf("{> %d: n%d [%d +%d)}", o.NotifyUnique, o.Nodeid, o.Offset, o.Size)
}
func (i *NotifyRetrieveIn) string() string {
return fmt.Sprintf("{[%d +%d)}", i.Offset, i.Size)
}
func (f *FallocateIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) mod 0%o}",
f.Fh, f.Offset, f.Length, f.Mode)
}
func (f *LinkIn) string() string {
return fmt.Sprintf("{Oldnodeid: n%d}", f.Oldnodeid)
}
func (o *WriteOut) string() string {
return fmt.Sprintf("{%db }", o.Size)
}
func (i *CopyFileRangeIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) => n%d Fh %d [%d, %d)}",
i.FhIn, i.OffIn, i.Len, i.NodeIdOut, i.FhOut, i.OffOut, i.Len)
}
func (in *InterruptIn) string() string {
return fmt.Sprintf("{ix %d}", in.Unique)
}
var seekNames = map[uint32]string{
0: "SET",
1: "CUR",
2: "END",
3: "DATA",
4: "HOLE",
}
func (in *LseekIn) string() string {
return fmt.Sprintf("{Fh %d [%s +%d)}", in.Fh,
seekNames[in.Whence], in.Offset)
}
func (o *LseekOut) string() string {
return fmt.Sprintf("{%d}", o.Offset)
}
func (p *_PollIn) string() string {
return fmt.Sprintf("Fh %d Kh %d Flags 0x%x", p.Fh, p.Kh, p.Flags)
}
// Print pretty prints FUSE data types for kernel communication
func Print(obj interface{}) string {
t, ok := obj.(interface {
string() string
})
if ok {
return t.string()
}
return fmt.Sprintf("%T: %v", obj, obj)
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"B%d*%d i%d:%d "+
"A %f "+
"M %f "+
"C %f}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
ft(a.Ctime, a.Ctimensec))
}
func (m *BackingMap) string() string {
return fmt.Sprintf("{fd %d, flags 0x%x}", m.Fd, m.Flags)
}
func (o *IoctlIn) string() string {
return fmt.Sprintf("{Fh %d Flags %s Cmd 0x%x Arg 0x%x, insz %d outsz %d}",
o.Fh,
flagString(ioctlFlagNames, int64(o.Flags), ""),
o.Cmd, o.Arg, o.InSize, o.OutSize)
}
func (o *IoctlOut) string() string {
return fmt.Sprintf("{Result %d Flags %s Iovs %d/%d",
o.Result,
flagString(ioctlFlagNames, int64(o.Flags), ""),
o.InIovs, o.OutIovs)
}

View File

@@ -0,0 +1,44 @@
// Copyright 2016 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 fuse
import (
"fmt"
)
func init() {
initFlagNames.set(CAP_NODE_RWLOCK, "NODE_RWLOCK")
initFlagNames.set(CAP_RENAME_SWAP, "RENAME_SWAP")
initFlagNames.set(CAP_RENAME_EXCL, "RENAME_EXCL")
initFlagNames.set(CAP_ALLOCATE, "ALLOCATE")
initFlagNames.set(CAP_EXCHANGE_DATA, "EXCHANGE_DATA")
initFlagNames.set(CAP_XTIMES, "XTIMES")
initFlagNames.set(CAP_VOL_RENAME, "VOL_RENAME")
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE")
}
func (me *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s]}", me.Mode,
flagString(openFlagNames, int64(me.Flags), "O_RDONLY"))
}
func (me *GetAttrIn) string() string { return "" }
func (me *MknodIn) string() string {
return fmt.Sprintf("{0%o, %d}", me.Mode, me.Rdev)
}
func (me *ReadIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s}",
me.Fh, me.Offset, me.Size,
flagString(readFlagNames, int64(me.ReadFlags), ""))
}
func (me *WriteIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s}",
me.Fh, me.Offset, me.Size,
flagString(writeFlagNames, int64(me.WriteFlags), ""))
}

View File

@@ -0,0 +1,5 @@
package fuse
func init() {
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
}

View File

@@ -0,0 +1,92 @@
// Copyright 2016 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 fuse
import (
"fmt"
"runtime"
"strings"
"syscall"
"golang.org/x/sys/unix"
)
var statxFieldFlags = newFlagNames([]flagNameEntry{
{unix.STATX_ATIME, "Atime"},
{unix.STATX_BLOCKS, "blocks"},
{unix.STATX_BTIME, "Btime"},
{unix.STATX_CTIME, "Ctime"},
{unix.STATX_GID, "Gid"},
{unix.STATX_INO, "Ino"},
{unix.STATX_MNT_ID, "Mntid"},
{unix.STATX_MODE, "Mode"},
{unix.STATX_MTIME, "Mtime"},
{unix.STATX_NLINK, "Nlink"},
{unix.STATX_SIZE, "Size"},
{unix.STATX_TYPE, "Type"},
{unix.STATX_UID, "Uid"},
})
func init() {
// syscall.O_LARGEFILE is 0x0 on x86_64, but the kernel
// supplies 0x8000 anyway, except on mips64el, where 0x8000 is
// used for O_DIRECT.
if !strings.Contains(runtime.GOARCH, "mips64") {
openFlagNames.set(0x8000, "LARGEFILE")
}
openFlagNames.set(syscall.O_DIRECT, "DIRECT")
openFlagNames.set(syscall_O_NOATIME, "NOATIME")
openFlagNames.set(FMODE_EXEC, "EXEC")
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
initFlagNames.set(CAP_EXPLICIT_INVAL_DATA, "EXPLICIT_INVAL_DATA")
initFlagNames.set(CAP_MAP_ALIGNMENT, "MAP_ALIGNMENT")
initFlagNames.set(CAP_SUBMOUNTS, "SUBMOUNTS")
initFlagNames.set(CAP_HANDLE_KILLPRIV_V2, "HANDLE_KILLPRIV_V2")
initFlagNames.set(CAP_SETXATTR_EXT, "SETXATTR_EXT")
initFlagNames.set(CAP_INIT_EXT, "INIT_EXT")
initFlagNames.set(CAP_INIT_RESERVED, "INIT_RESERVED")
}
func (a *Statx) string() string {
var ss []string
if a.Mask&unix.STATX_MODE != 0 || a.Mask&unix.STATX_TYPE != 0 {
ss = append(ss, fmt.Sprintf("M0%o", a.Mode))
}
if a.Mask&unix.STATX_SIZE != 0 {
ss = append(ss, fmt.Sprintf("SZ=%d", a.Size))
}
if a.Mask&unix.STATX_NLINK != 0 {
ss = append(ss, fmt.Sprintf("L=%d", a.Nlink))
}
if a.Mask&unix.STATX_UID != 0 || a.Mask&unix.STATX_GID != 0 {
ss = append(ss, fmt.Sprintf("%d:%d", a.Uid, a.Gid))
}
if a.Mask&unix.STATX_INO != 0 {
ss = append(ss, fmt.Sprintf("i%d", a.Ino))
}
if a.Mask&unix.STATX_ATIME != 0 {
ss = append(ss, fmt.Sprintf("A %f", a.Atime.Seconds()))
}
if a.Mask&unix.STATX_BTIME != 0 {
ss = append(ss, fmt.Sprintf("B %f", a.Btime.Seconds()))
}
if a.Mask&unix.STATX_CTIME != 0 {
ss = append(ss, fmt.Sprintf("C %f", a.Ctime.Seconds()))
}
if a.Mask&unix.STATX_MTIME != 0 {
ss = append(ss, fmt.Sprintf("M %f", a.Mtime.Seconds()))
}
if a.Mask&unix.STATX_BLOCKS != 0 {
ss = append(ss, fmt.Sprintf("%d*%d", a.Blocks, a.Blksize))
}
return "{" + strings.Join(ss, " ") + "}"
}
func (in *StatxIn) string() string {
return fmt.Sprintf("{Fh %d %s 0x%x %s}", in.Fh, flagString(getAttrFlagNames, int64(in.GetattrFlags), ""),
in.SxFlags, flagString(statxFieldFlags, int64(in.SxMask), ""))
}

35
vendor/github.com/hanwen/go-fuse/v2/fuse/print_unix.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
//go:build !darwin
package fuse
import "fmt"
func (in *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s] (0%o)}", in.Mode,
flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask)
}
func (in *GetAttrIn) string() string {
return fmt.Sprintf("{Fh %d %s}", in.Fh_, flagString(getAttrFlagNames, int64(in.Flags_), ""))
}
func (in *MknodIn) string() string {
return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev)
}
func (in *ReadIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(readFlagNames, int64(in.ReadFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}
func (in *WriteIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(writeFlagNames, int64(in.WriteFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}

View File

@@ -0,0 +1,128 @@
// 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 fuse
import (
"sync"
)
// protocolServer bridges from the FUSE datatypes to a RawFileSystem
type protocolServer struct {
fileSystem RawFileSystem
interruptMu sync.Mutex
reqInflight []*request
connectionDead bool
latencies LatencyMap
kernelSettings InitIn
opts *MountOptions
// in-flight notify-retrieve queries
retrieveMu sync.Mutex
retrieveNext uint64
retrieveTab map[uint64]*retrieveCacheRequest // notifyUnique -> retrieve request
}
func (ms *protocolServer) handleRequest(h *operationHandler, req *request) {
ms.addInflight(req)
defer ms.dropInflight(req)
if req.status.Ok() && ms.opts.Debug {
ms.opts.Logger.Println(req.InputDebug())
}
if req.inHeader().NodeId == pollHackInode ||
req.inHeader().NodeId == FUSE_ROOT_ID && h.FileNames > 0 && req.filename() == pollHackName {
doPollHackLookup(ms, req)
} else if req.status.Ok() && h.Func == nil {
ms.opts.Logger.Printf("Unimplemented opcode %v", operationName(req.inHeader().Opcode))
req.status = ENOSYS
} else if req.status.Ok() {
h.Func(ms, req)
}
// Forget/NotifyReply do not wait for reply from filesystem server.
switch req.inHeader().Opcode {
case _OP_FORGET, _OP_BATCH_FORGET, _OP_NOTIFY_REPLY:
req.suppressReply = true
case _OP_INTERRUPT:
// ? what other status can interrupt generate?
if req.status.Ok() {
req.suppressReply = true
}
}
if req.status == EINTR {
ms.interruptMu.Lock()
dead := ms.connectionDead
ms.interruptMu.Unlock()
if dead {
req.suppressReply = true
}
}
if req.suppressReply {
return
}
if req.fdData != nil && ms.opts.DisableSplice {
req.outPayload, req.status = req.fdData.Bytes(req.outPayload)
req.fdData = nil
}
req.serializeHeader(req.outPayloadSize())
if ms.opts.Debug {
ms.opts.Logger.Println(req.OutputDebug())
}
}
func (ms *protocolServer) addInflight(req *request) {
ms.interruptMu.Lock()
defer ms.interruptMu.Unlock()
req.inflightIndex = len(ms.reqInflight)
ms.reqInflight = append(ms.reqInflight, req)
}
func (ms *protocolServer) dropInflight(req *request) {
ms.interruptMu.Lock()
defer ms.interruptMu.Unlock()
this := req.inflightIndex
last := len(ms.reqInflight) - 1
if last != this {
ms.reqInflight[this] = ms.reqInflight[last]
ms.reqInflight[this].inflightIndex = this
}
ms.reqInflight = ms.reqInflight[:last]
}
func (ms *protocolServer) interruptRequest(unique uint64) Status {
ms.interruptMu.Lock()
defer ms.interruptMu.Unlock()
// This is slow, but this operation is rare.
for _, inflight := range ms.reqInflight {
if unique == inflight.inHeader().Unique && !inflight.interrupted {
close(inflight.cancel)
inflight.interrupted = true
return OK
}
}
return EAGAIN
}
func (ms *protocolServer) cancelAll() {
ms.interruptMu.Lock()
defer ms.interruptMu.Unlock()
ms.connectionDead = true
for _, req := range ms.reqInflight {
if !req.interrupted {
close(req.cancel)
req.interrupted = true
}
}
// Leave ms.reqInflight alone, or dropInflight will barf.
}

75
vendor/github.com/hanwen/go-fuse/v2/fuse/read.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2016 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 fuse
import (
"io"
"syscall"
)
// ReadResultData is the read return for returning bytes directly.
type readResultData struct {
// Raw bytes for the read.
Data []byte
}
func (r *readResultData) Size() int {
return len(r.Data)
}
func (r *readResultData) Done() {
}
func (r *readResultData) Bytes(buf []byte) ([]byte, Status) {
return r.Data, OK
}
func ReadResultData(b []byte) ReadResult {
return &readResultData{b}
}
func ReadResultFd(fd uintptr, off int64, sz int) ReadResult {
return &readResultFd{fd, off, sz}
}
// ReadResultFd is the read return for zero-copy file data.
type readResultFd struct {
// Splice from the following file.
Fd uintptr
// Offset within Fd, or -1 to use current offset.
Off int64
// Size of data to be loaded. Actual data available may be
// less at the EOF.
Sz int
}
// Reads raw bytes from file descriptor if necessary, using the passed
// buffer as storage.
func (r *readResultFd) Bytes(buf []byte) ([]byte, Status) {
sz := r.Sz
if len(buf) < sz {
sz = len(buf)
}
n, err := syscall.Pread(int(r.Fd), buf[:sz], r.Off)
if err == io.EOF {
err = nil
}
if n < 0 {
n = 0
}
return buf[:n], ToStatus(err)
}
func (r *readResultFd) Size() int {
return r.Sz
}
func (r *readResultFd) Done() {
}

303
vendor/github.com/hanwen/go-fuse/v2/fuse/request.go generated vendored Normal file
View File

@@ -0,0 +1,303 @@
// Copyright 2016 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 fuse
import (
"bytes"
"fmt"
"log"
"reflect"
"strings"
"time"
"unsafe"
)
var sizeOfOutHeader = unsafe.Sizeof(OutHeader{})
var zeroOutBuf [outputHeaderSize]byte
type request struct {
inflightIndex int
cancel chan struct{}
suppressReply bool
// written under Server.interruptMu
interrupted bool
// inHeader + opcode specific data
inputBuf []byte
// outHeader + opcode specific data.
outputBuf []byte
// Unstructured input (filenames, data for WRITE call)
inPayload []byte
// Output data.
status Status
// Unstructured output. Only one of these is non-nil.
outPayload []byte
fdData *readResultFd
// In case of read, keep read result here so we can call
// Done() on it.
readResult ReadResult
// Start timestamp for timing info.
startTime time.Time
}
// requestAlloc holds the request, plus I/O buffers, which are
// reused across requests.
type requestAlloc struct {
request
// Request storage. For large inputs and outputs, use data
// obtained through bufferpool.
bufferPoolInputBuf []byte
bufferPoolOutputBuf []byte
// For small pieces of data, we use the following inlines
// arrays:
//
// Output header and structured data.
outBuf [outputHeaderSize]byte
// Input, if small enough to fit here.
smallInputBuf [128]byte
}
func (r *request) inHeader() *InHeader {
return (*InHeader)(r.inData())
}
func (r *request) outHeader() *OutHeader {
return (*OutHeader)(unsafe.Pointer(&r.outputBuf[0]))
}
// TODO - benchmark to see if this is necessary?
func (r *request) clear() {
r.suppressReply = false
r.inputBuf = nil
r.outputBuf = nil
r.inPayload = nil
r.status = OK
r.outPayload = nil
r.fdData = nil
r.startTime = time.Time{}
r.readResult = nil
}
func asType(ptr unsafe.Pointer, typ interface{}) interface{} {
return reflect.NewAt(reflect.ValueOf(typ).Type(), ptr).Interface()
}
func typSize(typ interface{}) uintptr {
return reflect.ValueOf(typ).Type().Size()
}
func (r *request) InputDebug() string {
val := ""
hdr := r.inHeader()
h := getHandler(hdr.Opcode)
if h != nil && h.InType != nil {
val = Print(asType(r.inData(), h.InType))
}
names := ""
if h.FileNames == 1 {
names = fmt.Sprintf(" %q", r.filename())
} else if h.FileNames == 2 {
n1, n2 := r.filenames()
names = fmt.Sprintf(" %q %q", n1, n2)
} else if l := len(r.inPayload); l > 0 {
dots := ""
if l > 8 {
l = 8
dots = "..."
}
names = fmt.Sprintf("%q%s %db", r.inPayload[:l], dots, len(r.inPayload))
}
return fmt.Sprintf("rx %d: %s n%d %s%s p%d",
hdr.Unique, operationName(hdr.Opcode), hdr.NodeId,
val, names, hdr.Caller.Pid)
}
func (r *request) OutputDebug() string {
var dataStr string
h := getHandler(r.inHeader().Opcode)
if h != nil && h.OutType != nil && len(r.outputBuf) > int(sizeOfOutHeader) {
dataStr = Print(asType(r.outData(), h.OutType))
}
max := 1024
if len(dataStr) > max {
dataStr = dataStr[:max] + " ...trimmed"
}
flatStr := ""
if r.outPayloadSize() > 0 {
if h != nil && h.FileNameOut {
s := strings.TrimRight(string(r.outPayload), "\x00")
flatStr = fmt.Sprintf(" %q", s)
} else {
spl := ""
if r.fdData != nil {
spl = " (fd data)"
} else {
l := len(r.outPayload)
s := ""
if l > 8 {
l = 8
s = "..."
}
spl = fmt.Sprintf(" %q%s", r.outPayload[:l], s)
}
flatStr = fmt.Sprintf(" %db data%s", r.outPayloadSize(), spl)
}
}
extraStr := dataStr + flatStr
if extraStr != "" {
extraStr = ", " + extraStr
}
return fmt.Sprintf("tx %d: %v%s",
r.inHeader().Unique, r.status, extraStr)
}
// setInput returns true if it takes ownership of the argument, false if not.
func (r *requestAlloc) setInput(input []byte) bool {
if len(input) < len(r.smallInputBuf) {
copy(r.smallInputBuf[:], input)
r.inputBuf = r.smallInputBuf[:len(input)]
return false
}
r.inputBuf = input
r.bufferPoolInputBuf = input[:cap(input)]
return true
}
func (r *request) inData() unsafe.Pointer {
return unsafe.Pointer(&r.inputBuf[0])
}
// note: outSize is without OutHeader
func parseRequest(in []byte, kernelSettings *InitIn) (h *operationHandler, inSize, outSize, outPayloadSize int, errno Status) {
inSize = int(unsafe.Sizeof(InHeader{}))
if len(in) < inSize {
errno = EIO
return
}
inData := unsafe.Pointer(&in[0])
hdr := (*InHeader)(inData)
h = getHandler(hdr.Opcode)
if h == nil {
log.Printf("Unknown opcode %d", hdr.Opcode)
errno = ENOSYS
return
}
if h.InputSize > 0 {
inSize = int(h.InputSize)
}
if kernelSettings != nil && hdr.Opcode == _OP_RENAME && kernelSettings.supportsRenameSwap() {
inSize = int(unsafe.Sizeof(RenameIn{}))
}
if hdr.Opcode == _OP_INIT && inSize > len(in) {
// Minor version 36 extended the size of InitIn struct
inSize = len(in)
}
if len(in) < inSize {
log.Printf("Short read for %v: %q", h.Name, in)
errno = EIO
return
}
switch hdr.Opcode {
case _OP_READDIR, _OP_READDIRPLUS, _OP_READ:
outPayloadSize = int(((*ReadIn)(inData)).Size)
case _OP_GETXATTR, _OP_LISTXATTR:
outPayloadSize = int(((*GetXAttrIn)(inData)).Size)
case _OP_IOCTL:
outPayloadSize = int(((*IoctlIn)(inData)).OutSize)
}
outSize = int(h.OutputSize)
return
}
func (r *request) outData() unsafe.Pointer {
return unsafe.Pointer(&r.outputBuf[sizeOfOutHeader])
}
func (r *request) filename() string {
return string(r.inPayload[:len(r.inPayload)-1])
}
func (r *request) filenames() (string, string) {
i1 := bytes.IndexByte(r.inPayload, 0)
s1 := string(r.inPayload[:i1])
s2 := string(r.inPayload[i1+1 : len(r.inPayload)-1])
return s1, s2
}
// serializeHeader serializes the response header. The header points
// to an internal buffer of the receiver.
func (r *request) serializeHeader(outPayloadSize int) {
var dataLength uintptr
h := getHandler(r.inHeader().Opcode)
if h != nil {
dataLength = h.OutputSize
}
if r.status > OK {
// only do this for positive status; negative status
// is used for notification.
dataLength = 0
outPayloadSize = 0
}
// [GET|LIST]XATTR is two opcodes in one: get/list xattr size (return
// structured GetXAttrOut, no flat data) and get/list xattr data
// (return no structured data, but only flat data)
if r.inHeader().Opcode == _OP_GETXATTR || r.inHeader().Opcode == _OP_LISTXATTR {
if (*GetXAttrIn)(r.inData()).Size != 0 {
dataLength = 0
}
}
// The InitOut structure has 24 bytes (ie. TimeGran and
// further fields not available) in fuse version <= 22.
// https://john-millikin.com/the-fuse-protocol#FUSE_INIT
if r.inHeader().Opcode == _OP_INIT {
out := (*InitOut)(r.outData())
if out.Minor <= 22 {
dataLength = 24
}
}
o := r.outHeader()
o.Unique = r.inHeader().Unique
o.Status = int32(-r.status)
o.Length = uint32(
int(sizeOfOutHeader) + int(dataLength) + outPayloadSize)
r.outputBuf = r.outputBuf[:dataLength+sizeOfOutHeader]
if r.outPayload != nil {
r.outPayload = r.outPayload[:outPayloadSize]
}
}
func (r *request) outPayloadSize() int {
if r.fdData != nil {
return r.fdData.Size()
}
return len(r.outPayload)
}

View File

@@ -0,0 +1,13 @@
// Copyright 2016 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 fuse
const outputHeaderSize = 200
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 19
)

View File

@@ -0,0 +1,9 @@
package fuse
const outputHeaderSize = 160
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 28
)

View File

@@ -0,0 +1,13 @@
// Copyright 2016 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 fuse
const outputHeaderSize = 304
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 28
)

927
vendor/github.com/hanwen/go-fuse/v2/fuse/server.go generated vendored Normal file
View File

@@ -0,0 +1,927 @@
// Copyright 2016 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 fuse
import (
"fmt"
"log"
"math"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
const (
// Linux v4.20+ caps requests at 1 MiB. Older kernels at 128
// kiB. Deprecated: current linux kernels allow tuning this
// using sysctl.
MAX_KERNEL_WRITE = 1024 * 1024
// Linux kernel constant from include/uapi/linux/fuse.h
// Reads from /dev/fuse that are smaller fail with EINVAL.
_FUSE_MIN_READ_BUFFER = 8192
// defaultMaxWrite is the default value for MountOptions.MaxWrite
defaultMaxWrite = 128 * 1024 // 128 kiB
minMaxReaders = 2
maxMaxReaders = 16
)
// Server contains the logic for reading from the FUSE device and
// translating it to RawFileSystem interface calls.
type Server struct {
protocolServer
// Empty if unmounted.
mountPoint string
// writeMu serializes close and notify writes
writeMu sync.Mutex
// I/O with kernel and daemon.
mountFd int
opts *MountOptions
// maxReaders is the maximum number of goroutines reading requests
maxReaders int
// Pools for []byte
buffers bufferPool
// Pool for request structs.
reqPool sync.Pool
// Pool for raw requests data
readPool sync.Pool
reqMu sync.Mutex
reqReaders int
singleReader bool
canSplice bool
loops sync.WaitGroup
serving bool // for preventing duplicate Serve() calls
// Used to implement WaitMount on macos.
ready chan error
// for implementing single threaded processing.
requestProcessingMu sync.Mutex
}
// SetDebug is deprecated. Use MountOptions.Debug instead.
func (ms *Server) SetDebug(dbg bool) {
// This will typically trigger the race detector.
ms.opts.Debug = dbg
}
// KernelSettings returns the Init message from the kernel, so
// filesystems can adapt to availability of features of the kernel
// driver. The message should not be altered.
func (ms *Server) KernelSettings() *InitIn {
s := ms.kernelSettings
return &s
}
const _MAX_NAME_LEN = 20
// This type may be provided for recording latencies of each FUSE
// operation.
type LatencyMap interface {
Add(name string, dt time.Duration)
}
// RecordLatencies switches on collection of timing for each request
// coming from the kernel.P assing a nil argument switches off the
func (ms *Server) RecordLatencies(l LatencyMap) {
ms.latencies = l
}
// Unmount calls fusermount -u on the mount. This has the effect of
// shutting down the filesystem. After the Server is unmounted, it
// should be discarded. This function is idempotent.
//
// Does not work when we were mounted with the magic /dev/fd/N mountpoint syntax,
// as we do not know the real mountpoint. Unmount using
//
// fusermount -u /path/to/real/mountpoint
//
// in this case.
func (ms *Server) Unmount() (err error) {
if ms.mountPoint == "" {
return nil
}
if parseFuseFd(ms.mountPoint) >= 0 {
return fmt.Errorf("Cannot unmount magic mountpoint %q. Please use `fusermount -u REALMOUNTPOINT` instead.", ms.mountPoint)
}
delay := time.Duration(0)
for try := 0; try < 5; try++ {
err = unmount(ms.mountPoint, ms.opts)
if err == nil {
break
}
// Sleep for a bit. This is not pretty, but there is
// no way we can be certain that the kernel thinks all
// open files have already been closed.
delay = 2*delay + 5*time.Millisecond
time.Sleep(delay)
}
if err != nil {
return
}
// Wait for event loops to exit.
ms.loops.Wait()
ms.mountPoint = ""
return err
}
// alignSlice ensures that the byte at alignedByte is aligned with the
// given logical block size. The input slice should be at least (size
// + blockSize)
func alignSlice(buf []byte, alignedByte, blockSize, size uintptr) []byte {
misaligned := uintptr(unsafe.Pointer(&buf[alignedByte])) & (blockSize - 1)
buf = buf[blockSize-misaligned:]
return buf[:size]
}
// NewServer creates a FUSE server and attaches ("mounts") it to the
// `mountPoint` directory.
//
// See the "Mount styles" section in the package documentation if you want to
// know about the inner workings of the mount process. Usually you do not.
func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) {
if opts == nil {
opts = &MountOptions{
MaxBackground: _DEFAULT_BACKGROUND_TASKS,
}
}
o := *opts
if o.Logger == nil {
o.Logger = log.Default()
}
if o.MaxWrite < 0 {
o.MaxWrite = 0
}
if o.MaxWrite == 0 {
o.MaxWrite = defaultMaxWrite
}
kernelMaxWrite := getMaxWrite()
if o.MaxWrite > kernelMaxWrite {
o.MaxWrite = kernelMaxWrite
}
if o.MaxStackDepth == 0 {
o.MaxStackDepth = 1
}
if o.Name == "" {
name := fs.String()
l := len(name)
if l > _MAX_NAME_LEN {
l = _MAX_NAME_LEN
}
o.Name = strings.Replace(name[:l], ",", ";", -1)
}
maxReaders := runtime.GOMAXPROCS(0)
if maxReaders < minMaxReaders {
maxReaders = minMaxReaders
} else if maxReaders > maxMaxReaders {
maxReaders = maxMaxReaders
}
ms := &Server{
protocolServer: protocolServer{
fileSystem: fs,
retrieveTab: make(map[uint64]*retrieveCacheRequest),
opts: &o,
},
opts: &o,
maxReaders: maxReaders,
singleReader: useSingleReader,
ready: make(chan error, 1),
}
ms.reqPool.New = func() interface{} {
return &requestAlloc{
request: request{
cancel: make(chan struct{}),
},
}
}
ms.readPool.New = func() interface{} {
targetSize := o.MaxWrite + int(maxInputSize)
if targetSize < _FUSE_MIN_READ_BUFFER {
targetSize = _FUSE_MIN_READ_BUFFER
}
// O_DIRECT typically requires buffers aligned to
// blocksize (see man 2 open), but requirements vary
// across file systems. Presumably, we could also fix
// this by reading the requests using readv.
buf := make([]byte, targetSize+logicalBlockSize)
buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(targetSize))
return buf
}
mountPoint = filepath.Clean(mountPoint)
if !filepath.IsAbs(mountPoint) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint))
}
fd, err := mount(mountPoint, &o, ms.ready)
if err != nil {
return nil, err
}
ms.mountPoint = mountPoint
ms.mountFd = fd
if code := ms.handleInit(); !code.Ok() {
syscall.Close(fd)
// TODO - unmount as well?
return nil, fmt.Errorf("init: %s", code)
}
// This prepares for Serve being called somewhere, either
// synchronously or asynchronously.
ms.loops.Add(1)
return ms, nil
}
func escape(optionValue string) string {
return strings.Replace(strings.Replace(optionValue, `\`, `\\`, -1), `,`, `\,`, -1)
}
func (o *MountOptions) optionsStrings() []string {
var r []string
r = append(r, o.Options...)
if o.AllowOther {
r = append(r, "allow_other")
}
if o.FsName != "" {
r = append(r, "fsname="+o.FsName)
}
if o.Name != "" {
r = append(r, "subtype="+o.Name)
}
r = append(r, fmt.Sprintf("max_read=%d", o.MaxWrite))
// OSXFUSE applies a 60-second timeout for file operations. This
// is inconsistent with how FUSE works on Linux, where operations
// last as long as the daemon is willing to let them run.
if runtime.GOOS == "darwin" {
r = append(r, "daemon_timeout=0")
}
if o.IDMappedMount && !o.containsOption("default_permissions") {
r = append(r, "default_permissions")
}
// Commas and backslashs in an option need to be escaped, because
// options are separated by a comma and backslashs are used to
// escape other characters.
var rEscaped []string
for _, s := range r {
rEscaped = append(rEscaped, escape(s))
}
return rEscaped
}
func (o *MountOptions) containsOption(opt string) bool {
for _, o := range o.Options {
if o == opt {
return true
}
}
return false
}
// DebugData returns internal status information for debugging
// purposes.
func (ms *Server) DebugData() string {
var r int
ms.reqMu.Lock()
r = ms.reqReaders
ms.reqMu.Unlock()
return fmt.Sprintf("readers: %d", r)
}
// handleEINTR retries the given function until it doesn't return syscall.EINTR.
// This is similar to the HANDLE_EINTR() macro from Chromium ( see
// https://code.google.com/p/chromium/codesearch#chromium/src/base/posix/eintr_wrapper.h
// ) and the TEMP_FAILURE_RETRY() from glibc (see
// https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html
// ).
//
// Don't use handleEINTR() with syscall.Close(); see
// https://code.google.com/p/chromium/issues/detail?id=269623 .
func handleEINTR(fn func() error) (err error) {
for {
err = fn()
if err != syscall.EINTR {
break
}
}
return
}
// Returns a new request, or error. Returns
// nil, OK if we have too many readers already.
func (ms *Server) readRequest() (req *requestAlloc, code Status) {
ms.reqMu.Lock()
if ms.reqReaders > ms.maxReaders {
ms.reqMu.Unlock()
return nil, OK
}
ms.reqReaders++
ms.reqMu.Unlock()
reqIface := ms.reqPool.Get()
req = reqIface.(*requestAlloc)
destIface := ms.readPool.Get()
dest := destIface.([]byte)
var n int
err := handleEINTR(func() error {
var err error
n, err = syscall.Read(ms.mountFd, dest)
return err
})
if err != nil {
code = ToStatus(err)
ms.reqPool.Put(reqIface)
ms.reqMu.Lock()
ms.reqReaders--
ms.reqMu.Unlock()
return nil, code
}
if ms.latencies != nil {
req.startTime = time.Now()
}
ms.reqMu.Lock()
defer ms.reqMu.Unlock()
gobbled := req.setInput(dest[:n])
if len(req.inputBuf) < int(unsafe.Sizeof(InHeader{})) {
log.Printf("Short read for input header: %v", req.inputBuf)
return nil, EINVAL
}
opCode := ((*InHeader)(unsafe.Pointer(&req.inputBuf[0]))).Opcode
/* These messages don't expect reply, so they cost nothing for
the kernel to send. Make sure we're not overwhelmed by not
spawning a new reader.
*/
needsBackPressure := (opCode == _OP_FORGET || opCode == _OP_BATCH_FORGET)
if !gobbled {
ms.readPool.Put(destIface)
}
ms.reqReaders--
if !ms.singleReader && ms.reqReaders <= 0 && !needsBackPressure {
ms.loops.Add(1)
go ms.loop()
}
return req, OK
}
// returnRequest returns a request to the pool of unused requests.
func (ms *Server) returnRequest(req *requestAlloc) {
ms.recordStats(&req.request)
if req.bufferPoolOutputBuf != nil {
ms.buffers.FreeBuffer(req.bufferPoolOutputBuf)
req.bufferPoolOutputBuf = nil
}
if req.interrupted {
req.interrupted = false
req.cancel = make(chan struct{}, 0)
}
req.clear()
if p := req.bufferPoolInputBuf; p != nil {
req.bufferPoolInputBuf = nil
ms.readPool.Put(p)
}
ms.reqPool.Put(req)
}
func (ms *Server) recordStats(req *request) {
if ms.latencies != nil {
dt := time.Now().Sub(req.startTime)
opname := operationName(req.inHeader().Opcode)
ms.latencies.Add(opname, dt)
}
}
// Serve initiates the FUSE loop. Normally, callers should run Serve()
// and wait for it to exit, but tests will want to run this in a
// goroutine.
//
// Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() {
if ms.serving {
// Calling Serve() multiple times leads to a panic on unmount and fun
// debugging sessions ( https://github.com/hanwen/go-fuse/issues/512 ).
// Catch it early.
log.Panic("Serve() must only be called once, you have called it a second time")
}
ms.serving = true
ms.loop()
ms.loops.Wait()
ms.writeMu.Lock()
syscall.Close(ms.mountFd)
ms.writeMu.Unlock()
// shutdown in-flight cache retrieves.
//
// It is possible that umount comes in the middle - after retrieve
// request was sent to kernel, but corresponding kernel reply has not
// yet been read. We unblock all such readers and wake them up with ENODEV.
ms.retrieveMu.Lock()
rtab := ms.retrieveTab
// retrieve attempts might be erroneously tried even after close
// we have to keep retrieveTab !nil not to panic.
ms.retrieveTab = make(map[uint64]*retrieveCacheRequest)
ms.retrieveMu.Unlock()
for _, reading := range rtab {
reading.n = 0
reading.st = ENODEV
close(reading.ready)
}
ms.fileSystem.OnUnmount()
}
// Wait waits for the serve loop to exit. This should only be called
// after Serve has been called, or it will hang indefinitely.
func (ms *Server) Wait() {
ms.loops.Wait()
}
func (ms *Server) handleInit() Status {
// The first request should be INIT; read it synchronously,
// and don't spawn new readers.
orig := ms.singleReader
ms.singleReader = true
req, errNo := ms.readRequest()
ms.singleReader = orig
if errNo != OK || req == nil {
return errNo
}
if code := ms.handleRequest(req); !code.Ok() {
return code
}
if ms.kernelSettings.Minor >= 13 {
ms.setSplice()
}
// INIT is handled. Init the file system, but don't accept
// incoming requests, so the file system can setup itself.
ms.fileSystem.Init(ms)
return OK
}
// loop is the FUSE event loop. The simplistic way of calling this is
// with singleReader=true, which has a single goroutine reading the
// device, and spawning a new goroutine for each request. It is
// however 2x slower than processing the request inline with the
// reader. The latter requires more logic, because whenever we start
// processing the request, we have to make sure a new routine is
// spawned to read the device.
//
// Benchmark results i5-8350 pinned at 2Ghz:
//
// singleReader = true
//
// BenchmarkGoFuseRead 954 1137408 ns/op 1843.80 MB/s 5459 B/op 173 allocs/op
// BenchmarkGoFuseRead-2 1327 798635 ns/op 2625.92 MB/s 5072 B/op 169 allocs/op
// BenchmarkGoFuseStat 1530 750944 ns/op
// BenchmarkGoFuseStat-2 8455 120091 ns/op
// BenchmarkGoFuseReaddir 741 1561004 ns/op
// BenchmarkGoFuseReaddir-2 2508 599821 ns/op
//
// singleReader = false
//
// BenchmarkGoFuseRead 1890 671576 ns/op 3122.73 MB/s 5393 B/op 136 allocs/op
// BenchmarkGoFuseRead-2 2948 429578 ns/op 4881.88 MB/s 32235 B/op 157 allocs/op
// BenchmarkGoFuseStat 7886 153546 ns/op
// BenchmarkGoFuseStat-2 9310 121332 ns/op
// BenchmarkGoFuseReaddir 4074 361568 ns/op
// BenchmarkGoFuseReaddir-2 3511 319765 ns/op
func (ms *Server) loop() {
defer ms.loops.Done()
exit:
for {
req, errNo := ms.readRequest()
switch errNo {
case OK:
if req == nil {
break exit
}
case ENOENT:
continue
case ENODEV:
// Mount was killed. The obvious place to
// cancel outstanding requests is at the end
// of Serve, but that reader might be blocked.
ms.cancelAll()
if ms.opts.Debug {
ms.opts.Logger.Printf("received ENODEV (unmount request), thread exiting")
}
break exit
default: // some other error?
ms.opts.Logger.Printf("Failed to read from fuse conn: %v", errNo)
break exit
}
if ms.singleReader {
go ms.handleRequest(req)
} else {
ms.handleRequest(req)
}
}
}
func (ms *Server) handleRequest(req *requestAlloc) Status {
defer ms.returnRequest(req)
if ms.opts.SingleThreaded {
ms.requestProcessingMu.Lock()
defer ms.requestProcessingMu.Unlock()
}
h, inSize, outSize, outPayloadSize, code := parseRequest(req.inputBuf, &ms.kernelSettings)
if !code.Ok() {
ms.opts.Logger.Printf("parseRequest: %v", code)
return code
}
req.inPayload = req.inputBuf[inSize:]
req.inputBuf = req.inputBuf[:inSize]
req.outputBuf = req.outBuf[:outSize+int(sizeOfOutHeader)]
copy(req.outputBuf, zeroOutBuf[:])
if outPayloadSize > 0 {
req.outPayload = ms.buffers.AllocBuffer(uint32(outPayloadSize))
req.bufferPoolOutputBuf = req.outPayload
}
ms.protocolServer.handleRequest(h, &req.request)
if req.suppressReply {
return OK
}
errno := ms.write(&req.request)
if errno != 0 {
// Ignore ENOENT for INTERRUPT responses which
// indicates that the referred request is no longer
// known by the kernel. This is a normal if the
// referred request already has completed.
//
// Ignore ENOENT for RELEASE responses. When the FS
// is unmounted directly after a file close, the
// device can go away while we are still processing
// RELEASE. This is because RELEASE is analogous to
// FORGET, and is not synchronized with the calling
// process, but does require a response.
if ms.opts.Debug || !(errno == ENOENT && (req.inHeader().Opcode == _OP_INTERRUPT ||
req.inHeader().Opcode == _OP_RELEASEDIR ||
req.inHeader().Opcode == _OP_RELEASE)) {
ms.opts.Logger.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errno, operationName(req.inHeader().Opcode))
}
}
return errno
}
func (ms *Server) notifyWrite(req *request) Status {
req.serializeHeader(req.outPayloadSize())
if ms.opts.Debug {
ms.opts.Logger.Println(req.OutputDebug())
}
// Protect against concurrent close.
ms.writeMu.Lock()
result := ms.write(req)
ms.writeMu.Unlock()
if ms.opts.Debug {
h := getHandler(req.inHeader().Opcode)
ms.opts.Logger.Printf("Response %s: %v", h.Name, result)
}
return result
}
func newNotifyRequest(opcode uint32) *request {
r := &request{
inputBuf: make([]byte, unsafe.Sizeof(InHeader{})),
outputBuf: make([]byte, sizeOfOutHeader+getHandler(opcode).OutputSize),
status: map[uint32]Status{
_OP_NOTIFY_INVAL_INODE: NOTIFY_INVAL_INODE,
_OP_NOTIFY_INVAL_ENTRY: NOTIFY_INVAL_ENTRY,
_OP_NOTIFY_STORE_CACHE: NOTIFY_STORE_CACHE,
_OP_NOTIFY_RETRIEVE_CACHE: NOTIFY_RETRIEVE_CACHE,
_OP_NOTIFY_DELETE: NOTIFY_DELETE,
}[opcode],
}
r.inHeader().Opcode = opcode
return r
}
// InodeNotify invalidates the information associated with the inode
// (ie. data cache, attributes, etc.)
func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status {
if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_INODE) {
return ENOSYS
}
req := newNotifyRequest(_OP_NOTIFY_INVAL_INODE)
entry := (*NotifyInvalInodeOut)(req.outData())
entry.Ino = node
entry.Off = off
entry.Length = length
return ms.notifyWrite(req)
}
// InodeNotifyStoreCache tells kernel to store data into inode's cache.
//
// This call is similar to InodeNotify, but instead of only invalidating a data
// region, it gives updated data directly to the kernel.
func (ms *Server) InodeNotifyStoreCache(node uint64, offset int64, data []byte) Status {
if !ms.kernelSettings.SupportsNotify(NOTIFY_STORE_CACHE) {
return ENOSYS
}
for len(data) > 0 {
size := len(data)
if size > math.MaxInt32 {
// NotifyStoreOut has only uint32 for size.
// we check for max(int32), not max(uint32), because on 32-bit
// platforms int has only 31-bit for positive range.
size = math.MaxInt32
}
st := ms.inodeNotifyStoreCache32(node, offset, data[:size])
if st != OK {
return st
}
data = data[size:]
offset += int64(size)
}
return OK
}
// inodeNotifyStoreCache32 is internal worker for InodeNotifyStoreCache which
// handles data chunks not larger than 2GB.
func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte) Status {
req := newNotifyRequest(_OP_NOTIFY_STORE_CACHE)
store := (*NotifyStoreOut)(req.outData())
store.Nodeid = node
store.Offset = uint64(offset) // NOTE not int64, as it is e.g. in NotifyInvalInodeOut
store.Size = uint32(len(data))
req.outPayload = data
return ms.notifyWrite(req)
}
// InodeRetrieveCache retrieves data from kernel's inode cache.
//
// InodeRetrieveCache asks kernel to return data from its cache for inode at
// [offset:offset+len(dest)) and waits for corresponding reply. If kernel cache
// has fewer consecutive data starting at offset, that fewer amount is returned.
// In particular if inode data at offset is not cached (0, OK) is returned.
//
// The kernel returns ENOENT if it does not currently have entry for this inode
// in its dentry cache.
func (ms *Server) InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st Status) {
// the kernel won't send us in one go more then what we negotiated as MaxWrite.
// retrieve the data in chunks.
// TODO spawn some number of readahead retrievers in parallel.
ntotal := 0
for {
chunkSize := len(dest)
if chunkSize > ms.opts.MaxWrite {
chunkSize = ms.opts.MaxWrite
}
n, st = ms.inodeRetrieveCache1(node, offset, dest[:chunkSize])
if st != OK || n == 0 {
break
}
ntotal += n
offset += int64(n)
dest = dest[n:]
}
// if we could retrieve at least something - it is ok.
// if ntotal=0 - st will be st returned from first inodeRetrieveCache1.
if ntotal > 0 {
st = OK
}
return ntotal, st
}
// inodeRetrieveCache1 is internal worker for InodeRetrieveCache which
// actually talks to kernel and retrieves chunks not larger than ms.opts.MaxWrite.
func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n int, st Status) {
if !ms.kernelSettings.SupportsNotify(NOTIFY_RETRIEVE_CACHE) {
return 0, ENOSYS
}
req := newNotifyRequest(_OP_NOTIFY_RETRIEVE_CACHE)
// retrieve up to 2GB not to overflow uint32 size in NotifyRetrieveOut.
// see InodeNotifyStoreCache in similar place for why it is only 2GB, not 4GB.
//
// ( InodeRetrieveCache calls us with chunks not larger than
// ms.opts.MaxWrite, but MaxWrite is int, so let's be extra cautious )
size := len(dest)
if size > math.MaxInt32 {
size = math.MaxInt32
}
dest = dest[:size]
q := (*NotifyRetrieveOut)(req.outData())
q.Nodeid = node
q.Offset = uint64(offset) // not int64, as it is e.g. in NotifyInvalInodeOut
q.Size = uint32(len(dest))
reading := &retrieveCacheRequest{
nodeid: q.Nodeid,
offset: q.Offset,
dest: dest,
ready: make(chan struct{}),
}
ms.retrieveMu.Lock()
q.NotifyUnique = ms.retrieveNext
ms.retrieveNext++
ms.retrieveTab[q.NotifyUnique] = reading
ms.retrieveMu.Unlock()
// Protect against concurrent close.
result := ms.notifyWrite(req)
if result != OK {
ms.retrieveMu.Lock()
r := ms.retrieveTab[q.NotifyUnique]
if r == reading {
delete(ms.retrieveTab, q.NotifyUnique)
} else if r == nil {
// ok - might be dequeued by umount
} else {
// although very unlikely, it is possible that kernel sends
// unexpected NotifyReply with our notifyUnique, then
// retrieveNext wraps, makes full cycle, and another
// retrieve request is made with the same notifyUnique.
ms.opts.Logger.Printf("W: INODE_RETRIEVE_CACHE: request with notifyUnique=%d mutated", q.NotifyUnique)
}
ms.retrieveMu.Unlock()
return 0, result
}
// NotifyRetrieveOut sent to the kernel successfully. Now the kernel
// have to return data in a separate write-style NotifyReply request.
// Wait for the result.
<-reading.ready
return reading.n, reading.st
}
// retrieveCacheRequest represents in-flight cache retrieve request.
type retrieveCacheRequest struct {
nodeid uint64
offset uint64
dest []byte
// reply status
n int
st Status
ready chan struct{}
}
// DeleteNotify notifies the kernel that an entry is removed from a
// directory. In many cases, this is equivalent to EntryNotify,
// except when the directory is in use, eg. as working directory of
// some process. You should not hold any FUSE filesystem locks, as that
// can lead to deadlock.
func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status {
if ms.kernelSettings.Minor < 18 {
return ms.EntryNotify(parent, name)
}
req := newNotifyRequest(_OP_NOTIFY_DELETE)
entry := (*NotifyInvalDeleteOut)(req.outData())
entry.Parent = parent
entry.Child = child
entry.NameLen = uint32(len(name))
// Many versions of FUSE generate stacktraces if the
// terminating null byte is missing.
nameBytes := make([]byte, len(name)+1)
copy(nameBytes, name)
nameBytes[len(nameBytes)-1] = '\000'
req.outPayload = nameBytes
return ms.notifyWrite(req)
}
// EntryNotify should be used if the existence status of an entry
// within a directory changes. You should not hold any FUSE filesystem
// locks, as that can lead to deadlock.
func (ms *Server) EntryNotify(parent uint64, name string) Status {
if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_ENTRY) {
return ENOSYS
}
req := newNotifyRequest(_OP_NOTIFY_INVAL_ENTRY)
entry := (*NotifyInvalEntryOut)(req.outData())
entry.Parent = parent
entry.NameLen = uint32(len(name))
// Many versions of FUSE generate stacktraces if the
// terminating null byte is missing.
nameBytes := make([]byte, len(name)+1)
copy(nameBytes, name)
nameBytes[len(nameBytes)-1] = '\000'
req.outPayload = nameBytes
return ms.notifyWrite(req)
}
// SupportsVersion returns true if the kernel supports the given
// protocol version or newer.
func (in *InitIn) SupportsVersion(maj, min uint32) bool {
return in.Major > maj || (in.Major == maj && in.Minor >= min)
}
// SupportsNotify returns whether a certain notification type is
// supported. Pass any of the NOTIFY_* types as argument.
func (in *InitIn) SupportsNotify(notifyType int) bool {
switch notifyType {
case NOTIFY_INVAL_ENTRY:
return in.SupportsVersion(7, 12)
case NOTIFY_INVAL_INODE:
return in.SupportsVersion(7, 12)
case NOTIFY_STORE_CACHE, NOTIFY_RETRIEVE_CACHE:
return in.SupportsVersion(7, 15)
case NOTIFY_DELETE:
return in.SupportsVersion(7, 18)
}
return false
}
// supportsRenameSwap returns whether the kernel supports the
// renamex_np(2) syscall. This is only supported on OS X.
func (in *InitIn) supportsRenameSwap() bool {
return in.Flags&CAP_RENAME_SWAP != 0
}
// WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty or not yet mounted)
// mountpoint, and the OS trying to setup the user-space mount.
func (ms *Server) WaitMount() error {
err := <-ms.ready
if err != nil {
return err
}
if parseFuseFd(ms.mountPoint) >= 0 {
// Magic `/dev/fd/N` mountpoint. We don't know the real mountpoint, so
// we cannot run the poll hack.
return nil
}
return pollHack(ms.mountPoint)
}
// parseFuseFd checks if `mountPoint` is the special form /dev/fd/N (with N >= 0),
// and returns N in this case. Returns -1 otherwise.
func parseFuseFd(mountPoint string) (fd int) {
dir, file := path.Split(mountPoint)
if dir != "/dev/fd/" {
return -1
}
fd, err := strconv.Atoi(file)
if err != nil || fd <= 0 {
return -1
}
return fd
}

View File

@@ -0,0 +1,40 @@
// Copyright 2016 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 fuse
import (
"syscall"
)
const useSingleReader = false
func (ms *Server) write(req *request) Status {
if req.outPayloadSize() == 0 {
err := handleEINTR(func() error {
_, err := syscall.Write(ms.mountFd, req.outputBuf)
return err
})
return ToStatus(err)
}
if req.fdData != nil {
if ms.canSplice {
err := ms.trySplice(req, req.fdData)
if err == nil {
req.readResult.Done()
return OK
}
ms.opts.Logger.Println("trySplice:", err)
}
req.outPayload, req.status = req.fdData.Bytes(req.outPayload)
req.serializeHeader(len(req.outPayload))
}
_, err := writev(ms.mountFd, [][]byte{req.outputBuf, req.outPayload})
if req.readResult != nil {
req.readResult.Done()
}
return ToStatus(err)
}

View File

@@ -0,0 +1,33 @@
//go:build !linux
package fuse
import (
"golang.org/x/sys/unix"
)
// OSX and FreeBSD has races when multiple routines read
// from the FUSE device: on unmount, sometime some reads
// do not error-out, meaning that unmount will hang.
const useSingleReader = true
func (ms *Server) write(req *request) Status {
if req.outPayloadSize() == 0 {
err := handleEINTR(func() error {
_, err := unix.Write(ms.mountFd, req.outputBuf)
return err
})
return ToStatus(err)
}
if req.fdData != nil {
req.outPayload, req.status = req.fdData.Bytes(req.outPayload)
req.serializeHeader(len(req.outPayload))
}
_, err := writev(int(ms.mountFd), [][]byte{req.outputBuf, req.outPayload})
if req.readResult != nil {
req.readResult.Done()
}
return ToStatus(err)
}

View File

@@ -0,0 +1,17 @@
// Copyright 2016 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 fuse
import (
"fmt"
)
func (s *Server) setSplice() {
s.canSplice = false
}
func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error {
return fmt.Errorf("unimplemented")
}

View File

@@ -0,0 +1,11 @@
package fuse
import "fmt"
func (s *Server) setSplice() {
s.canSplice = false
}
func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error {
return fmt.Errorf("unimplemented")
}

View File

@@ -0,0 +1,96 @@
// Copyright 2016 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 fuse
import (
"fmt"
"os"
"github.com/hanwen/go-fuse/v2/splice"
)
func (s *Server) setSplice() {
s.canSplice = splice.Resizable() && !s.opts.DisableSplice
}
// trySplice: Zero-copy read from fdData.Fd into /dev/fuse
//
// This is a four-step process:
//
// 1. Splice data form fdData.Fd into the "pair1" pipe buffer --> pair1: [payload]
// Now we know the actual payload length and can
// construct the reply header
// 2. Write header into the "pair2" pipe buffer --> pair2: [header]
// 4. Splice data from "pair1" into "pair2" --> pair2: [header][payload]
// 3. Splice the data from "pair2" into /dev/fuse
//
// This dance is necessary because header and payload cannot be split across
// two splices and we cannot seek in a pipe buffer.
func (ms *Server) trySplice(req *request, fdData *readResultFd) error {
var err error
// Get a pair of connected pipes
pair1, err := splice.Get()
if err != nil {
return err
}
defer splice.Done(pair1)
// Grow buffer pipe to requested size + one extra page
// Without the extra page the kernel will block once the pipe is almost full
pair1Sz := fdData.Size() + os.Getpagesize()
if err := pair1.Grow(pair1Sz); err != nil {
return err
}
// Read data from file
payloadLen, err := pair1.LoadFromAt(fdData.Fd, fdData.Size(), fdData.Off)
if err != nil {
// TODO - extract the data from splice.
return err
}
// Get another pair of connected pipes
pair2, err := splice.Get()
if err != nil {
return err
}
defer splice.Done(pair2)
// Grow pipe to header + actually read size + one extra page
// Without the extra page the kernel will block once the pipe is almost full
req.serializeHeader(payloadLen)
total := len(req.outputBuf) + payloadLen
pair2Sz := total + os.Getpagesize()
if err := pair2.Grow(pair2Sz); err != nil {
return err
}
// Write header into pair2
n, err := pair2.Write(req.outputBuf)
if err != nil {
return err
}
if n != len(req.outputBuf) {
return fmt.Errorf("Short write into splice: wrote %d, want %d", n, len(req.outputBuf))
}
// Write data into pair2
n, err = pair2.LoadFrom(pair1.ReadFd(), payloadLen)
if err != nil {
return err
}
if n != payloadLen {
return fmt.Errorf("Short splice: wrote %d, want %d", n, payloadLen)
}
// Write header + data to /dev/fuse
_, err = pair2.WriteTo(uintptr(ms.mountFd), total)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,128 @@
// Copyright 2016 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 fuse
import (
"bytes"
"syscall"
"unsafe"
)
func getxattr(path string, attr string, dest []byte) (sz int, errno int) {
pathBs, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, int(syscall.EINVAL)
}
attrBs, err := syscall.BytePtrFromString(attr)
if err != nil {
return 0, int(syscall.EINVAL)
}
size, _, errNo := syscall.Syscall6(
syscall.SYS_GETXATTR,
uintptr(unsafe.Pointer(pathBs)),
uintptr(unsafe.Pointer(attrBs)),
uintptr(unsafe.Pointer(&dest[0])),
uintptr(len(dest)),
0, 0)
return int(size), int(errNo)
}
func GetXAttr(path string, attr string, dest []byte) (value []byte, errno int) {
sz, errno := getxattr(path, attr, dest)
for sz > cap(dest) && errno == 0 {
dest = make([]byte, sz)
sz, errno = getxattr(path, attr, dest)
}
if errno != 0 {
return nil, errno
}
return dest[:sz], errno
}
func listxattr(path string, dest []byte) (sz int, errno int) {
pathbs, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, int(syscall.EINVAL)
}
var destPointer unsafe.Pointer
if len(dest) > 0 {
destPointer = unsafe.Pointer(&dest[0])
}
size, _, errNo := syscall.Syscall(
syscall.SYS_LISTXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(destPointer),
uintptr(len(dest)))
return int(size), int(errNo)
}
func ListXAttr(path string) (attributes []string, errno int) {
dest := make([]byte, 0)
sz, errno := listxattr(path, dest)
if errno != 0 {
return nil, errno
}
for sz > cap(dest) && errno == 0 {
dest = make([]byte, sz)
sz, errno = listxattr(path, dest)
}
// -1 to drop the final empty slice.
dest = dest[:sz-1]
attributesBytes := bytes.Split(dest, []byte{0})
attributes = make([]string, len(attributesBytes))
for i, v := range attributesBytes {
attributes[i] = string(v)
}
return attributes, errno
}
func Setxattr(path string, attr string, data []byte, flags int) (errno int) {
pathbs, err := syscall.BytePtrFromString(path)
if err != nil {
return int(syscall.EINVAL)
}
attrbs, err := syscall.BytePtrFromString(attr)
if err != nil {
return int(syscall.EINVAL)
}
_, _, errNo := syscall.Syscall6(
syscall.SYS_SETXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(unsafe.Pointer(attrbs)),
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
uintptr(flags), 0)
return int(errNo)
}
func Removexattr(path string, attr string) (errno int) {
pathbs, err := syscall.BytePtrFromString(path)
if err != nil {
return int(syscall.EINVAL)
}
attrbs, err := syscall.BytePtrFromString(attr)
if err != nil {
return int(syscall.EINVAL)
}
_, _, errNo := syscall.Syscall(
syscall.SYS_REMOVEXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(unsafe.Pointer(attrbs)), 0)
return int(errNo)
}

View File

@@ -0,0 +1,14 @@
// Copyright 2016 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 fuse
import (
"golang.org/x/sys/unix"
)
func writev(fd int, packet [][]byte) (n int, err error) {
n, err = unix.Writev(fd, packet)
return
}

View File

@@ -0,0 +1,47 @@
//go:build !linux
package fuse
import (
"os"
"syscall"
"unsafe"
)
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return n, err
}
// Until golang.orgx/sys/unix provides Writev for Darwin and FreeBSD,
// keep the syscall wrapping funcitons.
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
}

View File

@@ -0,0 +1,9 @@
// Copyright 2016 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 fuse
func (a *Attr) String() string {
return Print(a)
}

872
vendor/github.com/hanwen/go-fuse/v2/fuse/types.go generated vendored Normal file
View File

@@ -0,0 +1,872 @@
// Copyright 2016 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 fuse
import (
"io"
"syscall"
"time"
)
const (
_DEFAULT_BACKGROUND_TASKS = 12
)
// Status is the errno number that a FUSE call returns to the kernel.
type Status int32
const (
OK = Status(0)
// EACCESS Permission denied
EACCES = Status(syscall.EACCES)
// EBUSY Device or resource busy
EBUSY = Status(syscall.EBUSY)
// EAGAIN Resource temporarily unavailable
EAGAIN = Status(syscall.EAGAIN)
// EINTR Call was interrupted
EINTR = Status(syscall.EINTR)
// EINVAL Invalid argument
EINVAL = Status(syscall.EINVAL)
// EIO I/O error
EIO = Status(syscall.EIO)
// ENOENT No such file or directory
ENOENT = Status(syscall.ENOENT)
// ENOSYS Function not implemented
ENOSYS = Status(syscall.ENOSYS)
// ENOTDIR Not a directory
ENOTDIR = Status(syscall.ENOTDIR)
// ENOTSUP Not supported
ENOTSUP = Status(syscall.ENOTSUP)
// EISDIR Is a directory
EISDIR = Status(syscall.EISDIR)
// EPERM Operation not permitted
EPERM = Status(syscall.EPERM)
// ERANGE Math result not representable
ERANGE = Status(syscall.ERANGE)
// EXDEV Cross-device link
EXDEV = Status(syscall.EXDEV)
// EBADF Bad file number
EBADF = Status(syscall.EBADF)
// ENODEV No such device
ENODEV = Status(syscall.ENODEV)
// EROFS Read-only file system
EROFS = Status(syscall.EROFS)
)
type ForgetIn struct {
InHeader
Nlookup uint64
}
// batch forget is handled internally.
type _ForgetOne struct {
NodeId uint64
Nlookup uint64
}
// batch forget is handled internally.
type _BatchForgetIn struct {
InHeader
Count uint32
Dummy uint32
}
type MkdirIn struct {
InHeader
// The mode for the new directory. The calling process' umask
// is already factored into the mode.
Mode uint32
Umask uint32
}
type Rename1In struct {
InHeader
Newdir uint64
}
type RenameIn struct {
InHeader
Newdir uint64
Flags uint32
Padding uint32
}
type LinkIn struct {
InHeader
Oldnodeid uint64
}
type Owner struct {
Uid uint32
Gid uint32
}
const ( // SetAttrIn.Valid
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2)
FATTR_SIZE = (1 << 3)
FATTR_ATIME = (1 << 4)
FATTR_MTIME = (1 << 5)
FATTR_FH = (1 << 6)
FATTR_ATIME_NOW = (1 << 7)
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
FATTR_CTIME = (1 << 10)
FATTR_KILL_SUIDGID = (1 << 11)
)
type SetAttrInCommon struct {
InHeader
Valid uint32
Padding uint32
Fh uint64
Size uint64
LockOwner uint64
Atime uint64
Mtime uint64
Ctime uint64
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Mode uint32
Unused4 uint32
Owner
Unused5 uint32
}
// GetFh returns the file handle if available, or 0 if undefined.
func (s *SetAttrInCommon) GetFh() (uint64, bool) {
if s.Valid&FATTR_FH != 0 {
return s.Fh, true
}
return 0, false
}
func (s *SetAttrInCommon) GetMode() (uint32, bool) {
if s.Valid&FATTR_MODE != 0 {
return s.Mode & 07777, true
}
return 0, false
}
func (s *SetAttrInCommon) GetUID() (uint32, bool) {
if s.Valid&FATTR_UID != 0 {
return s.Uid, true
}
return ^uint32(0), false
}
func (s *SetAttrInCommon) GetGID() (uint32, bool) {
if s.Valid&FATTR_GID != 0 {
return s.Gid, true
}
return ^uint32(0), false
}
func (s *SetAttrInCommon) GetSize() (uint64, bool) {
if s.Valid&FATTR_SIZE != 0 {
return s.Size, true
}
return 0, false
}
func (s *SetAttrInCommon) GetMTime() (time.Time, bool) {
var t time.Time
if s.Valid&FATTR_MTIME != 0 {
if s.Valid&FATTR_MTIME_NOW != 0 {
t = time.Now()
} else {
t = time.Unix(int64(s.Mtime), int64(s.Mtimensec))
}
return t, true
}
return t, false
}
func (s *SetAttrInCommon) GetATime() (time.Time, bool) {
var t time.Time
if s.Valid&FATTR_ATIME != 0 {
if s.Valid&FATTR_ATIME_NOW != 0 {
t = time.Now()
} else {
t = time.Unix(int64(s.Atime), int64(s.Atimensec))
}
return t, true
}
return t, false
}
func (s *SetAttrInCommon) GetCTime() (time.Time, bool) {
var t time.Time
if s.Valid&FATTR_CTIME != 0 {
t = time.Unix(int64(s.Ctime), int64(s.Ctimensec))
return t, true
}
return t, false
}
const RELEASE_FLUSH = (1 << 0)
type ReleaseIn struct {
InHeader
Fh uint64
Flags uint32
ReleaseFlags uint32
LockOwner uint64
}
type OpenIn struct {
InHeader
Flags uint32
Mode uint32
}
const (
// OpenOut.Flags
FOPEN_DIRECT_IO = (1 << 0)
FOPEN_KEEP_CACHE = (1 << 1)
FOPEN_NONSEEKABLE = (1 << 2)
FOPEN_CACHE_DIR = (1 << 3)
FOPEN_STREAM = (1 << 4)
FOPEN_NOFLUSH = (1 << 5)
FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6)
FOPEN_PASSTHROUGH = (1 << 7)
)
type OpenOut struct {
Fh uint64
OpenFlags uint32
BackingID int32
}
// To be set in InitIn/InitOut.Flags.
//
// Keep in sync with either of
// * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fuse.h
// * https://github.com/libfuse/libfuse/blob/master/include/fuse_kernel.h
// but NOT with
// * https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// This file has CAP_HANDLE_KILLPRIV and CAP_POSIX_ACL reversed!
const (
CAP_ASYNC_READ = (1 << 0)
CAP_POSIX_LOCKS = (1 << 1)
CAP_FILE_OPS = (1 << 2)
CAP_ATOMIC_O_TRUNC = (1 << 3)
CAP_EXPORT_SUPPORT = (1 << 4)
CAP_BIG_WRITES = (1 << 5)
CAP_DONT_MASK = (1 << 6)
CAP_SPLICE_WRITE = (1 << 7)
CAP_SPLICE_MOVE = (1 << 8)
CAP_SPLICE_READ = (1 << 9)
CAP_FLOCK_LOCKS = (1 << 10)
CAP_IOCTL_DIR = (1 << 11)
CAP_AUTO_INVAL_DATA = (1 << 12) // mtime changes invalidate page cache.
CAP_READDIRPLUS = (1 << 13)
CAP_READDIRPLUS_AUTO = (1 << 14)
CAP_ASYNC_DIO = (1 << 15)
CAP_WRITEBACK_CACHE = (1 << 16)
CAP_NO_OPEN_SUPPORT = (1 << 17)
CAP_PARALLEL_DIROPS = (1 << 18)
CAP_HANDLE_KILLPRIV = (1 << 19)
CAP_POSIX_ACL = (1 << 20)
CAP_ABORT_ERROR = (1 << 21)
CAP_MAX_PAGES = (1 << 22)
CAP_CACHE_SYMLINKS = (1 << 23)
/* bits 24..31 differ across linux and mac */
/* bits 32..63 get shifted down 32 bits into the Flags2 field */
CAP_SECURITY_CTX = (1 << 32)
CAP_HAS_INODE_DAX = (1 << 33)
CAP_CREATE_SUPP_GROUP = (1 << 34)
CAP_HAS_EXPIRE_ONLY = (1 << 35)
CAP_DIRECT_IO_ALLOW_MMAP = (1 << 36)
CAP_PASSTHROUGH = (1 << 37)
CAP_NO_EXPORT_SUPPORT = (1 << 38)
CAP_HAS_RESEND = (1 << 39)
CAP_ALLOW_IDMAP = (1 << 40)
CAP_OVER_IO_URING = (1 << 41)
CAP_REQUEST_TIMEOUT = (1 << 42)
)
type InitIn struct {
InHeader
Major uint32
Minor uint32
MaxReadAhead uint32
Flags uint32
Flags2 uint32
Unused [11]uint32
}
func (i *InitIn) Flags64() uint64 {
return uint64(i.Flags) | uint64(i.Flags2)<<32
}
type InitOut struct {
Major uint32
Minor uint32
MaxReadAhead uint32
Flags uint32
MaxBackground uint16
CongestionThreshold uint16
MaxWrite uint32
TimeGran uint32
MaxPages uint16
Padding uint16
Flags2 uint32
MaxStackDepth uint32
RequestTimeout uint16
_Unused [11]uint16
}
func (o *InitOut) Flags64() uint64 {
return uint64(o.Flags) | uint64(o.Flags2)<<32
}
type _CuseInitIn struct {
InHeader
Major uint32
Minor uint32
Unused uint32
Flags uint32
}
type _CuseInitOut struct {
Major uint32
Minor uint32
Unused uint32
Flags uint32
MaxRead uint32
MaxWrite uint32
DevMajor uint32
DevMinor uint32
Spare [10]uint32
}
type InterruptIn struct {
InHeader
Unique uint64
}
type _BmapIn struct {
InHeader
Block uint64
Blocksize uint32
Padding uint32
}
type _BmapOut struct {
Block uint64
}
const (
IOCTL_COMPAT = (1 << 0)
IOCTL_UNRESTRICTED = (1 << 1)
IOCTL_RETRY = (1 << 2)
IOCTL_DIR = (1 << 4)
)
type IoctlIn struct {
InHeader
Fh uint64
Flags uint32
// The command, consisting of 16-bits metadata (direction + size) and 16-bits to encode the ioctl number
Cmd uint32
// The uint64 argument. If there is a payload, this is the
// payload address in the caller, and should not be used.
Arg uint64
// Size for the payload, non-zero if Cmd is WRITE or READ/WRITE.
InSize uint32
// Size for the payload, non-zero if Cmd is READ or READ/WRITE.
OutSize uint32
}
type IoctlOut struct {
Result int32
// The following fields are used for unrestricted ioctls,
// which are only enabled on CUSE.
Flags uint32
InIovs uint32
OutIovs uint32
}
type _PollIn struct {
InHeader
Fh uint64
Kh uint64
Flags uint32
Padding uint32
}
type _PollOut struct {
Revents uint32
Padding uint32
}
type _NotifyPollWakeupOut struct {
Kh uint64
}
type WriteOut struct {
Size uint32
Padding uint32
}
type GetXAttrOut struct {
Size uint32
Padding uint32
}
type FileLock struct {
Start uint64
End uint64
Typ uint32
Pid uint32
}
type LkIn struct {
InHeader
Fh uint64
Owner uint64
Lk FileLock
LkFlags uint32
Padding uint32
}
type LkOut struct {
Lk FileLock
}
// For AccessIn.Mask.
const (
X_OK = 1
W_OK = 2
R_OK = 4
F_OK = 0
)
type AccessIn struct {
InHeader
Mask uint32
Padding uint32
}
type FsyncIn struct {
InHeader
Fh uint64
FsyncFlags uint32
Padding uint32
}
type OutHeader struct {
Length uint32
Status int32
Unique uint64
}
type NotifyInvalInodeOut struct {
Ino uint64
Off int64
Length int64
}
type NotifyInvalEntryOut struct {
Parent uint64
NameLen uint32
Padding uint32
}
type NotifyInvalDeleteOut struct {
Parent uint64
Child uint64
NameLen uint32
Padding uint32
}
type NotifyStoreOut struct {
Nodeid uint64
Offset uint64
Size uint32
Padding uint32
}
type NotifyRetrieveOut struct {
NotifyUnique uint64
Nodeid uint64
Offset uint64
Size uint32
Padding uint32
}
type NotifyRetrieveIn struct {
InHeader
Dummy1 uint64
Offset uint64
Size uint32
Dummy2 uint32
Dummy3 uint64
Dummy4 uint64
}
const (
// NOTIFY_POLL = -1 // notify kernel that a poll waiting for IO on a file handle should wake up
NOTIFY_INVAL_INODE = -2 // notify kernel that an inode should be invalidated
NOTIFY_INVAL_ENTRY = -3 // notify kernel that a directory entry should be invalidated
NOTIFY_STORE_CACHE = -4 // store data into kernel cache of an inode
NOTIFY_RETRIEVE_CACHE = -5 // retrieve data from kernel cache of an inode
NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted
NOTIFY_RESEND = -7
// NOTIFY_CODE_MAX = -6
)
type FlushIn struct {
InHeader
Fh uint64
Unused uint32
Padding uint32
LockOwner uint64
}
type LseekIn struct {
InHeader
Fh uint64
Offset uint64
Whence uint32
Padding uint32
}
type LseekOut struct {
Offset uint64
}
type CopyFileRangeIn struct {
InHeader
FhIn uint64
OffIn uint64
NodeIdOut uint64
FhOut uint64
OffOut uint64
Len uint64
Flags uint64
}
// EntryOut holds the result of a (directory,name) lookup. It has two
// TTLs, one for the (directory, name) lookup itself, and one for the
// attributes (eg. size, mode). The entry TTL also applies if the
// lookup result is ENOENT ("negative entry lookup")
type EntryOut struct {
NodeId uint64
Generation uint64
EntryValid uint64
AttrValid uint64
EntryValidNsec uint32
AttrValidNsec uint32
Attr
}
// EntryTimeout returns the timeout in nanoseconds for a directory
// entry (existence or non-existence of a file within a directory).
func (o *EntryOut) EntryTimeout() time.Duration {
return time.Duration(uint64(o.EntryValidNsec) + o.EntryValid*1e9)
}
// AttrTimeout returns the TTL in nanoseconds of the attribute data.
func (o *EntryOut) AttrTimeout() time.Duration {
return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9)
}
// SetEntryTimeout sets the entry TTL.
func (o *EntryOut) SetEntryTimeout(dt time.Duration) {
ns := int64(dt)
o.EntryValidNsec = uint32(ns % 1e9)
o.EntryValid = uint64(ns / 1e9)
}
// SetAttrTimeout sets the attribute TTL.
func (o *EntryOut) SetAttrTimeout(dt time.Duration) {
ns := int64(dt)
o.AttrValidNsec = uint32(ns % 1e9)
o.AttrValid = uint64(ns / 1e9)
}
// AttrOut is the type returned by the Getattr call.
type AttrOut struct {
AttrValid uint64
AttrValidNsec uint32
Dummy uint32
Attr
}
func (o *AttrOut) Timeout() time.Duration {
return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9)
}
func (o *AttrOut) SetTimeout(dt time.Duration) {
ns := int64(dt)
o.AttrValidNsec = uint32(ns % 1e9)
o.AttrValid = uint64(ns / 1e9)
}
type CreateOut struct {
EntryOut
OpenOut
}
// Caller has data on the process making the FS call.
//
// The UID and GID are effective UID/GID, except for the ACCESS
// opcode, where UID and GID are the real UIDs
type Caller struct {
Owner
Pid uint32
}
type InHeader struct {
Length uint32
Opcode uint32
Unique uint64
NodeId uint64
Caller
Padding uint32
}
type StatfsOut struct {
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Bsize uint32
NameLen uint32
Frsize uint32
Padding uint32
Spare [6]uint32
}
// _Dirent is what we send to the kernel, but we offer DirEntry and
// DirEntryList to the user.
type _Dirent struct {
Ino uint64
Off uint64
NameLen uint32
Typ uint32
}
const (
READ_LOCKOWNER = (1 << 1)
)
const (
WRITE_CACHE = (1 << 0)
WRITE_LOCKOWNER = (1 << 1)
WRITE_KILL_SUIDGID = (1 << 2)
)
type FallocateIn struct {
InHeader
Fh uint64
Offset uint64
Length uint64
Mode uint32
Padding uint32
}
func (lk *FileLock) ToFlockT(flockT *syscall.Flock_t) {
flockT.Start = int64(lk.Start)
if lk.End == (1<<63)-1 {
flockT.Len = 0
} else {
flockT.Len = int64(lk.End - lk.Start + 1)
}
flockT.Whence = int16(io.SeekStart)
flockT.Type = int16(lk.Typ)
}
func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) {
lk.Typ = uint32(flockT.Type)
if flockT.Type != syscall.F_UNLCK {
lk.Start = uint64(flockT.Start)
if flockT.Len == 0 {
lk.End = (1 << 63) - 1
} else {
lk.End = uint64(flockT.Start + flockT.Len - 1)
}
}
lk.Pid = uint32(flockT.Pid)
}
const (
// Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set.
FUSE_GETATTR_FH = (1 << 0)
)
type GetAttrIn struct {
InHeader
Flags_ uint32
Dummy uint32
Fh_ uint64
}
// Flags accesses the flags. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Flags() uint32 {
return g.Flags_
}
// Fh accesses the file handle. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Fh() uint64 {
return g.Fh_
}
type MknodIn struct {
InHeader
// Mode to use, including the Umask value
Mode uint32
Rdev uint32
Umask uint32
Padding uint32
}
type CreateIn struct {
InHeader
Flags uint32
// Mode for the new file; already takes Umask into account.
Mode uint32
// Umask used for this create call.
Umask uint32
Padding uint32
}
type ReadIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
ReadFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type WriteIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
WriteFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
// Data for registering a file as backing an inode.
type BackingMap struct {
Fd int32
Flags uint32
padding uint64
}
type SxTime struct {
Sec uint64
Nsec uint32
_reserved uint32
}
func (t *SxTime) Seconds() float64 {
return ft(t.Sec, t.Nsec)
}
type Statx struct {
Mask uint32
Blksize uint32
Attributes uint64
Nlink uint32
Uid uint32
Gid uint32
Mode uint16
_spare0 uint16
Ino uint64
Size uint64
Blocks uint64
AttributesMask uint64
Atime SxTime
Btime SxTime
Ctime SxTime
Mtime SxTime
RdevMajor uint32
RdevMinor uint32
DevMajor uint32
DevMinor uint32
_spare2 [14]uint64
}
type StatxIn struct {
InHeader
GetattrFlags uint32
_reserved uint32
Fh uint64
SxFlags uint32
SxMask uint32
}
type StatxOut struct {
AttrValid uint64
AttrValidNsec uint32
Flags uint32
_spare [2]uint64
Statx
}
func (o *StatxOut) Timeout() time.Duration {
return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9)
}
func (o *StatxOut) SetTimeout(dt time.Duration) {
ns := int64(dt)
o.AttrValidNsec = uint32(ns % 1e9)
o.AttrValid = uint64(ns / 1e9)
}

View File

@@ -0,0 +1,131 @@
// Copyright 2016 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 fuse
import (
"syscall"
)
const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS.
// EREMOTEIO is not supported on Darwin.
EREMOTEIO = Status(syscall.EIO)
)
type Attr struct {
Ino uint64
Size uint64
Blocks uint64
Atime uint64
Mtime uint64
Ctime uint64
Crtime_ uint64 // OS X
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Crtimensec_ uint32 // OS X
Mode uint32
Nlink uint32
Owner
Rdev uint32
Flags_ uint32 // OS X
Blksize uint32
Padding uint32
}
const (
FATTR_CRTIME = (1 << 28)
FATTR_CHGTIME = (1 << 29)
FATTR_BKUPTIME = (1 << 30)
FATTR_FLAGS = (1 << 31)
)
type SetAttrIn struct {
SetAttrInCommon
// OS X only
Bkuptime_ uint64
Chgtime_ uint64
Crtime uint64
BkuptimeNsec uint32
ChgtimeNsec uint32
CrtimeNsec uint32
Flags_ uint32 // see chflags(2)
}
const (
FOPEN_PURGE_ATTR = (1 << 30)
FOPEN_PURGE_UBC = (1 << 31)
)
type SetXAttrIn struct {
InHeader
Size uint32
Flags uint32
Position uint32
Padding uint32
}
type GetXAttrIn struct {
InHeader
Size uint32
Padding uint32
Position uint32
Padding2 uint32
}
const (
CAP_NODE_RWLOCK = (1 << 24)
CAP_RENAME_SWAP = (1 << 25)
CAP_RENAME_EXCL = (1 << 26)
CAP_ALLOCATE = (1 << 27)
CAP_EXCHANGE_DATA = (1 << 28)
CAP_CASE_INSENSITIVE = (1 << 29)
CAP_VOL_RENAME = (1 << 30)
CAP_XTIMES = (1 << 31)
// CAP_EXPLICIT_INVAL_DATA is not supported on Darwin.
CAP_EXPLICIT_INVAL_DATA = 0x0
)
type GetxtimesOut struct {
Bkuptime uint64
Crtime uint64
Bkuptimensec uint32
Crtimensec uint32
}
type ExchangeIn struct {
InHeader
Olddir uint64
Newdir uint64
Options uint64
}
func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks
s.Bfree = statfs.Bfree
s.Bavail = statfs.Bavail
s.Files = statfs.Files
s.Ffree = statfs.Ffree
s.Bsize = uint32(statfs.Iosize) // Iosize translates to Bsize: the optimal transfer size.
s.Frsize = s.Bsize // Bsize translates to Frsize: the minimum transfer size.
// The block counts are in units of statfs.Bsize.
// If s.Bsize != statfs.Bsize, we have to recalculate the block counts
// accordingly (s.Bsize is usually 256*statfs.Bsize).
if s.Bsize > statfs.Bsize {
adj := uint64(s.Bsize / statfs.Bsize)
s.Blocks /= adj
s.Bfree /= adj
s.Bavail /= adj
}
}
func (o *InitOut) setFlags(flags uint64) {
o.Flags = uint32(flags)
}

View File

@@ -0,0 +1,36 @@
package fuse
import "syscall"
const (
ENOATTR = Status(syscall.ENOATTR)
ENODATA = Status(syscall.EIO)
)
const (
CAP_NO_OPENDIR_SUPPORT = (1 << 24)
// The higher capabilities are not currently defined by FreeBSD.
// Ref: https://cgit.freebsd.org/src/tree/sys/fs/fuse/fuse_kernel.h
// CAP_RENAME_SWAP only exists on OSX.
CAP_RENAME_SWAP = 0x0
// CAP_EXPLICIT_INVAL_DATA is not supported on FreeBSD.
CAP_EXPLICIT_INVAL_DATA = 0x0
)
func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks
s.Bsize = uint32(statfs.Bsize)
s.Bfree = statfs.Bfree
s.Bavail = uint64(statfs.Bavail)
s.Files = statfs.Files
s.Ffree = uint64(statfs.Ffree)
s.Frsize = uint32(statfs.Bsize)
s.NameLen = uint32(statfs.Namemax)
}
func (o *InitOut) setFlags(flags uint64) {
o.Flags = uint32(flags)
}

View File

@@ -0,0 +1,59 @@
// Copyright 2016 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 fuse
import (
"syscall"
"golang.org/x/sys/unix"
)
const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA.
// EREMOTEIO Remote I/O error
EREMOTEIO = Status(syscall.EREMOTEIO)
)
// To be set in InitIn/InitOut.Flags.
//
// This flags conflict with https://github.com/macfuse/library/blob/master/include/fuse_common.h
// and should be used only on Linux.
const (
CAP_NO_OPENDIR_SUPPORT = (1 << 24)
CAP_EXPLICIT_INVAL_DATA = (1 << 25)
CAP_MAP_ALIGNMENT = (1 << 26)
CAP_SUBMOUNTS = (1 << 27)
CAP_HANDLE_KILLPRIV_V2 = (1 << 28)
CAP_SETXATTR_EXT = (1 << 29)
CAP_INIT_EXT = (1 << 30)
CAP_INIT_RESERVED = (1 << 31)
// CAP_RENAME_SWAP only exists on OSX.
CAP_RENAME_SWAP = 0x0
)
func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks
s.Bsize = uint32(statfs.Bsize)
s.Bfree = statfs.Bfree
s.Bavail = statfs.Bavail
s.Files = statfs.Files
s.Ffree = statfs.Ffree
s.Frsize = uint32(statfs.Frsize)
s.NameLen = uint32(statfs.Namelen)
}
func (o *InitOut) setFlags(flags uint64) {
o.Flags = uint32(flags) | CAP_INIT_EXT
o.Flags2 = uint32(flags >> 32)
}
func (t *SxTime) FromStatxTimestamp(ts *unix.StatxTimestamp) {
t.Sec = uint64(ts.Sec)
t.Nsec = ts.Nsec
}

41
vendor/github.com/hanwen/go-fuse/v2/fuse/types_unix.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
//go:build !darwin
package fuse
type Attr struct {
Ino uint64
Size uint64
// Blocks is the number of 512-byte blocks that the file occupies on disk.
Blocks uint64
Atime uint64
Mtime uint64
Ctime uint64
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Mode uint32
Nlink uint32
Owner
Rdev uint32
// Blksize is the preferred size for file system operations.
Blksize uint32
Padding uint32
}
type SetAttrIn struct {
SetAttrInCommon
}
type SetXAttrIn struct {
InHeader
Size uint32
Flags uint32
}
type GetXAttrIn struct {
InHeader
Size uint32
Padding uint32
}

60
vendor/github.com/hanwen/go-fuse/v2/internal/access.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// 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 internal
import (
"os/user"
"strconv"
)
// HasAccess tests if a caller can access a file with permissions
// `perm` in mode `mask`
func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool {
if callerUid == 0 {
// root can do anything.
return true
}
mask = mask & 7
if mask == 0 {
return true
}
if callerUid == fileUid {
if perm&(mask<<6) != 0 {
return true
}
}
if callerGid == fileGid {
if perm&(mask<<3) != 0 {
return true
}
}
if perm&mask != 0 {
return true
}
// Check other groups.
if perm&(mask<<3) == 0 {
// avoid expensive lookup if it's not allowed anyway
return false
}
u, err := user.LookupId(strconv.Itoa(int(callerUid)))
if err != nil {
return false
}
gs, err := u.GroupIds()
if err != nil {
return false
}
fileGidStr := strconv.Itoa(int(fileGid))
for _, gidStr := range gs {
if gidStr == fileGidStr {
return true
}
}
return false
}

View File

@@ -0,0 +1,9 @@
package fallocate
// Fallocate is a wrapper around fallocate syscall.
// On Linux, it is a wrapper around fallocate(2).
// On Darwin, it is a wrapper around fnctl(2).
// On FreeBSD, it is a wrapper around posix_fallocate(2).
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return fallocate(fd, mode, off, len)
}

View File

@@ -0,0 +1,56 @@
package fallocate
import (
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// TODO: Handle `mode` parameter.
_ = mode
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(len),
0,
}
_, _, errno := unix.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(unix.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
return errno
}
return nil
}

View File

@@ -0,0 +1,15 @@
package fallocate
import (
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// Ignore mode
_ = mode
ret, _, _ := unix.Syscall(unix.SYS_POSIX_FALLOCATE, uintptr(fd), uintptr(off), uintptr(len))
if ret != 0 {
return unix.Errno(ret)
}
return nil
}

View File

@@ -0,0 +1,7 @@
package fallocate
import "golang.org/x/sys/unix"
func fallocate(fd int, mode uint32, off int64, len int64) error {
return unix.Fallocate(fd, mode, off, len)
}

View File

@@ -0,0 +1,45 @@
package ioctl
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/ioctl.h
const (
NONE = 0x0
READ = 0x1
WRITE = 0x2
)
// The ioctl command. It encodes direction (read/write), argument size
// and a type/number, where type should be globally unique and number
// is unique to the driver.
type Command uint32
// New constructs an ioctl command. size should be less than 16kb.
func New(dir byte, typ byte, nr byte, size uintptr) Command {
if size >= (1 << 14) {
panic("invalid ioctl sizeof")
}
return Command(dir)<<(14+16) |
Command(size)<<16 |
Command(typ)<<8 |
Command(nr)
}
// Read returns true if the ioctl reads data
func (c Command) Read() bool {
return (c>>(14+16))&READ != 0
}
// Write returns true if the ioctl writes data
func (c Command) Write() bool {
return (c>>(14+16))&WRITE != 0
}
// Number returns the lower 8 bits of the command
func (c Command) Number() byte {
return byte(c & 0xff)
}
// Type returns the upper 8 bits of the command
func (c Command) Type() byte {
return byte((c >> 8) & 0xff)
}

View File

@@ -0,0 +1,35 @@
package openat
import (
"golang.org/x/sys/unix"
)
// OpenSymlinkAware is a symlink-aware syscall.Open replacement.
//
// What it does:
//
// 1. Open baseDir (usually an absolute path), following symlinks.
//
// The user may have set up the directory tree with symlinks,
// that's not neccessarily malicous, but a normal use case.
//
// 2. Open path (must be a relative path) within baseDir, rejecting symlinks with ELOOP.
//
// On Linux, it calls openat2(2) with RESOLVE_NO_SYMLINKS. This prevents following
// symlinks in any component of the path.
//
// On other platforms, it calls openat(2) with O_NOFOLLOW.
// TODO: This is insecure as O_NOFOLLOW only affects the final path component.
func OpenSymlinkAware(baseDir string, path string, flags int, mode uint32) (fd int, err error) {
// Passing an absolute path is a bug in the caller
if len(path) > 0 && path[0] == '/' {
return -1, unix.EINVAL
}
baseFd, err := unix.Open(baseDir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return -1, err
}
defer unix.Close(baseFd)
return openatNoSymlinks(baseFd, path, flags, mode)
}

View File

@@ -0,0 +1,20 @@
package openat
import (
"golang.org/x/sys/unix"
)
func openatNoSymlinks(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
how := unix.OpenHow{
// os/exec expects all fds to have O_CLOEXEC or it will leak them to subprocesses.
Flags: uint64(flags) | unix.O_CLOEXEC,
Mode: uint64(mode),
Resolve: unix.RESOLVE_NO_SYMLINKS,
}
fd, err = unix.Openat2(dirfd, path, &how)
if err != nil && err == unix.ENOSYS {
flags |= unix.O_NOFOLLOW | unix.O_CLOEXEC
fd, err = unix.Openat(dirfd, path, flags, mode)
}
return fd, err
}

View File

@@ -0,0 +1,15 @@
//go:build !linux
package openat
import (
"golang.org/x/sys/unix"
)
// TODO: This is insecure as O_NOFOLLOW only affects the final path component.
// See https://github.com/rfjakob/gocryptfs/issues/165 for how this could be handled.
func openatNoSymlinks(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
// os/exec expects all fds to have O_CLOEXEC or it will leak them to subprocesses.
flags |= unix.O_NOFOLLOW | unix.O_CLOEXEC
return unix.Openat(dirfd, path, flags, mode)
}

View File

@@ -0,0 +1,9 @@
package renameat
// Renameat is a wrapper around renameat syscall.
// On Linux, it is a wrapper around renameat2(2).
// On Darwin, it is a wrapper around renameatx_np(2).
// On FreeBSD, it is a wrapper around renameat(2).
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return renameat(olddirfd, oldpath, newdirfd, newpath, flags)
}

View File

@@ -0,0 +1,38 @@
package renameat
import (
"syscall"
"unsafe"
)
const (
SYS_RENAMEATX_NP = 488
RENAME_SWAP = 0x2
RENAME_EXCHANGE = RENAME_SWAP
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
oldpathCString, err := syscall.BytePtrFromString(oldpath)
if err != nil {
return err
}
newpathCString, err := syscall.BytePtrFromString(newpath)
if err != nil {
return err
}
_, _, errno := syscall.Syscall6(
SYS_RENAMEATX_NP,
uintptr(olddirfd),
uintptr(unsafe.Pointer(oldpathCString)),
uintptr(newdirfd),
uintptr(unsafe.Pointer(newpathCString)),
uintptr(flags),
0,
)
if errno != 0 {
return errno
}
return nil
}

View File

@@ -0,0 +1,17 @@
package renameat
import "golang.org/x/sys/unix"
const (
// Since FreeBSD does not currently privode renameat syscall
// beyond POSIX standard like Linux and Darwin do, we borrow
// the defination from Linux but reject these non-POSIX flags.
RENAME_EXCHANGE = (1 << 1)
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
if flags != 0 {
return unix.ENOSYS
}
return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
}

View File

@@ -0,0 +1,11 @@
package renameat
import "golang.org/x/sys/unix"
const (
RENAME_EXCHANGE = unix.RENAME_EXCHANGE
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return unix.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}

View File

@@ -0,0 +1,40 @@
// Copyright 2018 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 utimens
import (
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
)
// timeToTimeval converts time.Time to syscall.Timeval
func timeToTimeval(t *time.Time) 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
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}
// Fill converts a and m to a syscall.Timeval slice that can be passed
// to syscall.Utimes. Missing values (if any) are taken from attr
func Fill(a *time.Time, m *time.Time, attr *fuse.Attr) []syscall.Timeval {
if a == nil {
a2 := time.Unix(int64(attr.Atime), int64(attr.Atimensec))
a = &a2
}
if m == nil {
m2 := time.Unix(int64(attr.Mtime), int64(attr.Mtimensec))
m = &m2
}
tv := make([]syscall.Timeval, 2)
tv[0] = timeToTimeval(a)
tv[1] = timeToTimeval(m)
return tv
}

View File

@@ -0,0 +1,7 @@
// Copyright 2018 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 utimens
// placeholder file so this package exists on all platforms.

View File

@@ -0,0 +1,10 @@
// 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 xattr
import "golang.org/x/sys/unix"
// ENOATTR indicates that an extended attribute was not present.
const ENOATTR = unix.ENODATA

View File

@@ -0,0 +1,11 @@
//go:build !linux
// 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 xattr
import "golang.org/x/sys/unix"
const ENOATTR = unix.ENOATTR

Some files were not shown because too many files have changed in this diff Show More