rclone/vendor/bazil.org/fuse/fuse.go
Nick Craig-Wood 2aed3bf9ab vendor: roll back bazil.org/fuse to the last version which supports macOS #4393
Roll back the bazil.org/fuse update to give us some time to explore
alternatives for macOS.

See upstream issue: https://github.com/bazil/fuse/issues/224
2020-06-29 14:45:02 +01:00

2338 lines
60 KiB
Go

// See the file LICENSE for copyright and licensing information.
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
// which carries this notice:
//
// The files in this directory are subject to the following license.
//
// The author of this software is Russ Cox.
//
// Copyright (c) 2006 Russ Cox
//
// Permission to use, copy, modify, and distribute this software for any
// purpose without fee is hereby granted, provided that this entire notice
// is included in all copies of any software which is or includes a copy
// or modification of this software and in all copies of the supporting
// documentation for such software.
//
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
// FITNESS FOR ANY PARTICULAR PURPOSE.
// Package fuse enables writing FUSE file systems on Linux, OS X, and FreeBSD.
//
// On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
//
// There are two approaches to writing a FUSE file system. The first is to speak
// the low-level message protocol, reading from a Conn using ReadRequest and
// writing using the various Respond methods. This approach is closest to
// the actual interaction with the kernel and can be the simplest one in contexts
// such as protocol translators.
//
// Servers of synthesized file systems tend to share common
// bookkeeping abstracted away by the second approach, which is to
// call fs.Serve to serve the FUSE protocol using an implementation of
// the service methods in the interfaces FS* (file system), Node* (file
// or directory), and Handle* (opened file or directory).
// There are a daunting number of such methods that can be written,
// but few are required.
// The specific methods are described in the documentation for those interfaces.
//
// The hellofs subdirectory contains a simple illustration of the fs.Serve approach.
//
// Service Methods
//
// The required and optional methods for the FS, Node, and Handle interfaces
// have the general form
//
// Op(ctx context.Context, req *OpRequest, resp *OpResponse) error
//
// where Op is the name of a FUSE operation. Op reads request
// parameters from req and writes results to resp. An operation whose
// only result is the error result omits the resp parameter.
//
// Multiple goroutines may call service methods simultaneously; the
// methods being called are responsible for appropriate
// synchronization.
//
// The operation must not hold on to the request or response,
// including any []byte fields such as WriteRequest.Data or
// SetxattrRequest.Xattr.
//
// Errors
//
// Operations can return errors. The FUSE interface can only
// communicate POSIX errno error numbers to file system clients, the
// message is not visible to file system clients. The returned error
// can implement ErrorNumber to control the errno returned. Without
// ErrorNumber, a generic errno (EIO) is returned.
//
// Error messages will be visible in the debug log as part of the
// response.
//
// Interrupted Operations
//
// In some file systems, some operations
// may take an undetermined amount of time. For example, a Read waiting for
// a network message or a matching Write might wait indefinitely. If the request
// is cancelled and no longer needed, the context will be cancelled.
// Blocking operations should select on a receive from ctx.Done() and attempt to
// abort the operation early if the receive succeeds (meaning the channel is closed).
// To indicate that the operation failed because it was aborted, return syscall.EINTR.
//
// If an operation does not block for an indefinite amount of time, supporting
// cancellation is not necessary.
//
// Authentication
//
// All requests types embed a Header, meaning that the method can
// inspect req.Pid, req.Uid, and req.Gid as necessary to implement
// permission checking. The kernel FUSE layer normally prevents other
// users from accessing the FUSE file system (to change this, see
// AllowOther), but does not enforce access modes (to change this, see
// DefaultPermissions).
//
// Mount Options
//
// Behavior and metadata of the mounted file system can be changed by
// passing MountOption values to Mount.
//
package fuse // import "bazil.org/fuse"
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"sync"
"syscall"
"time"
"unsafe"
)
// A Conn represents a connection to a mounted FUSE file system.
type Conn struct {
// Ready is closed when the mount is complete or has failed.
Ready <-chan struct{}
// MountError stores any error from the mount process. Only valid
// after Ready is closed.
MountError error
// File handle for kernel communication. Only safe to access if
// rio or wio is held.
dev *os.File
wio sync.RWMutex
rio sync.RWMutex
// Protocol version negotiated with InitRequest/InitResponse.
proto Protocol
}
// MountpointDoesNotExistError is an error returned when the
// mountpoint does not exist.
type MountpointDoesNotExistError struct {
Path string
}
var _ error = (*MountpointDoesNotExistError)(nil)
func (e *MountpointDoesNotExistError) Error() string {
return fmt.Sprintf("mountpoint does not exist: %v", e.Path)
}
// Mount mounts a new FUSE connection on the named directory
// and returns a connection for reading and writing FUSE messages.
//
// After a successful return, caller must call Close to free
// resources.
//
// Even on successful return, the new mount is not guaranteed to be
// visible until after Conn.Ready is closed. See Conn.MountError for
// possible errors. Incoming requests on Conn must be served to make
// progress.
func Mount(dir string, options ...MountOption) (*Conn, error) {
conf := mountConfig{
options: make(map[string]string),
}
for _, option := range options {
if err := option(&conf); err != nil {
return nil, err
}
}
ready := make(chan struct{}, 1)
c := &Conn{
Ready: ready,
}
f, err := mount(dir, &conf, ready, &c.MountError)
if err != nil {
return nil, err
}
c.dev = f
if err := initMount(c, &conf); err != nil {
c.Close()
if err == ErrClosedWithoutInit {
// see if we can provide a better error
<-c.Ready
if err := c.MountError; err != nil {
return nil, err
}
}
return nil, err
}
return c, nil
}
type OldVersionError struct {
Kernel Protocol
LibraryMin Protocol
}
func (e *OldVersionError) Error() string {
return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin)
}
var (
ErrClosedWithoutInit = errors.New("fuse connection closed without init")
)
func initMount(c *Conn, conf *mountConfig) error {
req, err := c.ReadRequest()
if err != nil {
if err == io.EOF {
return ErrClosedWithoutInit
}
return err
}
r, ok := req.(*InitRequest)
if !ok {
return fmt.Errorf("missing init, got: %T", req)
}
min := Protocol{protoVersionMinMajor, protoVersionMinMinor}
if r.Kernel.LT(min) {
req.RespondError(Errno(syscall.EPROTO))
c.Close()
return &OldVersionError{
Kernel: r.Kernel,
LibraryMin: min,
}
}
proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor}
if r.Kernel.LT(proto) {
// Kernel doesn't support the latest version we have.
proto = r.Kernel
}
c.proto = proto
s := &InitResponse{
Library: proto,
MaxReadahead: conf.maxReadahead,
MaxWrite: maxWrite,
Flags: InitBigWrites | conf.initFlags,
}
r.Respond(s)
return nil
}
// A Request represents a single FUSE request received from the kernel.
// Use a type switch to determine the specific kind.
// A request of unrecognized type will have concrete type *Header.
type Request interface {
// Hdr returns the Header associated with this request.
Hdr() *Header
// RespondError responds to the request with the given error.
RespondError(error)
String() string
}
// A RequestID identifies an active FUSE request.
type RequestID uint64
func (r RequestID) String() string {
return fmt.Sprintf("%#x", uint64(r))
}
// A NodeID is a number identifying a directory or file.
// It must be unique among IDs returned in LookupResponses
// that have not yet been forgotten by ForgetRequests.
type NodeID uint64
func (n NodeID) String() string {
return fmt.Sprintf("%#x", uint64(n))
}
// A HandleID is a number identifying an open directory or file.
// It only needs to be unique while the directory or file is open.
type HandleID uint64
func (h HandleID) String() string {
return fmt.Sprintf("%#x", uint64(h))
}
// The RootID identifies the root directory of a FUSE file system.
const RootID NodeID = rootID
// A Header describes the basic information sent in every request.
type Header struct {
Conn *Conn `json:"-"` // connection this request was received on
ID RequestID // unique ID for request
Node NodeID // file or directory the request is about
Uid uint32 // user ID of process making request
Gid uint32 // group ID of process making request
Pid uint32 // process ID of process making request
// for returning to reqPool
msg *message
}
func (h *Header) String() string {
return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
}
func (h *Header) Hdr() *Header {
return h
}
func (h *Header) noResponse() {
putMessage(h.msg)
}
func (h *Header) respond(msg []byte) {
out := (*outHeader)(unsafe.Pointer(&msg[0]))
out.Unique = uint64(h.ID)
h.Conn.respond(msg)
putMessage(h.msg)
}
// An ErrorNumber is an error with a specific error number.
//
// Operations may return an error value that implements ErrorNumber to
// control what specific error number (errno) to return.
type ErrorNumber interface {
// Errno returns the the error number (errno) for this error.
Errno() Errno
}
// Deprecated: Return a syscall.Errno directly. See ToErrno for exact
// rules.
const (
// ENOSYS indicates that the call is not supported.
ENOSYS = Errno(syscall.ENOSYS)
// ESTALE is used by Serve to respond to violations of the FUSE protocol.
ESTALE = Errno(syscall.ESTALE)
ENOENT = Errno(syscall.ENOENT)
EIO = Errno(syscall.EIO)
EPERM = Errno(syscall.EPERM)
// EINTR indicates request was interrupted by an InterruptRequest.
// See also fs.Intr.
EINTR = Errno(syscall.EINTR)
ERANGE = Errno(syscall.ERANGE)
ENOTSUP = Errno(syscall.ENOTSUP)
EEXIST = Errno(syscall.EEXIST)
)
// DefaultErrno is the errno used when error returned does not
// implement ErrorNumber.
const DefaultErrno = EIO
var errnoNames = map[Errno]string{
ENOSYS: "ENOSYS",
ESTALE: "ESTALE",
ENOENT: "ENOENT",
EIO: "EIO",
EPERM: "EPERM",
EINTR: "EINTR",
EEXIST: "EEXIST",
Errno(syscall.ENAMETOOLONG): "ENAMETOOLONG",
}
// Errno implements Error and ErrorNumber using a syscall.Errno.
type Errno syscall.Errno
var _ = ErrorNumber(Errno(0))
var _ = error(Errno(0))
func (e Errno) Errno() Errno {
return e
}
func (e Errno) String() string {
return syscall.Errno(e).Error()
}
func (e Errno) Error() string {
return syscall.Errno(e).Error()
}
// ErrnoName returns the short non-numeric identifier for this errno.
// For example, "EIO".
func (e Errno) ErrnoName() string {
s := errnoNames[e]
if s == "" {
s = fmt.Sprint(e.Errno())
}
return s
}
func (e Errno) MarshalText() ([]byte, error) {
s := e.ErrnoName()
return []byte(s), nil
}
// ToErrno converts arbitrary errors to Errno.
//
// If the underlying type of err is syscall.Errno, it is used
// directly. No unwrapping is done, to prevent wrong errors from
// leaking via e.g. *os.PathError.
//
// If err unwraps to implement ErrorNumber, that is used.
//
// Finally, returns DefaultErrno.
func ToErrno(err error) Errno {
if err, ok := err.(syscall.Errno); ok {
return Errno(err)
}
var errnum ErrorNumber
if errors.As(err, &errnum) {
return Errno(errnum.Errno())
}
return DefaultErrno
}
func (h *Header) RespondError(err error) {
errno := ToErrno(err)
// FUSE uses negative errors!
// TODO: File bug report against OSXFUSE: positive error causes kernel panic.
buf := newBuffer(0)
hOut := (*outHeader)(unsafe.Pointer(&buf[0]))
hOut.Error = -int32(errno)
h.respond(buf)
}
// All requests read from the kernel, without data, are shorter than
// this.
var maxRequestSize = syscall.Getpagesize()
var bufSize = maxRequestSize + maxWrite
// reqPool is a pool of messages.
//
// Lifetime of a logical message is from getMessage to putMessage.
// getMessage is called by ReadRequest. putMessage is called by
// Conn.ReadRequest, Request.Respond, or Request.RespondError.
//
// Messages in the pool are guaranteed to have conn and off zeroed,
// buf allocated and len==bufSize, and hdr set.
var reqPool = sync.Pool{
New: allocMessage,
}
func allocMessage() interface{} {
m := &message{buf: make([]byte, bufSize)}
m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0]))
return m
}
func getMessage(c *Conn) *message {
m := reqPool.Get().(*message)
m.conn = c
return m
}
func putMessage(m *message) {
m.buf = m.buf[:bufSize]
m.conn = nil
m.off = 0
reqPool.Put(m)
}
// a message represents the bytes of a single FUSE message
type message struct {
conn *Conn
buf []byte // all bytes
hdr *inHeader // header
off int // offset for reading additional fields
}
func (m *message) len() uintptr {
return uintptr(len(m.buf) - m.off)
}
func (m *message) data() unsafe.Pointer {
var p unsafe.Pointer
if m.off < len(m.buf) {
p = unsafe.Pointer(&m.buf[m.off])
}
return p
}
func (m *message) bytes() []byte {
return m.buf[m.off:]
}
func (m *message) Header() Header {
h := m.hdr
return Header{
Conn: m.conn,
ID: RequestID(h.Unique),
Node: NodeID(h.Nodeid),
Uid: h.Uid,
Gid: h.Gid,
Pid: h.Pid,
msg: m,
}
}
// fileMode returns a Go os.FileMode from a Unix mode.
func fileMode(unixMode uint32) os.FileMode {
mode := os.FileMode(unixMode & 0777)
switch unixMode & syscall.S_IFMT {
case syscall.S_IFREG:
// nothing
case syscall.S_IFDIR:
mode |= os.ModeDir
case syscall.S_IFCHR:
mode |= os.ModeCharDevice | os.ModeDevice
case syscall.S_IFBLK:
mode |= os.ModeDevice
case syscall.S_IFIFO:
mode |= os.ModeNamedPipe
case syscall.S_IFLNK:
mode |= os.ModeSymlink
case syscall.S_IFSOCK:
mode |= os.ModeSocket
case 0:
// apparently there's plenty of times when the FUSE request
// does not contain the file type
mode |= os.ModeIrregular
default:
// not just unavailable in the kernel codepath; known to
// kernel but unrecognized by us
Debug(fmt.Sprintf("unrecognized file mode type: %04o", unixMode))
mode |= os.ModeIrregular
}
if unixMode&syscall.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if unixMode&syscall.S_ISGID != 0 {
mode |= os.ModeSetgid
}
return mode
}
type noOpcode struct {
Opcode uint32
}
func (m noOpcode) String() string {
return fmt.Sprintf("No opcode %v", m.Opcode)
}
type malformedMessage struct {
}
func (malformedMessage) String() string {
return "malformed message"
}
// Close closes the FUSE connection.
func (c *Conn) Close() error {
c.wio.Lock()
defer c.wio.Unlock()
c.rio.Lock()
defer c.rio.Unlock()
return c.dev.Close()
}
// caller must hold wio or rio
func (c *Conn) fd() int {
return int(c.dev.Fd())
}
func (c *Conn) Protocol() Protocol {
return c.proto
}
// ReadRequest returns the next FUSE request from the kernel.
//
// Caller must call either Request.Respond or Request.RespondError in
// a reasonable time. Caller must not retain Request after that call.
func (c *Conn) ReadRequest() (Request, error) {
m := getMessage(c)
loop:
c.rio.RLock()
n, err := syscall.Read(c.fd(), m.buf)
c.rio.RUnlock()
if err == syscall.EINTR {
// OSXFUSE sends EINTR to userspace when a request interrupt
// completed before it got sent to userspace?
goto loop
}
if err != nil && err != syscall.ENODEV {
putMessage(m)
return nil, err
}
if n <= 0 {
putMessage(m)
return nil, io.EOF
}
m.buf = m.buf[:n]
if n < inHeaderSize {
putMessage(m)
return nil, errors.New("fuse: message too short")
}
// FreeBSD FUSE sends a short length in the header
// for FUSE_INIT even though the actual read length is correct.
if n == inHeaderSize+initInSize && m.hdr.Opcode == opInit && m.hdr.Len < uint32(n) {
m.hdr.Len = uint32(n)
}
// OSXFUSE sometimes sends the wrong m.hdr.Len in a FUSE_WRITE message.
if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(writeIn{})) && m.hdr.Opcode == opWrite {
m.hdr.Len = uint32(n)
}
if m.hdr.Len != uint32(n) {
// prepare error message before returning m to pool
err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len)
putMessage(m)
return nil, err
}
m.off = inHeaderSize
// Convert to data structures.
// Do not trust kernel to hand us well-formed data.
var req Request
switch m.hdr.Opcode {
default:
Debug(noOpcode{Opcode: m.hdr.Opcode})
goto unrecognized
case opLookup:
buf := m.bytes()
n := len(buf)
if n == 0 || buf[n-1] != '\x00' {
goto corrupt
}
req = &LookupRequest{
Header: m.Header(),
Name: string(buf[:n-1]),
}
case opForget:
in := (*forgetIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &ForgetRequest{
Header: m.Header(),
N: in.Nlookup,
}
case opGetattr:
switch {
case c.proto.LT(Protocol{7, 9}):
req = &GetattrRequest{
Header: m.Header(),
}
default:
in := (*getattrIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &GetattrRequest{
Header: m.Header(),
Flags: GetattrFlags(in.GetattrFlags),
Handle: HandleID(in.Fh),
}
}
case opSetattr:
in := (*setattrIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &SetattrRequest{
Header: m.Header(),
Valid: SetattrValid(in.Valid),
Handle: HandleID(in.Fh),
Size: in.Size,
Atime: time.Unix(int64(in.Atime), int64(in.AtimeNsec)),
Mtime: time.Unix(int64(in.Mtime), int64(in.MtimeNsec)),
Mode: fileMode(in.Mode),
Uid: in.Uid,
Gid: in.Gid,
Bkuptime: in.BkupTime(),
Chgtime: in.Chgtime(),
Flags: in.Flags(),
}
case opReadlink:
if len(m.bytes()) > 0 {
goto corrupt
}
req = &ReadlinkRequest{
Header: m.Header(),
}
case opSymlink:
// m.bytes() is "newName\0target\0"
names := m.bytes()
if len(names) == 0 || names[len(names)-1] != 0 {
goto corrupt
}
i := bytes.IndexByte(names, '\x00')
if i < 0 {
goto corrupt
}
newName, target := names[0:i], names[i+1:len(names)-1]
req = &SymlinkRequest{
Header: m.Header(),
NewName: string(newName),
Target: string(target),
}
case opLink:
in := (*linkIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
newName := m.bytes()[unsafe.Sizeof(*in):]
if len(newName) < 2 || newName[len(newName)-1] != 0 {
goto corrupt
}
newName = newName[:len(newName)-1]
req = &LinkRequest{
Header: m.Header(),
OldNode: NodeID(in.Oldnodeid),
NewName: string(newName),
}
case opMknod:
size := mknodInSize(c.proto)
if m.len() < size {
goto corrupt
}
in := (*mknodIn)(m.data())
name := m.bytes()[size:]
if len(name) < 2 || name[len(name)-1] != '\x00' {
goto corrupt
}
name = name[:len(name)-1]
r := &MknodRequest{
Header: m.Header(),
Mode: fileMode(in.Mode),
Rdev: in.Rdev,
Name: string(name),
}
if c.proto.GE(Protocol{7, 12}) {
r.Umask = fileMode(in.Umask) & os.ModePerm
}
req = r
case opMkdir:
size := mkdirInSize(c.proto)
if m.len() < size {
goto corrupt
}
in := (*mkdirIn)(m.data())
name := m.bytes()[size:]
i := bytes.IndexByte(name, '\x00')
if i < 0 {
goto corrupt
}
r := &MkdirRequest{
Header: m.Header(),
Name: string(name[:i]),
// observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0,
// and this causes fileMode to go into it's "no idea"
// code branch; enforce type to directory
Mode: fileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR),
}
if c.proto.GE(Protocol{7, 12}) {
r.Umask = fileMode(in.Umask) & os.ModePerm
}
req = r
case opUnlink, opRmdir:
buf := m.bytes()
n := len(buf)
if n == 0 || buf[n-1] != '\x00' {
goto corrupt
}
req = &RemoveRequest{
Header: m.Header(),
Name: string(buf[:n-1]),
Dir: m.hdr.Opcode == opRmdir,
}
case opRename:
in := (*renameIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
newDirNodeID := NodeID(in.Newdir)
oldNew := m.bytes()[unsafe.Sizeof(*in):]
// oldNew should be "old\x00new\x00"
if len(oldNew) < 4 {
goto corrupt
}
if oldNew[len(oldNew)-1] != '\x00' {
goto corrupt
}
i := bytes.IndexByte(oldNew, '\x00')
if i < 0 {
goto corrupt
}
oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
req = &RenameRequest{
Header: m.Header(),
NewDir: newDirNodeID,
OldName: oldName,
NewName: newName,
}
case opOpendir, opOpen:
in := (*openIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &OpenRequest{
Header: m.Header(),
Dir: m.hdr.Opcode == opOpendir,
Flags: openFlags(in.Flags),
}
case opRead, opReaddir:
in := (*readIn)(m.data())
if m.len() < readInSize(c.proto) {
goto corrupt
}
r := &ReadRequest{
Header: m.Header(),
Dir: m.hdr.Opcode == opReaddir,
Handle: HandleID(in.Fh),
Offset: int64(in.Offset),
Size: int(in.Size),
}
if c.proto.GE(Protocol{7, 9}) {
r.Flags = ReadFlags(in.ReadFlags)
r.LockOwner = in.LockOwner
r.FileFlags = openFlags(in.Flags)
}
req = r
case opWrite:
in := (*writeIn)(m.data())
if m.len() < writeInSize(c.proto) {
goto corrupt
}
r := &WriteRequest{
Header: m.Header(),
Handle: HandleID(in.Fh),
Offset: int64(in.Offset),
Flags: WriteFlags(in.WriteFlags),
}
if c.proto.GE(Protocol{7, 9}) {
r.LockOwner = in.LockOwner
r.FileFlags = openFlags(in.Flags)
}
buf := m.bytes()[writeInSize(c.proto):]
if uint32(len(buf)) < in.Size {
goto corrupt
}
r.Data = buf
req = r
case opStatfs:
req = &StatfsRequest{
Header: m.Header(),
}
case opRelease, opReleasedir:
in := (*releaseIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &ReleaseRequest{
Header: m.Header(),
Dir: m.hdr.Opcode == opReleasedir,
Handle: HandleID(in.Fh),
Flags: openFlags(in.Flags),
ReleaseFlags: ReleaseFlags(in.ReleaseFlags),
LockOwner: in.LockOwner,
}
case opFsync, opFsyncdir:
in := (*fsyncIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &FsyncRequest{
Dir: m.hdr.Opcode == opFsyncdir,
Header: m.Header(),
Handle: HandleID(in.Fh),
Flags: in.FsyncFlags,
}
case opSetxattr:
in := (*setxattrIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
m.off += int(unsafe.Sizeof(*in))
name := m.bytes()
i := bytes.IndexByte(name, '\x00')
if i < 0 {
goto corrupt
}
xattr := name[i+1:]
if uint32(len(xattr)) < in.Size {
goto corrupt
}
xattr = xattr[:in.Size]
req = &SetxattrRequest{
Header: m.Header(),
Flags: in.Flags,
Position: in.position(),
Name: string(name[:i]),
Xattr: xattr,
}
case opGetxattr:
in := (*getxattrIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
name := m.bytes()[unsafe.Sizeof(*in):]
i := bytes.IndexByte(name, '\x00')
if i < 0 {
goto corrupt
}
req = &GetxattrRequest{
Header: m.Header(),
Name: string(name[:i]),
Size: in.Size,
Position: in.position(),
}
case opListxattr:
in := (*getxattrIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &ListxattrRequest{
Header: m.Header(),
Size: in.Size,
Position: in.position(),
}
case opRemovexattr:
buf := m.bytes()
n := len(buf)
if n == 0 || buf[n-1] != '\x00' {
goto corrupt
}
req = &RemovexattrRequest{
Header: m.Header(),
Name: string(buf[:n-1]),
}
case opFlush:
in := (*flushIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &FlushRequest{
Header: m.Header(),
Handle: HandleID(in.Fh),
Flags: in.FlushFlags,
LockOwner: in.LockOwner,
}
case opInit:
in := (*initIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &InitRequest{
Header: m.Header(),
Kernel: Protocol{in.Major, in.Minor},
MaxReadahead: in.MaxReadahead,
Flags: InitFlags(in.Flags),
}
case opGetlk:
panic("opGetlk")
case opSetlk:
panic("opSetlk")
case opSetlkw:
panic("opSetlkw")
case opAccess:
in := (*accessIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &AccessRequest{
Header: m.Header(),
Mask: in.Mask,
}
case opCreate:
size := createInSize(c.proto)
if m.len() < size {
goto corrupt
}
in := (*createIn)(m.data())
name := m.bytes()[size:]
i := bytes.IndexByte(name, '\x00')
if i < 0 {
goto corrupt
}
r := &CreateRequest{
Header: m.Header(),
Flags: openFlags(in.Flags),
Mode: fileMode(in.Mode),
Name: string(name[:i]),
}
if c.proto.GE(Protocol{7, 12}) {
r.Umask = fileMode(in.Umask) & os.ModePerm
}
req = r
case opInterrupt:
in := (*interruptIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
req = &InterruptRequest{
Header: m.Header(),
IntrID: RequestID(in.Unique),
}
case opBmap:
// bmap asks to map a byte offset within a file to a single
// uint64. On Linux, it triggers only with blkdev fuse mounts,
// that claim to be backed by an actual block device. FreeBSD
// seems to send it for just any fuse mount, whether there's a
// block device involved or not.
goto unrecognized
case opDestroy:
req = &DestroyRequest{
Header: m.Header(),
}
// OS X
case opSetvolname:
panic("opSetvolname")
case opGetxtimes:
panic("opGetxtimes")
case opExchange:
in := (*exchangeIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
oldDirNodeID := NodeID(in.Olddir)
newDirNodeID := NodeID(in.Newdir)
oldNew := m.bytes()[unsafe.Sizeof(*in):]
// oldNew should be "oldname\x00newname\x00"
if len(oldNew) < 4 {
goto corrupt
}
if oldNew[len(oldNew)-1] != '\x00' {
goto corrupt
}
i := bytes.IndexByte(oldNew, '\x00')
if i < 0 {
goto corrupt
}
oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
req = &ExchangeDataRequest{
Header: m.Header(),
OldDir: oldDirNodeID,
NewDir: newDirNodeID,
OldName: oldName,
NewName: newName,
// TODO options
}
}
return req, nil
corrupt:
Debug(malformedMessage{})
putMessage(m)
return nil, fmt.Errorf("fuse: malformed message")
unrecognized:
// Unrecognized message.
// Assume higher-level code will send a "no idea what you mean" error.
h := m.Header()
return &h, nil
}
type bugShortKernelWrite struct {
Written int64
Length int64
Error string
Stack string
}
func (b bugShortKernelWrite) String() string {
return fmt.Sprintf("short kernel write: written=%d/%d error=%q stack=\n%s", b.Written, b.Length, b.Error, b.Stack)
}
type bugKernelWriteError struct {
Error string
Stack string
}
func (b bugKernelWriteError) String() string {
return fmt.Sprintf("kernel write error: error=%q stack=\n%s", b.Error, b.Stack)
}
// safe to call even with nil error
func errorString(err error) string {
if err == nil {
return ""
}
return err.Error()
}
func (c *Conn) writeToKernel(msg []byte) error {
out := (*outHeader)(unsafe.Pointer(&msg[0]))
out.Len = uint32(len(msg))
c.wio.RLock()
defer c.wio.RUnlock()
nn, err := syscall.Write(c.fd(), msg)
if err == nil && nn != len(msg) {
Debug(bugShortKernelWrite{
Written: int64(nn),
Length: int64(len(msg)),
Error: errorString(err),
Stack: stack(),
})
}
return err
}
func (c *Conn) respond(msg []byte) {
if err := c.writeToKernel(msg); err != nil {
Debug(bugKernelWriteError{
Error: errorString(err),
Stack: stack(),
})
}
}
type notCachedError struct{}
func (notCachedError) Error() string {
return "node not cached"
}
var _ ErrorNumber = notCachedError{}
func (notCachedError) Errno() Errno {
// Behave just like if the original syscall.ENOENT had been passed
// straight through.
return ENOENT
}
var (
ErrNotCached = notCachedError{}
)
// sendInvalidate sends an invalidate notification to kernel.
//
// A returned ENOENT is translated to a friendlier error.
func (c *Conn) sendInvalidate(msg []byte) error {
switch err := c.writeToKernel(msg); err {
case syscall.ENOENT:
return ErrNotCached
default:
return err
}
}
// InvalidateNode invalidates the kernel cache of the attributes and a
// range of the data of a node.
//
// Giving offset 0 and size -1 means all data. To invalidate just the
// attributes, give offset 0 and size 0.
//
// Returns ErrNotCached if the kernel is not currently caching the
// node.
func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error {
buf := newBuffer(unsafe.Sizeof(notifyInvalInodeOut{}))
h := (*outHeader)(unsafe.Pointer(&buf[0]))
// h.Unique is 0
h.Error = notifyCodeInvalInode
out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{})))
out.Ino = uint64(nodeID)
out.Off = off
out.Len = size
return c.sendInvalidate(buf)
}
// InvalidateEntry invalidates the kernel cache of the directory entry
// identified by parent directory node ID and entry basename.
//
// Kernel may or may not cache directory listings. To invalidate
// those, use InvalidateNode to invalidate all of the data for a
// directory. (As of 2015-06, Linux FUSE does not cache directory
// listings.)
//
// Returns ErrNotCached if the kernel is not currently caching the
// node.
func (c *Conn) InvalidateEntry(parent NodeID, name string) error {
const maxUint32 = ^uint32(0)
if uint64(len(name)) > uint64(maxUint32) {
// very unlikely, but we don't want to silently truncate
return syscall.ENAMETOOLONG
}
buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1)
h := (*outHeader)(unsafe.Pointer(&buf[0]))
// h.Unique is 0
h.Error = notifyCodeInvalEntry
out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{})))
out.Parent = uint64(parent)
out.Namelen = uint32(len(name))
buf = append(buf, name...)
buf = append(buf, '\x00')
return c.sendInvalidate(buf)
}
// An InitRequest is the first request sent on a FUSE file system.
type InitRequest struct {
Header `json:"-"`
Kernel Protocol
// Maximum readahead in bytes that the kernel plans to use.
MaxReadahead uint32
Flags InitFlags
}
var _ = Request(&InitRequest{})
func (r *InitRequest) String() string {
return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
}
// An InitResponse is the response to an InitRequest.
type InitResponse struct {
Library Protocol
// Maximum readahead in bytes that the kernel can use. Ignored if
// greater than InitRequest.MaxReadahead.
MaxReadahead uint32
Flags InitFlags
// Maximum size of a single write operation.
// Linux enforces a minimum of 4 KiB.
MaxWrite uint32
}
func (r *InitResponse) String() string {
return fmt.Sprintf("Init %v ra=%d fl=%v w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxWrite)
}
// Respond replies to the request with the given response.
func (r *InitRequest) Respond(resp *InitResponse) {
buf := newBuffer(unsafe.Sizeof(initOut{}))
out := (*initOut)(buf.alloc(unsafe.Sizeof(initOut{})))
out.Major = resp.Library.Major
out.Minor = resp.Library.Minor
out.MaxReadahead = resp.MaxReadahead
out.Flags = uint32(resp.Flags)
out.MaxWrite = resp.MaxWrite
// MaxWrite larger than our receive buffer would just lead to
// errors on large writes.
if out.MaxWrite > maxWrite {
out.MaxWrite = maxWrite
}
r.respond(buf)
}
// A StatfsRequest requests information about the mounted file system.
type StatfsRequest struct {
Header `json:"-"`
}
var _ = Request(&StatfsRequest{})
func (r *StatfsRequest) String() string {
return fmt.Sprintf("Statfs [%s]", &r.Header)
}
// Respond replies to the request with the given response.
func (r *StatfsRequest) Respond(resp *StatfsResponse) {
buf := newBuffer(unsafe.Sizeof(statfsOut{}))
out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{})))
out.St = kstatfs{
Blocks: resp.Blocks,
Bfree: resp.Bfree,
Bavail: resp.Bavail,
Files: resp.Files,
Ffree: resp.Ffree,
Bsize: resp.Bsize,
Namelen: resp.Namelen,
Frsize: resp.Frsize,
}
r.respond(buf)
}
// A StatfsResponse is the response to a StatfsRequest.
type StatfsResponse struct {
Blocks uint64 // Total data blocks in file system.
Bfree uint64 // Free blocks in file system.
Bavail uint64 // Free blocks in file system if you're not root.
Files uint64 // Total files in file system.
Ffree uint64 // Free files in file system.
Bsize uint32 // Block size
Namelen uint32 // Maximum file name length?
Frsize uint32 // Fragment size, smallest addressable data size in the file system.
}
func (r *StatfsResponse) String() string {
return fmt.Sprintf("Statfs blocks=%d/%d/%d files=%d/%d bsize=%d frsize=%d namelen=%d",
r.Bavail, r.Bfree, r.Blocks,
r.Ffree, r.Files,
r.Bsize,
r.Frsize,
r.Namelen,
)
}
// An AccessRequest asks whether the file can be accessed
// for the purpose specified by the mask.
type AccessRequest struct {
Header `json:"-"`
Mask uint32
}
var _ = Request(&AccessRequest{})
func (r *AccessRequest) String() string {
return fmt.Sprintf("Access [%s] mask=%#x", &r.Header, r.Mask)
}
// Respond replies to the request indicating that access is allowed.
// To deny access, use RespondError.
func (r *AccessRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// An Attr is the metadata for a single file or directory.
type Attr struct {
Valid time.Duration // how long Attr can be cached
Inode uint64 // inode number
Size uint64 // size in bytes
Blocks uint64 // size in 512-byte units
Atime time.Time // time of last access
Mtime time.Time // time of last modification
Ctime time.Time // time of last inode change
Crtime time.Time // time of creation (OS X only)
Mode os.FileMode // file mode
Nlink uint32 // number of links (usually 1)
Uid uint32 // owner uid
Gid uint32 // group gid
Rdev uint32 // device numbers
Flags uint32 // chflags(2) flags (OS X only)
BlockSize uint32 // preferred blocksize for filesystem I/O
}
func (a Attr) String() string {
return fmt.Sprintf("valid=%v ino=%v size=%d mode=%v", a.Valid, a.Inode, a.Size, a.Mode)
}
func unix(t time.Time) (sec uint64, nsec uint32) {
nano := t.UnixNano()
sec = uint64(nano / 1e9)
nsec = uint32(nano % 1e9)
return
}
func (a *Attr) attr(out *attr, proto Protocol) {
out.Ino = a.Inode
out.Size = a.Size
out.Blocks = a.Blocks
out.Atime, out.AtimeNsec = unix(a.Atime)
out.Mtime, out.MtimeNsec = unix(a.Mtime)
out.Ctime, out.CtimeNsec = unix(a.Ctime)
out.SetCrtime(unix(a.Crtime))
out.Mode = uint32(a.Mode) & 0777
switch {
default:
out.Mode |= syscall.S_IFREG
case a.Mode&os.ModeDir != 0:
out.Mode |= syscall.S_IFDIR
case a.Mode&os.ModeDevice != 0:
if a.Mode&os.ModeCharDevice != 0 {
out.Mode |= syscall.S_IFCHR
} else {
out.Mode |= syscall.S_IFBLK
}
case a.Mode&os.ModeNamedPipe != 0:
out.Mode |= syscall.S_IFIFO
case a.Mode&os.ModeSymlink != 0:
out.Mode |= syscall.S_IFLNK
case a.Mode&os.ModeSocket != 0:
out.Mode |= syscall.S_IFSOCK
}
if a.Mode&os.ModeSetuid != 0 {
out.Mode |= syscall.S_ISUID
}
if a.Mode&os.ModeSetgid != 0 {
out.Mode |= syscall.S_ISGID
}
out.Nlink = a.Nlink
out.Uid = a.Uid
out.Gid = a.Gid
out.Rdev = a.Rdev
out.SetFlags(a.Flags)
if proto.GE(Protocol{7, 9}) {
out.Blksize = a.BlockSize
}
}
// A GetattrRequest asks for the metadata for the file denoted by r.Node.
type GetattrRequest struct {
Header `json:"-"`
Flags GetattrFlags
Handle HandleID
}
var _ = Request(&GetattrRequest{})
func (r *GetattrRequest) String() string {
return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags)
}
// Respond replies to the request with the given response.
func (r *GetattrRequest) Respond(resp *GetattrResponse) {
size := attrOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*attrOut)(buf.alloc(size))
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A GetattrResponse is the response to a GetattrRequest.
type GetattrResponse struct {
Attr Attr // file attributes
}
func (r *GetattrResponse) String() string {
return fmt.Sprintf("Getattr %v", r.Attr)
}
// A GetxattrRequest asks for the extended attributes associated with r.Node.
type GetxattrRequest struct {
Header `json:"-"`
// Maximum size to return.
Size uint32
// Name of the attribute requested.
Name string
// Offset within extended attributes.
//
// Only valid for OS X, and then only with the resource fork
// attribute.
Position uint32
}
var _ = Request(&GetxattrRequest{})
func (r *GetxattrRequest) String() string {
return fmt.Sprintf("Getxattr [%s] %q %d @%d", &r.Header, r.Name, r.Size, r.Position)
}
// Respond replies to the request with the given response.
func (r *GetxattrRequest) Respond(resp *GetxattrResponse) {
if r.Size == 0 {
buf := newBuffer(unsafe.Sizeof(getxattrOut{}))
out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{})))
out.Size = uint32(len(resp.Xattr))
r.respond(buf)
} else {
buf := newBuffer(uintptr(len(resp.Xattr)))
buf = append(buf, resp.Xattr...)
r.respond(buf)
}
}
// A GetxattrResponse is the response to a GetxattrRequest.
type GetxattrResponse struct {
Xattr []byte
}
func (r *GetxattrResponse) String() string {
return fmt.Sprintf("Getxattr %q", r.Xattr)
}
// A ListxattrRequest asks to list the extended attributes associated with r.Node.
type ListxattrRequest struct {
Header `json:"-"`
Size uint32 // maximum size to return
Position uint32 // offset within attribute list
}
var _ = Request(&ListxattrRequest{})
func (r *ListxattrRequest) String() string {
return fmt.Sprintf("Listxattr [%s] %d @%d", &r.Header, r.Size, r.Position)
}
// Respond replies to the request with the given response.
func (r *ListxattrRequest) Respond(resp *ListxattrResponse) {
if r.Size == 0 {
buf := newBuffer(unsafe.Sizeof(getxattrOut{}))
out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{})))
out.Size = uint32(len(resp.Xattr))
r.respond(buf)
} else {
buf := newBuffer(uintptr(len(resp.Xattr)))
buf = append(buf, resp.Xattr...)
r.respond(buf)
}
}
// A ListxattrResponse is the response to a ListxattrRequest.
type ListxattrResponse struct {
Xattr []byte
}
func (r *ListxattrResponse) String() string {
return fmt.Sprintf("Listxattr %q", r.Xattr)
}
// Append adds an extended attribute name to the response.
func (r *ListxattrResponse) Append(names ...string) {
for _, name := range names {
r.Xattr = append(r.Xattr, name...)
r.Xattr = append(r.Xattr, '\x00')
}
}
// A RemovexattrRequest asks to remove an extended attribute associated with r.Node.
type RemovexattrRequest struct {
Header `json:"-"`
Name string // name of extended attribute
}
var _ = Request(&RemovexattrRequest{})
func (r *RemovexattrRequest) String() string {
return fmt.Sprintf("Removexattr [%s] %q", &r.Header, r.Name)
}
// Respond replies to the request, indicating that the attribute was removed.
func (r *RemovexattrRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A SetxattrRequest asks to set an extended attribute associated with a file.
type SetxattrRequest struct {
Header `json:"-"`
// Flags can make the request fail if attribute does/not already
// exist. Unfortunately, the constants are platform-specific and
// not exposed by Go1.2. Look for XATTR_CREATE, XATTR_REPLACE.
//
// TODO improve this later
//
// TODO XATTR_CREATE and exist -> EEXIST
//
// TODO XATTR_REPLACE and not exist -> ENODATA
Flags uint32
// Offset within extended attributes.
//
// Only valid for OS X, and then only with the resource fork
// attribute.
Position uint32
Name string
Xattr []byte
}
var _ = Request(&SetxattrRequest{})
func trunc(b []byte, max int) ([]byte, string) {
if len(b) > max {
return b[:max], "..."
}
return b, ""
}
func (r *SetxattrRequest) String() string {
xattr, tail := trunc(r.Xattr, 16)
return fmt.Sprintf("Setxattr [%s] %q %q%s fl=%v @%#x", &r.Header, r.Name, xattr, tail, r.Flags, r.Position)
}
// Respond replies to the request, indicating that the extended attribute was set.
func (r *SetxattrRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A LookupRequest asks to look up the given name in the directory named by r.Node.
type LookupRequest struct {
Header `json:"-"`
Name string
}
var _ = Request(&LookupRequest{})
func (r *LookupRequest) String() string {
return fmt.Sprintf("Lookup [%s] %q", &r.Header, r.Name)
}
// Respond replies to the request with the given response.
func (r *LookupRequest) Respond(resp *LookupResponse) {
size := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*entryOut)(buf.alloc(size))
out.Nodeid = uint64(resp.Node)
out.Generation = resp.Generation
out.EntryValid = uint64(resp.EntryValid / time.Second)
out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A LookupResponse is the response to a LookupRequest.
type LookupResponse struct {
Node NodeID
Generation uint64
EntryValid time.Duration
Attr Attr
}
func (r *LookupResponse) string() string {
return fmt.Sprintf("%v gen=%d valid=%v attr={%v}", r.Node, r.Generation, r.EntryValid, r.Attr)
}
func (r *LookupResponse) String() string {
return fmt.Sprintf("Lookup %s", r.string())
}
// An OpenRequest asks to open a file or directory
type OpenRequest struct {
Header `json:"-"`
Dir bool // is this Opendir?
Flags OpenFlags
}
var _ = Request(&OpenRequest{})
func (r *OpenRequest) String() string {
return fmt.Sprintf("Open [%s] dir=%v fl=%v", &r.Header, r.Dir, r.Flags)
}
// Respond replies to the request with the given response.
func (r *OpenRequest) Respond(resp *OpenResponse) {
buf := newBuffer(unsafe.Sizeof(openOut{}))
out := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{})))
out.Fh = uint64(resp.Handle)
out.OpenFlags = uint32(resp.Flags)
r.respond(buf)
}
// A OpenResponse is the response to a OpenRequest.
type OpenResponse struct {
Handle HandleID
Flags OpenResponseFlags
}
func (r *OpenResponse) string() string {
return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags)
}
func (r *OpenResponse) String() string {
return fmt.Sprintf("Open %s", r.string())
}
// A CreateRequest asks to create and open a file (not a directory).
type CreateRequest struct {
Header `json:"-"`
Name string
Flags OpenFlags
Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&CreateRequest{})
func (r *CreateRequest) String() string {
return fmt.Sprintf("Create [%s] %q fl=%v mode=%v umask=%v", &r.Header, r.Name, r.Flags, r.Mode, r.Umask)
}
// Respond replies to the request with the given response.
func (r *CreateRequest) Respond(resp *CreateResponse) {
eSize := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(eSize + unsafe.Sizeof(openOut{}))
e := (*entryOut)(buf.alloc(eSize))
e.Nodeid = uint64(resp.Node)
e.Generation = resp.Generation
e.EntryValid = uint64(resp.EntryValid / time.Second)
e.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
e.AttrValid = uint64(resp.Attr.Valid / time.Second)
e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&e.Attr, r.Header.Conn.proto)
o := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{})))
o.Fh = uint64(resp.Handle)
o.OpenFlags = uint32(resp.Flags)
r.respond(buf)
}
// A CreateResponse is the response to a CreateRequest.
// It describes the created node and opened handle.
type CreateResponse struct {
LookupResponse
OpenResponse
}
func (r *CreateResponse) String() string {
return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string())
}
// A MkdirRequest asks to create (but not open) a directory.
type MkdirRequest struct {
Header `json:"-"`
Name string
Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&MkdirRequest{})
func (r *MkdirRequest) String() string {
return fmt.Sprintf("Mkdir [%s] %q mode=%v umask=%v", &r.Header, r.Name, r.Mode, r.Umask)
}
// Respond replies to the request with the given response.
func (r *MkdirRequest) Respond(resp *MkdirResponse) {
size := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*entryOut)(buf.alloc(size))
out.Nodeid = uint64(resp.Node)
out.Generation = resp.Generation
out.EntryValid = uint64(resp.EntryValid / time.Second)
out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A MkdirResponse is the response to a MkdirRequest.
type MkdirResponse struct {
LookupResponse
}
func (r *MkdirResponse) String() string {
return fmt.Sprintf("Mkdir %v", r.LookupResponse.string())
}
// A ReadRequest asks to read from an open file.
type ReadRequest struct {
Header `json:"-"`
Dir bool // is this Readdir?
Handle HandleID
Offset int64
Size int
Flags ReadFlags
LockOwner uint64
FileFlags OpenFlags
}
var _ = Request(&ReadRequest{})
func (r *ReadRequest) String() string {
return fmt.Sprintf("Read [%s] %v %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags)
}
// Respond replies to the request with the given response.
func (r *ReadRequest) Respond(resp *ReadResponse) {
buf := newBuffer(uintptr(len(resp.Data)))
buf = append(buf, resp.Data...)
r.respond(buf)
}
// A ReadResponse is the response to a ReadRequest.
type ReadResponse struct {
Data []byte
}
func (r *ReadResponse) String() string {
return fmt.Sprintf("Read %d", len(r.Data))
}
type jsonReadResponse struct {
Len uint64
}
func (r *ReadResponse) MarshalJSON() ([]byte, error) {
j := jsonReadResponse{
Len: uint64(len(r.Data)),
}
return json.Marshal(j)
}
// A ReleaseRequest asks to release (close) an open file handle.
type ReleaseRequest struct {
Header `json:"-"`
Dir bool // is this Releasedir?
Handle HandleID
Flags OpenFlags // flags from OpenRequest
ReleaseFlags ReleaseFlags
LockOwner uint32
}
var _ = Request(&ReleaseRequest{})
func (r *ReleaseRequest) String() string {
return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
}
// Respond replies to the request, indicating that the handle has been released.
func (r *ReleaseRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A DestroyRequest is sent by the kernel when unmounting the file system.
// No more requests will be received after this one, but it should still be
// responded to.
type DestroyRequest struct {
Header `json:"-"`
}
var _ = Request(&DestroyRequest{})
func (r *DestroyRequest) String() string {
return fmt.Sprintf("Destroy [%s]", &r.Header)
}
// Respond replies to the request.
func (r *DestroyRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A ForgetRequest is sent by the kernel when forgetting about r.Node
// as returned by r.N lookup requests.
type ForgetRequest struct {
Header `json:"-"`
N uint64
}
var _ = Request(&ForgetRequest{})
func (r *ForgetRequest) String() string {
return fmt.Sprintf("Forget [%s] %d", &r.Header, r.N)
}
// Respond replies to the request, indicating that the forgetfulness has been recorded.
func (r *ForgetRequest) Respond() {
// Don't reply to forget messages.
r.noResponse()
}
// A Dirent represents a single directory entry.
type Dirent struct {
// Inode this entry names.
Inode uint64
// Type of the entry, for example DT_File.
//
// Setting this is optional. The zero value (DT_Unknown) means
// callers will just need to do a Getattr when the type is
// needed. Providing a type can speed up operations
// significantly.
Type DirentType
// Name of the entry
Name string
}
// Type of an entry in a directory listing.
type DirentType uint32
const (
// These don't quite match os.FileMode; especially there's an
// explicit unknown, instead of zero value meaning file. They
// are also not quite syscall.DT_*; nothing says the FUSE
// protocol follows those, and even if they were, we don't
// want each fs to fiddle with syscall.
// The shift by 12 is hardcoded in the FUSE userspace
// low-level C library, so it's safe here.
DT_Unknown DirentType = 0
DT_Socket DirentType = syscall.S_IFSOCK >> 12
DT_Link DirentType = syscall.S_IFLNK >> 12
DT_File DirentType = syscall.S_IFREG >> 12
DT_Block DirentType = syscall.S_IFBLK >> 12
DT_Dir DirentType = syscall.S_IFDIR >> 12
DT_Char DirentType = syscall.S_IFCHR >> 12
DT_FIFO DirentType = syscall.S_IFIFO >> 12
)
func (t DirentType) String() string {
switch t {
case DT_Unknown:
return "unknown"
case DT_Socket:
return "socket"
case DT_Link:
return "link"
case DT_File:
return "file"
case DT_Block:
return "block"
case DT_Dir:
return "dir"
case DT_Char:
return "char"
case DT_FIFO:
return "fifo"
}
return "invalid"
}
// AppendDirent appends the encoded form of a directory entry to data
// and returns the resulting slice.
func AppendDirent(data []byte, dir Dirent) []byte {
de := dirent{
Ino: dir.Inode,
Namelen: uint32(len(dir.Name)),
Type: uint32(dir.Type),
}
de.Off = uint64(len(data) + direntSize + (len(dir.Name)+7)&^7)
data = append(data, (*[direntSize]byte)(unsafe.Pointer(&de))[:]...)
data = append(data, dir.Name...)
n := direntSize + uintptr(len(dir.Name))
if n%8 != 0 {
var pad [8]byte
data = append(data, pad[:8-n%8]...)
}
return data
}
// A WriteRequest asks to write to an open file.
type WriteRequest struct {
Header
Handle HandleID
Offset int64
Data []byte
Flags WriteFlags
LockOwner uint64
FileFlags OpenFlags
}
var _ = Request(&WriteRequest{})
func (r *WriteRequest) String() string {
return fmt.Sprintf("Write [%s] %v %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags)
}
type jsonWriteRequest struct {
Handle HandleID
Offset int64
Len uint64
Flags WriteFlags
}
func (r *WriteRequest) MarshalJSON() ([]byte, error) {
j := jsonWriteRequest{
Handle: r.Handle,
Offset: r.Offset,
Len: uint64(len(r.Data)),
Flags: r.Flags,
}
return json.Marshal(j)
}
// Respond replies to the request with the given response.
func (r *WriteRequest) Respond(resp *WriteResponse) {
buf := newBuffer(unsafe.Sizeof(writeOut{}))
out := (*writeOut)(buf.alloc(unsafe.Sizeof(writeOut{})))
out.Size = uint32(resp.Size)
r.respond(buf)
}
// A WriteResponse replies to a write indicating how many bytes were written.
type WriteResponse struct {
Size int
}
func (r *WriteResponse) String() string {
return fmt.Sprintf("Write %d", r.Size)
}
// A SetattrRequest asks to change one or more attributes associated with a file,
// as indicated by Valid.
type SetattrRequest struct {
Header `json:"-"`
Valid SetattrValid
Handle HandleID
Size uint64
Atime time.Time
Mtime time.Time
// Mode is the file mode to set (when valid).
//
// The type of the node (as in os.ModeType, os.ModeDir etc) is not
// guaranteed to be sent by the kernel, in which case
// os.ModeIrregular will be set.
Mode os.FileMode
Uid uint32
Gid uint32
// OS X only
Bkuptime time.Time
Chgtime time.Time
Crtime time.Time
Flags uint32 // see chflags(2)
}
var _ = Request(&SetattrRequest{})
func (r *SetattrRequest) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Setattr [%s]", &r.Header)
if r.Valid.Mode() {
fmt.Fprintf(&buf, " mode=%v", r.Mode)
}
if r.Valid.Uid() {
fmt.Fprintf(&buf, " uid=%d", r.Uid)
}
if r.Valid.Gid() {
fmt.Fprintf(&buf, " gid=%d", r.Gid)
}
if r.Valid.Size() {
fmt.Fprintf(&buf, " size=%d", r.Size)
}
if r.Valid.Atime() {
fmt.Fprintf(&buf, " atime=%v", r.Atime)
}
if r.Valid.AtimeNow() {
fmt.Fprintf(&buf, " atime=now")
}
if r.Valid.Mtime() {
fmt.Fprintf(&buf, " mtime=%v", r.Mtime)
}
if r.Valid.MtimeNow() {
fmt.Fprintf(&buf, " mtime=now")
}
if r.Valid.Handle() {
fmt.Fprintf(&buf, " handle=%v", r.Handle)
} else {
fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle)
}
if r.Valid.LockOwner() {
fmt.Fprintf(&buf, " lockowner")
}
if r.Valid.Crtime() {
fmt.Fprintf(&buf, " crtime=%v", r.Crtime)
}
if r.Valid.Chgtime() {
fmt.Fprintf(&buf, " chgtime=%v", r.Chgtime)
}
if r.Valid.Bkuptime() {
fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime)
}
if r.Valid.Flags() {
fmt.Fprintf(&buf, " flags=%v", r.Flags)
}
return buf.String()
}
// Respond replies to the request with the given response,
// giving the updated attributes.
func (r *SetattrRequest) Respond(resp *SetattrResponse) {
size := attrOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*attrOut)(buf.alloc(size))
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A SetattrResponse is the response to a SetattrRequest.
type SetattrResponse struct {
Attr Attr // file attributes
}
func (r *SetattrResponse) String() string {
return fmt.Sprintf("Setattr %v", r.Attr)
}
// A FlushRequest asks for the current state of an open file to be flushed
// to storage, as when a file descriptor is being closed. A single opened Handle
// may receive multiple FlushRequests over its lifetime.
type FlushRequest struct {
Header `json:"-"`
Handle HandleID
Flags uint32
LockOwner uint64
}
var _ = Request(&FlushRequest{})
func (r *FlushRequest) String() string {
return fmt.Sprintf("Flush [%s] %v fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
}
// Respond replies to the request, indicating that the flush succeeded.
func (r *FlushRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A RemoveRequest asks to remove a file or directory from the
// directory r.Node.
type RemoveRequest struct {
Header `json:"-"`
Name string // name of the entry to remove
Dir bool // is this rmdir?
}
var _ = Request(&RemoveRequest{})
func (r *RemoveRequest) String() string {
return fmt.Sprintf("Remove [%s] %q dir=%v", &r.Header, r.Name, r.Dir)
}
// Respond replies to the request, indicating that the file was removed.
func (r *RemoveRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// A SymlinkRequest is a request to create a symlink making NewName point to Target.
type SymlinkRequest struct {
Header `json:"-"`
NewName, Target string
}
var _ = Request(&SymlinkRequest{})
func (r *SymlinkRequest) String() string {
return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target)
}
// Respond replies to the request, indicating that the symlink was created.
func (r *SymlinkRequest) Respond(resp *SymlinkResponse) {
size := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*entryOut)(buf.alloc(size))
out.Nodeid = uint64(resp.Node)
out.Generation = resp.Generation
out.EntryValid = uint64(resp.EntryValid / time.Second)
out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A SymlinkResponse is the response to a SymlinkRequest.
type SymlinkResponse struct {
LookupResponse
}
func (r *SymlinkResponse) String() string {
return fmt.Sprintf("Symlink %v", r.LookupResponse.string())
}
// A ReadlinkRequest is a request to read a symlink's target.
type ReadlinkRequest struct {
Header `json:"-"`
}
var _ = Request(&ReadlinkRequest{})
func (r *ReadlinkRequest) String() string {
return fmt.Sprintf("Readlink [%s]", &r.Header)
}
func (r *ReadlinkRequest) Respond(target string) {
buf := newBuffer(uintptr(len(target)))
buf = append(buf, target...)
r.respond(buf)
}
// A LinkRequest is a request to create a hard link.
type LinkRequest struct {
Header `json:"-"`
OldNode NodeID
NewName string
}
var _ = Request(&LinkRequest{})
func (r *LinkRequest) String() string {
return fmt.Sprintf("Link [%s] node %d to %q", &r.Header, r.OldNode, r.NewName)
}
func (r *LinkRequest) Respond(resp *LookupResponse) {
size := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*entryOut)(buf.alloc(size))
out.Nodeid = uint64(resp.Node)
out.Generation = resp.Generation
out.EntryValid = uint64(resp.EntryValid / time.Second)
out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
// A RenameRequest is a request to rename a file.
type RenameRequest struct {
Header `json:"-"`
NewDir NodeID
OldName, NewName string
}
var _ = Request(&RenameRequest{})
func (r *RenameRequest) String() string {
return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName)
}
func (r *RenameRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
type MknodRequest struct {
Header `json:"-"`
Name string
Mode os.FileMode
Rdev uint32
// Umask of the request. Not supported on OS X.
Umask os.FileMode
}
var _ = Request(&MknodRequest{})
func (r *MknodRequest) String() string {
return fmt.Sprintf("Mknod [%s] Name %q mode=%v umask=%v rdev=%d", &r.Header, r.Name, r.Mode, r.Umask, r.Rdev)
}
func (r *MknodRequest) Respond(resp *LookupResponse) {
size := entryOutSize(r.Header.Conn.proto)
buf := newBuffer(size)
out := (*entryOut)(buf.alloc(size))
out.Nodeid = uint64(resp.Node)
out.Generation = resp.Generation
out.EntryValid = uint64(resp.EntryValid / time.Second)
out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond)
out.AttrValid = uint64(resp.Attr.Valid / time.Second)
out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond)
resp.Attr.attr(&out.Attr, r.Header.Conn.proto)
r.respond(buf)
}
type FsyncRequest struct {
Header `json:"-"`
Handle HandleID
// TODO bit 1 is datasync, not well documented upstream
Flags uint32
Dir bool
}
var _ = Request(&FsyncRequest{})
func (r *FsyncRequest) String() string {
return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags)
}
func (r *FsyncRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}
// An InterruptRequest is a request to interrupt another pending request. The
// response to that request should return an error status of EINTR.
type InterruptRequest struct {
Header `json:"-"`
IntrID RequestID // ID of the request to be interrupt.
}
var _ = Request(&InterruptRequest{})
func (r *InterruptRequest) Respond() {
// nothing to do here
r.noResponse()
}
func (r *InterruptRequest) String() string {
return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID)
}
// An ExchangeDataRequest is a request to exchange the contents of two
// files, while leaving most metadata untouched.
//
// This request comes from OS X exchangedata(2) and represents its
// specific semantics. Crucially, it is very different from Linux
// renameat(2) RENAME_EXCHANGE.
//
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
type ExchangeDataRequest struct {
Header `json:"-"`
OldDir, NewDir NodeID
OldName, NewName string
// TODO options
}
var _ = Request(&ExchangeDataRequest{})
func (r *ExchangeDataRequest) String() string {
// TODO options
return fmt.Sprintf("ExchangeData [%s] %v %q and %v %q", &r.Header, r.OldDir, r.OldName, r.NewDir, r.NewName)
}
func (r *ExchangeDataRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}