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

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
}