Commit 9f9ad4a1 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into y/nodefs-cancel

Notable patches:

- 22d9c9dc "fuse: retain interface types"

  this removes one allocation from hot path.

- 7b4f97c3 "fuse: add Options.Logger"

  this would allow to customize FUSE-related logging, if we would need.

- 043296a8 "fuse: sync opcodes and capabilities"

  update from recent kernel.

* master:
  fuse: parse socket control message using the standard library
  fs: support renameExchange in loopback for darwin
  fs: describe one more deadlock scenario
  fuse: extract macos and linux conflicted opcodes to platform specific builds
  fuse: move conflict opcodes into linux specific build
  fs: document known deadlocks
  fuse/test: avoid low-numbered fds for FUSE-backed files
  fuse: retain interface types
  fs: bridge: Add missing Lseek whence checks
  posixtest: run tests against normal filesystem
  fuse: document 0x8000 open flag
  fs: correct documentation on return errno codes
  fuse: sync opcodes and capabilities
  fuse: drop unused function
  fs: fix typo in comment
  Lazily init /dev/null fd
  fuse: add Options.Logger
parents aebdd447 cbb13ba8
...@@ -139,6 +139,67 @@ ...@@ -139,6 +139,67 @@
// system issuing file operations in parallel, and using the race // system issuing file operations in parallel, and using the race
// detector to weed out data races. // detector to weed out data races.
// //
// # Deadlocks
//
// The Go runtime multiplexes Goroutines onto operating system
// threads, and makes assumptions that some system calls do not
// block. When accessing a file system from the same process that
// serves the file system (e.g. in unittests), this can lead to
// deadlocks, especially when GOMAXPROCS=1, when the Go runtime
// assumes a system call does not block, but actually is served by the
// Go-FUSE process.
//
// The following deadlocks are known:
//
// 1. Spawning a subprocess uses a fork/exec sequence: the process
// forks itself into a parent and child. The parent waits for the
// child to signal that the exec failed or succeeded, while the child
// prepares for calling exec(). Any setup step in the child that
// triggers a FUSE request can cause a deadlock.
//
// 1a. If the subprocess has a directory specified, the child will
// chdir into that directory. This generates an ACCESS operation on
// the directory.
//
// This deadlock can be avoided by disabling the ACCESS
// operation: return syscall.ENOSYS in the Access implementation, and
// ensure it is triggered called before initiating the subprocess.
//
// 1b. If the subprocess inherits files, the child process uses dup3()
// to remap file descriptors. If the destination fd happens to be
// backed by Go-FUSE, the dup3() call will implicitly close the fd,
// generating a FLUSH operation, eg.
//
// f1, err := os.Open("/fusemnt/file1")
// // f1.Fd() == 3
// f2, err := os.Open("/fusemnt/file1")
// // f2.Fd() == 4
//
// cmd := exec.Command("/bin/true")
// cmd.ExtraFiles = []*os.File{f2}
// // f2 (fd 4) is moved to fd 3. Deadlocks with GOMAXPROCS=1.
// cmd.Start()
//
// This deadlock can be avoided by ensuring that file descriptors
// pointing into FUSE mounts and file descriptors passed into
// subprocesses do not overlap, e.g. inserting the following before
// the above example:
//
// for {
// f, _ := os.Open("/dev/null")
// defer f.Close()
// if f.Fd() > 3 {
// break
// }
// }
//
// 2. The Go runtime uses the epoll system call to understand which
// goroutines can respond to I/O. The runtime assumes that epoll does
// not block, but if files are on a FUSE filesystem, the kernel will
// generate a POLL operation. To prevent this from happening, Go-FUSE
// disables the POLL opcode on mount. To ensure this has happened, call
// WaitMount.
//
// # Dynamically discovered file systems // # Dynamically discovered file systems
// //
// File system data usually cannot fit all in RAM, so the kernel must // File system data usually cannot fit all in RAM, so the kernel must
...@@ -233,7 +294,7 @@ type NodeGetattrer interface { ...@@ -233,7 +294,7 @@ type NodeGetattrer interface {
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
} }
// SetAttr sets attributes for an Inode. // SetAttr sets attributes for an Inode. Default is to return ENOTSUP.
type NodeSetattrer interface { type NodeSetattrer interface {
Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
} }
...@@ -409,14 +470,14 @@ type NodeLookuper interface { ...@@ -409,14 +470,14 @@ type NodeLookuper interface {
} }
// OpenDir opens a directory Inode for reading its // OpenDir opens a directory Inode for reading its
// contents. The actual reading is driven from ReadDir, so // contents. The actual reading is driven from Readdir, so
// this method is just for performing sanity/permission // this method is just for performing sanity/permission
// checks. The default is to return success. // checks. The default is to return success.
type NodeOpendirer interface { type NodeOpendirer interface {
Opendir(ctx context.Context) syscall.Errno Opendir(ctx context.Context) syscall.Errno
} }
// ReadDir opens a stream of directory entries. // Readdir opens a stream of directory entries.
// //
// Readdir essentiallly returns a list of strings, and it is allowed // Readdir essentiallly returns a list of strings, and it is allowed
// for Readdir to return different results from Lookup. For example, // for Readdir to return different results from Lookup. For example,
...@@ -435,25 +496,25 @@ type NodeReaddirer interface { ...@@ -435,25 +496,25 @@ type NodeReaddirer interface {
} }
// Mkdir is similar to Lookup, but must create a directory entry and Inode. // Mkdir is similar to Lookup, but must create a directory entry and Inode.
// Default is to return EROFS. // Default is to return ENOTSUP.
type NodeMkdirer interface { type NodeMkdirer interface {
Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
} }
// Mknod is similar to Lookup, but must create a device entry and Inode. // Mknod is similar to Lookup, but must create a device entry and Inode.
// Default is to return EROFS. // Default is to return ENOTSUP.
type NodeMknoder interface { type NodeMknoder interface {
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
} }
// Link is similar to Lookup, but must create a new link to an existing Inode. // Link is similar to Lookup, but must create a new link to an existing Inode.
// Default is to return EROFS. // Default is to return ENOTSUP.
type NodeLinker interface { type NodeLinker interface {
Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
} }
// Symlink is similar to Lookup, but must create a new symbolic link. // Symlink is similar to Lookup, but must create a new symbolic link.
// Default is to return EROFS. // Default is to return ENOTSUP.
type NodeSymlinker interface { type NodeSymlinker interface {
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
} }
...@@ -468,20 +529,20 @@ type NodeCreater interface { ...@@ -468,20 +529,20 @@ type NodeCreater interface {
// Unlink should remove a child from this directory. If the // Unlink should remove a child from this directory. If the
// return status is OK, the Inode is removed as child in the // return status is OK, the Inode is removed as child in the
// FS tree automatically. Default is to return EROFS. // FS tree automatically. Default is to return success.
type NodeUnlinker interface { type NodeUnlinker interface {
Unlink(ctx context.Context, name string) syscall.Errno Unlink(ctx context.Context, name string) syscall.Errno
} }
// Rmdir is like Unlink but for directories. // Rmdir is like Unlink but for directories.
// Default is to return EROFS. // Default is to return success.
type NodeRmdirer interface { type NodeRmdirer interface {
Rmdir(ctx context.Context, name string) syscall.Errno Rmdir(ctx context.Context, name string) syscall.Errno
} }
// Rename should move a child from one directory to a different // Rename should move a child from one directory to a different
// one. The change is effected in the FS tree if the return status is // one. The change is effected in the FS tree if the return status is
// OK. Default is to return EROFS. // OK. Default is to return ENOTSUP.
type NodeRenamer interface { type NodeRenamer interface {
Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno
} }
......
...@@ -378,6 +378,8 @@ func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name st ...@@ -378,6 +378,8 @@ func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name st
errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
} }
// TODO - this should not succeed silently.
if errno == 0 { if errno == 0 {
parent.RmChild(name) parent.RmChild(name)
} }
...@@ -391,6 +393,8 @@ func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name s ...@@ -391,6 +393,8 @@ func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name s
errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
} }
// TODO - this should not succeed silently.
if errno == 0 { if errno == 0 {
parent.RmChild(name) parent.RmChild(name)
} }
...@@ -1171,18 +1175,22 @@ func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.Ls ...@@ -1171,18 +1175,22 @@ func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.Ls
out.Offset = off out.Offset = off
return errnoToStatus(errno) return errnoToStatus(errno)
} }
var attr fuse.AttrOut
if s := b.getattr(ctx, n, nil, &attr); s != 0 {
return errnoToStatus(s)
}
if in.Whence == _SEEK_DATA { if in.Whence == _SEEK_DATA {
if in.Offset >= attr.Size {
return errnoToStatus(syscall.ENXIO)
}
out.Offset = in.Offset out.Offset = in.Offset
return fuse.OK return fuse.OK
} }
if in.Whence == _SEEK_HOLE { if in.Whence == _SEEK_HOLE {
var attr fuse.AttrOut if in.Offset > attr.Size {
if s := b.getattr(ctx, n, nil, &attr); s != 0 { return errnoToStatus(syscall.ENXIO)
return errnoToStatus(s)
} }
out.Offset = attr.Size out.Offset = attr.Size
return fuse.OK return fuse.OK
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"syscall" "syscall"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/renameat"
) )
// LoopbackRoot holds the parameters for creating a new loopback // LoopbackRoot holds the parameters for creating a new loopback
...@@ -218,6 +219,41 @@ func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mo ...@@ -218,6 +219,41 @@ func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mo
return ch, lf, 0, 0 return ch, lf, 0, 0
} }
func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newName string) syscall.Errno {
fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0)
if err != nil {
return ToErrno(err)
}
defer syscall.Close(fd1)
p2 := filepath.Join(n.RootData.Path, newparent.EmbeddedInode().Path(nil))
fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0)
defer syscall.Close(fd2)
if err != nil {
return ToErrno(err)
}
var st syscall.Stat_t
if err := syscall.Fstat(fd1, &st); err != nil {
return ToErrno(err)
}
// Double check that nodes didn't change from under us.
inode := &n.Inode
if inode.Root() != inode && inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
return syscall.EBUSY
}
if err := syscall.Fstat(fd2, &st); err != nil {
return ToErrno(err)
}
newinode := newparent.EmbeddedInode()
if newinode.Root() != newinode && newinode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
return syscall.EBUSY
}
return ToErrno(renameat.Renameat(fd1, name, fd2, newName, renameat.RENAME_EXCHANGE))
}
var _ = (NodeSymlinker)((*LoopbackNode)(nil)) var _ = (NodeSymlinker)((*LoopbackNode)(nil))
func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
......
...@@ -41,10 +41,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc ...@@ -41,10 +41,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
} }
func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newName string) syscall.Errno {
return syscall.ENOSYS
}
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
// TODO: Handle `mode` parameter. // TODO: Handle `mode` parameter.
......
...@@ -9,7 +9,6 @@ package fs ...@@ -9,7 +9,6 @@ package fs
import ( import (
"context" "context"
"path/filepath"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
...@@ -43,41 +42,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc ...@@ -43,41 +42,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc
return uint32(sz), ToErrno(err) return uint32(sz), ToErrno(err)
} }
func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newName string) syscall.Errno {
fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0)
if err != nil {
return ToErrno(err)
}
defer syscall.Close(fd1)
p2 := filepath.Join(n.RootData.Path, newparent.EmbeddedInode().Path(nil))
fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0)
defer syscall.Close(fd2)
if err != nil {
return ToErrno(err)
}
var st syscall.Stat_t
if err := syscall.Fstat(fd1, &st); err != nil {
return ToErrno(err)
}
// Double check that nodes didn't change from under us.
inode := &n.Inode
if inode.Root() != inode && inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
return syscall.EBUSY
}
if err := syscall.Fstat(fd2, &st); err != nil {
return ToErrno(err)
}
newinode := newparent.EmbeddedInode()
if newinode.Root() != newinode && newinode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino {
return syscall.EBUSY
}
return ToErrno(unix.Renameat2(fd1, name, fd2, newName, unix.RENAME_EXCHANGE))
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil)) var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
......
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"sync" "sync"
"syscall" "syscall"
"testing" "testing"
...@@ -16,82 +15,9 @@ import ( ...@@ -16,82 +15,9 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func TestRenameExchange(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
tc.writeOrig("file", "hello", 0644)
tc.writeOrig("dir/file", "x", 0644)
f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 1: %v", err)
}
defer syscall.Close(f1)
f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 2: %v", err)
}
defer syscall.Close(f2)
var before1, before2 unix.Stat_t
if err := unix.Fstatat(f1, "file", &before1, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Fstatat(f2, "file", &before2, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil {
t.Errorf("rename EXCHANGE: %v", err)
}
var after1, after2 unix.Stat_t
if err := unix.Fstatat(f1, "file", &after1, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Fstatat(f2, "file", &after2, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
clearCtime := func(s *unix.Stat_t) {
s.Ctim.Sec = 0
s.Ctim.Nsec = 0
}
clearCtime(&after1)
clearCtime(&after2)
clearCtime(&before2)
clearCtime(&before1)
if diff := pretty.Compare(after1, before2); diff != "" {
t.Errorf("after1, before2: %s", diff)
}
if !reflect.DeepEqual(after2, before1) {
t.Errorf("after2, before1: %#v, %#v", after2, before1)
}
root := tc.loopback.EmbeddedInode().Root()
ino1 := root.GetChild("file")
if ino1 == nil {
t.Fatalf("root.GetChild(%q): null inode", "file")
}
ino2 := root.GetChild("dir").GetChild("file")
if ino2 == nil {
t.Fatalf("dir.GetChild(%q): null inode", "file")
}
if ino1.StableAttr().Ino != after1.Ino {
t.Errorf("got inode %d for %q, want %d", ino1.StableAttr().Ino, "file", after1.Ino)
}
if ino2.StableAttr().Ino != after2.Ino {
t.Errorf("got inode %d for %q want %d", ino2.StableAttr().Ino, "dir/file", after2.Ino)
}
}
func TestRenameNoOverwrite(t *testing.T) { func TestRenameNoOverwrite(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
......
package fs
import (
"os"
"reflect"
"syscall"
"testing"
"github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix"
)
func TestRenameExchange(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
tc.writeOrig("file", "hello", 0644)
tc.writeOrig("dir/file", "x", 0644)
f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 1: %v", err)
}
defer syscall.Close(f1)
f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 2: %v", err)
}
defer syscall.Close(f2)
var before1, before2 unix.Stat_t
if err := unix.Fstatat(f1, "file", &before1, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Fstatat(f2, "file", &before2, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil {
t.Errorf("rename EXCHANGE: %v", err)
}
var after1, after2 unix.Stat_t
if err := unix.Fstatat(f1, "file", &after1, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Fstatat(f2, "file", &after2, 0); err != nil {
t.Fatalf("Fstatat: %v", err)
}
clearCtime := func(s *unix.Stat_t) {
s.Ctim.Sec = 0
s.Ctim.Nsec = 0
}
clearCtime(&after1)
clearCtime(&after2)
clearCtime(&before2)
clearCtime(&before1)
if diff := pretty.Compare(after1, before2); diff != "" {
t.Errorf("after1, before2: %s", diff)
}
if !reflect.DeepEqual(after2, before1) {
t.Errorf("after2, before1: %#v, %#v", after2, before1)
}
root := tc.loopback.EmbeddedInode().Root()
ino1 := root.GetChild("file")
if ino1 == nil {
t.Fatalf("root.GetChild(%q): null inode", "file")
}
ino2 := root.GetChild("dir").GetChild("file")
if ino2 == nil {
t.Fatalf("dir.GetChild(%q): null inode", "file")
}
if ino1.StableAttr().Ino != after1.Ino {
t.Errorf("got inode %d for %q, want %d", ino1.StableAttr().Ino, "file", after1.Ino)
}
if ino2.StableAttr().Ino != after2.Ino {
t.Errorf("got inode %d for %q want %d", ino2.StableAttr().Ino, "dir/file", after2.Ino)
}
}
...@@ -122,6 +122,8 @@ ...@@ -122,6 +122,8 @@
// [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts // [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts
package fuse package fuse
import "log"
// Types for users to implement. // Types for users to implement.
// The result of Read is an array of bytes, but for performance // The result of Read is an array of bytes, but for performance
...@@ -219,6 +221,9 @@ type MountOptions struct { ...@@ -219,6 +221,9 @@ type MountOptions struct {
// If set, print debugging information. // If set, print debugging information.
Debug bool Debug bool
// If set, sink for debug statements.
Logger *log.Logger
// If set, ask kernel to forward file locks to FUSE. If using, // If set, ask kernel to forward file locks to FUSE. If using,
// you must implement the GetLk/SetLk/SetLkw methods. // you must implement the GetLk/SetLk/SetLkw methods.
EnableLocks bool EnableLocks bool
......
...@@ -10,10 +10,8 @@ import ( ...@@ -10,10 +10,8 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"reflect"
"syscall" "syscall"
"time" "time"
"unsafe"
) )
func (code Status) String() string { func (code Status) String() string {
...@@ -65,15 +63,6 @@ func ToStatus(err error) Status { ...@@ -65,15 +63,6 @@ func ToStatus(err error) Status {
return ENOSYS return ENOSYS
} }
func toSlice(dest *[]byte, ptr unsafe.Pointer, byteCount uintptr) {
h := (*reflect.SliceHeader)(unsafe.Pointer(dest))
*h = reflect.SliceHeader{
Data: uintptr(ptr),
Len: int(byteCount),
Cap: int(byteCount),
}
}
func CurrentOwner() *Owner { func CurrentOwner() *Owner {
return &Owner{ return &Owner{
Uid: uint32(os.Getuid()), Uid: uint32(os.Getuid()),
......
package fuse
import (
"fmt"
"net"
"os"
"syscall"
)
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
}
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"syscall" "syscall"
"unsafe"
) )
func unixgramSocketpair() (l, r *os.File, err error) { func unixgramSocketpair() (l, r *os.File, err error) {
...@@ -90,33 +89,6 @@ func unmount(dir string, opts *MountOptions) error { ...@@ -90,33 +89,6 @@ func unmount(dir string, opts *MountOptions) error {
return syscall.Unmount(dir, 0) return syscall.Unmount(dir, 0)
} }
func getConnection(local *os.File) (int, error) {
var data [4]byte
control := make([]byte, 4*256)
// n, oobn, recvflags, from, errno - todo: error checking.
_, oobn, _, _,
err := syscall.Recvmsg(
int(local.Fd()), data[:], control[:], 0)
if err != nil {
return 0, err
}
message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0]))
fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr))
if message.Type != syscall.SCM_RIGHTS {
return 0, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type)
}
if oobn <= syscall.SizeofCmsghdr {
return 0, fmt.Errorf("getConnection: too short control message. Length: %d", oobn)
}
if fd < 0 {
return 0, fmt.Errorf("getConnection: fd < 0: %d", fd)
}
return int(fd), nil
}
func fusermountBinary() (string, error) { func fusermountBinary() (string, error) {
binPaths := []string{ binPaths := []string{
"/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse", "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
......
...@@ -7,13 +7,11 @@ package fuse ...@@ -7,13 +7,11 @@ package fuse
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"strings" "strings"
"syscall" "syscall"
"unsafe"
) )
func unixgramSocketpair() (l, r *os.File, err error) { func unixgramSocketpair() (l, r *os.File, err error) {
...@@ -69,7 +67,7 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd ...@@ -69,7 +67,7 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
} }
if opts.Debug { if opts.Debug {
log.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)", opts.Logger.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)",
source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ",")) source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
} }
err = syscall.Mount(source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ",")) err = syscall.Mount(source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
...@@ -108,7 +106,7 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { ...@@ -108,7 +106,7 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) {
cmd = append(cmd, "-o", strings.Join(s, ",")) cmd = append(cmd, "-o", strings.Join(s, ","))
} }
if opts.Debug { if opts.Debug {
log.Printf("callFusermount: executing %q", cmd) opts.Logger.Printf("callFusermount: executing %q", cmd)
} }
proc, err := os.StartProcess(bin, proc, err := os.StartProcess(bin,
cmd, cmd,
...@@ -145,7 +143,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -145,7 +143,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
if err == nil { if err == nil {
return fd, nil return fd, nil
} else if opts.Debug { } else if opts.Debug {
log.Printf("mount: failed to do direct mount: %s", err) opts.Logger.Printf("mount: failed to do direct mount: %s", err)
} }
if opts.DirectMountStrict { if opts.DirectMountStrict {
return -1, err return -1, err
...@@ -157,7 +155,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -157,7 +155,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
fd = parseFuseFd(mountPoint) fd = parseFuseFd(mountPoint)
if fd >= 0 { if fd >= 0 {
if opts.Debug { if opts.Debug {
log.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd) opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
} }
} else { } else {
// Usual case: mount via the `fusermount` suid helper // Usual case: mount via the `fusermount` suid helper
...@@ -194,7 +192,7 @@ func unmount(mountPoint string, opts *MountOptions) (err error) { ...@@ -194,7 +192,7 @@ func unmount(mountPoint string, opts *MountOptions) (err error) {
cmd := exec.Command(bin, "-u", mountPoint) cmd := exec.Command(bin, "-u", mountPoint)
cmd.Stderr = &errBuf cmd.Stderr = &errBuf
if opts.Debug { if opts.Debug {
log.Printf("unmount: executing %q", cmd.Args) opts.Logger.Printf("unmount: executing %q", cmd.Args)
} }
err = cmd.Run() err = cmd.Run()
if errBuf.Len() > 0 { if errBuf.Len() > 0 {
...@@ -204,33 +202,6 @@ func unmount(mountPoint string, opts *MountOptions) (err error) { ...@@ -204,33 +202,6 @@ func unmount(mountPoint string, opts *MountOptions) (err error) {
return err return err
} }
func getConnection(local *os.File) (int, error) {
var data [4]byte
control := make([]byte, 4*256)
// n, oobn, recvflags, from, errno - todo: error checking.
_, oobn, _, _,
err := syscall.Recvmsg(
int(local.Fd()), data[:], control[:], 0)
if err != nil {
return 0, err
}
message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0]))
fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr))
if message.Type != 1 {
return 0, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type)
}
if oobn <= syscall.SizeofCmsghdr {
return 0, fmt.Errorf("getConnection: too short control message. Length: %d", oobn)
}
if fd < 0 {
return 0, fmt.Errorf("getConnection: fd < 0: %d", fd)
}
return int(fd), nil
}
// lookPathFallback - search binary in PATH and, if that fails, // lookPathFallback - search binary in PATH and, if that fails,
// in fallbackDir. This is useful if PATH is possible empty. // in fallbackDir. This is useful if PATH is possible empty.
func lookPathFallback(file string, fallbackDir string) (string, error) { func lookPathFallback(file string, fallbackDir string) (string, error) {
......
...@@ -62,6 +62,12 @@ const ( ...@@ -62,6 +62,12 @@ const (
_OP_LSEEK = uint32(46) // protocol version 24 _OP_LSEEK = uint32(46) // protocol version 24
_OP_COPY_FILE_RANGE = uint32(47) // protocol version 28. _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. // The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_INVAL_ENTRY = uint32(100) _OP_NOTIFY_INVAL_ENTRY = uint32(100)
_OP_NOTIFY_INVAL_INODE = uint32(101) _OP_NOTIFY_INVAL_INODE = uint32(101)
...@@ -96,7 +102,7 @@ func doInit(server *Server, req *request) { ...@@ -96,7 +102,7 @@ func doInit(server *Server, req *request) {
server.reqMu.Lock() server.reqMu.Lock()
server.kernelSettings = *input server.kernelSettings = *input
server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS | server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES) CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES | CAP_RENAME_SWAP)
if server.opts.EnableLocks { if server.opts.EnableLocks {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
...@@ -229,7 +235,7 @@ func doNotifyReply(server *Server, req *request) { ...@@ -229,7 +235,7 @@ func doNotifyReply(server *Server, req *request) {
server.retrieveMu.Unlock() server.retrieveMu.Unlock()
badf := func(format string, argv ...interface{}) { badf := func(format string, argv ...interface{}) {
log.Printf("notify reply: "+format, argv...) server.opts.Logger.Printf("notify reply: "+format, argv...)
} }
if reading == nil { if reading == nil {
...@@ -328,7 +334,7 @@ func doBatchForget(server *Server, req *request) { ...@@ -328,7 +334,7 @@ func doBatchForget(server *Server, req *request) {
wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{}) wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{})
if uintptr(len(req.arg)) < wantBytes { if uintptr(len(req.arg)) < wantBytes {
// We have no return value to complain, so log an error. // We have no return value to complain, so log an error.
log.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)", server.opts.Logger.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)",
len(req.arg), wantBytes, in.Count) len(req.arg), wantBytes, in.Count)
} }
...@@ -341,7 +347,7 @@ func doBatchForget(server *Server, req *request) { ...@@ -341,7 +347,7 @@ func doBatchForget(server *Server, req *request) {
forgets := *(*[]_ForgetOne)(unsafe.Pointer(h)) forgets := *(*[]_ForgetOne)(unsafe.Pointer(h))
for i, f := range forgets { for i, f := range forgets {
if server.opts.Debug { if server.opts.Debug {
log.Printf("doBatchForget: rx %d %d/%d: FORGET n%d {Nlookup=%d}", 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) req.inHeader.Unique, i+1, len(forgets), f.NodeId, f.Nlookup)
} }
if f.NodeId == pollHackInode { if f.NodeId == pollHackInode {
...@@ -437,6 +443,10 @@ func doSymlink(server *Server, req *request) { ...@@ -437,6 +443,10 @@ func doSymlink(server *Server, req *request) {
} }
func doRename(server *Server, req *request) { func doRename(server *Server, req *request) {
if server.kernelSettings.supportsRenameSwap() {
doRename2(server, req)
return
}
in1 := (*Rename1In)(req.inData) in1 := (*Rename1In)(req.inData)
in := RenameIn{ in := RenameIn{
InHeader: in1.InHeader, InHeader: in1.InHeader,
...@@ -692,6 +702,10 @@ func init() { ...@@ -692,6 +702,10 @@ func init() {
_OP_RENAME2: "RENAME2", _OP_RENAME2: "RENAME2",
_OP_LSEEK: "LSEEK", _OP_LSEEK: "LSEEK",
_OP_COPY_FILE_RANGE: "COPY_FILE_RANGE", _OP_COPY_FILE_RANGE: "COPY_FILE_RANGE",
_OP_SETUPMAPPING: "SETUPMAPPING",
_OP_REMOVEMAPPING: "REMOVEMAPPING",
_OP_SYNCFS: "SYNCFS",
_OP_TMPFILE: "TMPFILE",
} { } {
operationHandlers[op].Name = v operationHandlers[op].Name = v
} }
......
...@@ -20,48 +20,51 @@ var ( ...@@ -20,48 +20,51 @@ var (
READ_LOCKOWNER: "LOCKOWNER", READ_LOCKOWNER: "LOCKOWNER",
}) })
initFlagNames = newFlagNames(map[int64]string{ initFlagNames = newFlagNames(map[int64]string{
CAP_ASYNC_READ: "ASYNC_READ", CAP_ASYNC_READ: "ASYNC_READ",
CAP_POSIX_LOCKS: "POSIX_LOCKS", CAP_POSIX_LOCKS: "POSIX_LOCKS",
CAP_FILE_OPS: "FILE_OPS", CAP_FILE_OPS: "FILE_OPS",
CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC", CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC",
CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT", CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT",
CAP_BIG_WRITES: "BIG_WRITES", CAP_BIG_WRITES: "BIG_WRITES",
CAP_DONT_MASK: "DONT_MASK", CAP_DONT_MASK: "DONT_MASK",
CAP_SPLICE_WRITE: "SPLICE_WRITE", CAP_SPLICE_WRITE: "SPLICE_WRITE",
CAP_SPLICE_MOVE: "SPLICE_MOVE", CAP_SPLICE_MOVE: "SPLICE_MOVE",
CAP_SPLICE_READ: "SPLICE_READ", CAP_SPLICE_READ: "SPLICE_READ",
CAP_FLOCK_LOCKS: "FLOCK_LOCKS", CAP_FLOCK_LOCKS: "FLOCK_LOCKS",
CAP_IOCTL_DIR: "IOCTL_DIR", CAP_IOCTL_DIR: "IOCTL_DIR",
CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA", CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA",
CAP_READDIRPLUS: "READDIRPLUS", CAP_READDIRPLUS: "READDIRPLUS",
CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO", CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO",
CAP_ASYNC_DIO: "ASYNC_DIO", CAP_ASYNC_DIO: "ASYNC_DIO",
CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE", CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE",
CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT", CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT",
CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS", CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS",
CAP_POSIX_ACL: "POSIX_ACL", CAP_POSIX_ACL: "POSIX_ACL",
CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV", CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV",
CAP_ABORT_ERROR: "ABORT_ERROR", CAP_ABORT_ERROR: "ABORT_ERROR",
CAP_MAX_PAGES: "MAX_PAGES", CAP_MAX_PAGES: "MAX_PAGES",
CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS", CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS",
CAP_NO_OPENDIR_SUPPORT: "NO_OPENDIR_SUPPORT", CAP_SECURITY_CTX: "SECURITY_CTX",
CAP_EXPLICIT_INVAL_DATA: "EXPLICIT_INVAL_DATA", CAP_HAS_INODE_DAX: "HAS_INODE_DAX",
CAP_CREATE_SUPP_GROUP: "CREATE_SUPP_GROUP",
CAP_HAS_EXPIRE_ONLY: "HAS_EXPIRE_ONLY",
CAP_DIRECT_IO_RELAX: "DIRECT_IO_RELAX",
}) })
releaseFlagNames = newFlagNames(map[int64]string{ releaseFlagNames = newFlagNames(map[int64]string{
RELEASE_FLUSH: "FLUSH", RELEASE_FLUSH: "FLUSH",
}) })
openFlagNames = newFlagNames(map[int64]string{ openFlagNames = newFlagNames(map[int64]string{
int64(os.O_WRONLY): "WRONLY", int64(os.O_WRONLY): "WRONLY",
int64(os.O_RDWR): "RDWR", int64(os.O_RDWR): "RDWR",
int64(os.O_APPEND): "APPEND", int64(os.O_APPEND): "APPEND",
int64(syscall.O_ASYNC): "ASYNC", int64(syscall.O_ASYNC): "ASYNC",
int64(os.O_CREATE): "CREAT", int64(os.O_CREATE): "CREAT",
int64(os.O_EXCL): "EXCL", int64(os.O_EXCL): "EXCL",
int64(syscall.O_NOCTTY): "NOCTTY", int64(syscall.O_NOCTTY): "NOCTTY",
int64(syscall.O_NONBLOCK): "NONBLOCK", int64(syscall.O_NONBLOCK): "NONBLOCK",
int64(os.O_SYNC): "SYNC", int64(os.O_SYNC): "SYNC",
int64(os.O_TRUNC): "TRUNC", int64(os.O_TRUNC): "TRUNC",
0x8000: "LARGEFILE",
int64(syscall.O_CLOEXEC): "CLOEXEC", int64(syscall.O_CLOEXEC): "CLOEXEC",
int64(syscall.O_DIRECTORY): "DIRECTORY", int64(syscall.O_DIRECTORY): "DIRECTORY",
}) })
...@@ -209,7 +212,7 @@ func (in *OpenOut) string() string { ...@@ -209,7 +212,7 @@ func (in *OpenOut) string() string {
func (in *InitIn) string() string { func (in *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s}", return fmt.Sprintf("{%d.%d Ra %d %s}",
in.Major, in.Minor, in.MaxReadAhead, in.Major, in.Minor, in.MaxReadAhead,
flagString(initFlagNames, int64(in.Flags), "")) flagString(initFlagNames, int64(in.Flags)|(int64(in.Flags2)<<32), ""))
} }
func (o *InitOut) string() string { func (o *InitOut) string() string {
......
...@@ -9,6 +9,11 @@ import ( ...@@ -9,6 +9,11 @@ import (
) )
func init() { 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_XTIMES, "XTIMES")
initFlagNames.set(CAP_VOL_RENAME, "VOL_RENAME") initFlagNames.set(CAP_VOL_RENAME, "VOL_RENAME")
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE") initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE")
......
...@@ -13,6 +13,14 @@ func init() { ...@@ -13,6 +13,14 @@ func init() {
openFlagNames.set(syscall.O_DIRECT, "DIRECT") openFlagNames.set(syscall.O_DIRECT, "DIRECT")
openFlagNames.set(syscall.O_LARGEFILE, "LARGEFILE") openFlagNames.set(syscall.O_LARGEFILE, "LARGEFILE")
openFlagNames.set(syscall_O_NOATIME, "NOATIME") openFlagNames.set(syscall_O_NOATIME, "NOATIME")
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 *Attr) string() string { func (a *Attr) string() string {
......
...@@ -172,7 +172,7 @@ func (r *request) parseHeader() Status { ...@@ -172,7 +172,7 @@ func (r *request) parseHeader() Status {
return OK return OK
} }
func (r *request) parse() { func (r *request) parse(kernelSettings InitIn) {
r.arg = r.inputBuf[:] r.arg = r.inputBuf[:]
r.handler = getHandler(r.inHeader.Opcode) r.handler = getHandler(r.inHeader.Opcode)
if r.handler == nil { if r.handler == nil {
...@@ -181,7 +181,15 @@ func (r *request) parse() { ...@@ -181,7 +181,15 @@ func (r *request) parse() {
return return
} }
if len(r.arg) < int(r.handler.InputSize) { inSz := int(r.handler.InputSize)
if r.inHeader.Opcode == _OP_RENAME && kernelSettings.supportsRenameSwap() {
inSz = int(unsafe.Sizeof(RenameIn{}))
}
if r.inHeader.Opcode == _OP_INIT && inSz > len(r.arg) {
// Minor version 36 extended the size of InitIn struct
inSz = len(r.arg)
}
if len(r.arg) < inSz {
log.Printf("Short read for %v: %v", operationName(r.inHeader.Opcode), r.arg) log.Printf("Short read for %v: %v", operationName(r.inHeader.Opcode), r.arg)
r.status = EIO r.status = EIO
return return
...@@ -189,7 +197,7 @@ func (r *request) parse() { ...@@ -189,7 +197,7 @@ func (r *request) parse() {
if r.handler.InputSize > 0 { if r.handler.InputSize > 0 {
r.inData = unsafe.Pointer(&r.arg[0]) r.inData = unsafe.Pointer(&r.arg[0])
r.arg = r.arg[r.handler.InputSize:] r.arg = r.arg[inSz:]
} else { } else {
r.arg = r.arg[unsafe.Sizeof(InHeader{}):] r.arg = r.arg[unsafe.Sizeof(InHeader{}):]
} }
......
...@@ -9,5 +9,5 @@ const outputHeaderSize = 200 ...@@ -9,5 +9,5 @@ const outputHeaderSize = 200
const ( const (
_FUSE_KERNEL_VERSION = 7 _FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12 _MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 12 _OUR_MINOR_VERSION = 19
) )
...@@ -165,7 +165,9 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -165,7 +165,9 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
} }
} }
o := *opts o := *opts
if o.Logger == nil {
o.Logger = log.Default()
}
if o.MaxWrite < 0 { if o.MaxWrite < 0 {
o.MaxWrite = 0 o.MaxWrite = 0
} }
...@@ -323,8 +325,10 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) { ...@@ -323,8 +325,10 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
ms.reqReaders++ ms.reqReaders++
ms.reqMu.Unlock() ms.reqMu.Unlock()
req = ms.reqPool.Get().(*request) reqIface := ms.reqPool.Get()
dest := ms.readPool.Get().([]byte) req = reqIface.(*request)
destIface := ms.readPool.Get()
dest := destIface.([]byte)
var n int var n int
err := handleEINTR(func() error { err := handleEINTR(func() error {
...@@ -334,7 +338,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) { ...@@ -334,7 +338,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
}) })
if err != nil { if err != nil {
code = ToStatus(err) code = ToStatus(err)
ms.reqPool.Put(req) ms.reqPool.Put(reqIface)
ms.reqMu.Lock() ms.reqMu.Lock()
ms.reqReaders-- ms.reqReaders--
ms.reqMu.Unlock() ms.reqMu.Unlock()
...@@ -355,8 +359,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) { ...@@ -355,8 +359,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
req.inflightIndex = len(ms.reqInflight) req.inflightIndex = len(ms.reqInflight)
ms.reqInflight = append(ms.reqInflight, req) ms.reqInflight = append(ms.reqInflight, req)
if !gobbled { if !gobbled {
ms.readPool.Put(dest) ms.readPool.Put(destIface)
dest = nil
} }
ms.reqReaders-- ms.reqReaders--
if !ms.singleReader && ms.reqReaders <= 0 { if !ms.singleReader && ms.reqReaders <= 0 {
...@@ -483,11 +486,11 @@ exit: ...@@ -483,11 +486,11 @@ exit:
case ENODEV: case ENODEV:
// unmount // unmount
if ms.opts.Debug { if ms.opts.Debug {
log.Printf("received ENODEV (unmount request), thread exiting") ms.opts.Logger.Printf("received ENODEV (unmount request), thread exiting")
} }
break exit break exit
default: // some other error? default: // some other error?
log.Printf("Failed to read from fuse conn: %v", errNo) ms.opts.Logger.Printf("Failed to read from fuse conn: %v", errNo)
break exit break exit
} }
...@@ -505,20 +508,20 @@ func (ms *Server) handleRequest(req *request) Status { ...@@ -505,20 +508,20 @@ func (ms *Server) handleRequest(req *request) Status {
defer ms.requestProcessingMu.Unlock() defer ms.requestProcessingMu.Unlock()
} }
req.parse() req.parse(ms.kernelSettings)
if req.handler == nil { if req.handler == nil {
req.status = ENOSYS req.status = ENOSYS
} }
if req.status.Ok() && ms.opts.Debug { if req.status.Ok() && ms.opts.Debug {
log.Println(req.InputDebug()) ms.opts.Logger.Println(req.InputDebug())
} }
if req.inHeader.NodeId == pollHackInode || if req.inHeader.NodeId == pollHackInode ||
req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName { req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName {
doPollHackLookup(ms, req) doPollHackLookup(ms, req)
} else if req.status.Ok() && req.handler.Func == nil { } else if req.status.Ok() && req.handler.Func == nil {
log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode)) ms.opts.Logger.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
req.status = ENOSYS req.status = ENOSYS
} else if req.status.Ok() { } else if req.status.Ok() {
req.handler.Func(ms, req) req.handler.Func(ms, req)
...@@ -531,7 +534,7 @@ func (ms *Server) handleRequest(req *request) Status { ...@@ -531,7 +534,7 @@ func (ms *Server) handleRequest(req *request) Status {
// kernel. This is a normal if the referred request already has // kernel. This is a normal if the referred request already has
// completed. // completed.
if ms.opts.Debug || !(req.inHeader.Opcode == _OP_INTERRUPT && errNo == ENOENT) { if ms.opts.Debug || !(req.inHeader.Opcode == _OP_INTERRUPT && errNo == ENOENT) {
log.Printf("writer: Write/Writev failed, err: %v. opcode: %v", ms.opts.Logger.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode)) errNo, operationName(req.inHeader.Opcode))
} }
...@@ -577,7 +580,7 @@ func (ms *Server) write(req *request) Status { ...@@ -577,7 +580,7 @@ func (ms *Server) write(req *request) Status {
header := req.serializeHeader(req.flatDataSize()) header := req.serializeHeader(req.flatDataSize())
if ms.opts.Debug { if ms.opts.Debug {
log.Println(req.OutputDebug()) ms.opts.Logger.Println(req.OutputDebug())
} }
if header == nil { if header == nil {
...@@ -614,7 +617,7 @@ func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status { ...@@ -614,7 +617,7 @@ func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status {
ms.writeMu.Unlock() ms.writeMu.Unlock()
if ms.opts.Debug { if ms.opts.Debug {
log.Println("Response: INODE_NOTIFY", result) ms.opts.Logger.Println("Response: INODE_NOTIFY", result)
} }
return result return result
} }
...@@ -673,7 +676,7 @@ func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte ...@@ -673,7 +676,7 @@ func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte
ms.writeMu.Unlock() ms.writeMu.Unlock()
if ms.opts.Debug { if ms.opts.Debug {
log.Printf("Response: INODE_NOTIFY_STORE_CACHE: %v", result) ms.opts.Logger.Printf("Response: INODE_NOTIFY_STORE_CACHE: %v", result)
} }
return result return result
} }
...@@ -765,7 +768,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n ...@@ -765,7 +768,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n
ms.writeMu.Unlock() ms.writeMu.Unlock()
if ms.opts.Debug { if ms.opts.Debug {
log.Printf("Response: NOTIFY_RETRIEVE_CACHE: %v", result) ms.opts.Logger.Printf("Response: NOTIFY_RETRIEVE_CACHE: %v", result)
} }
if result != OK { if result != OK {
ms.retrieveMu.Lock() ms.retrieveMu.Lock()
...@@ -779,7 +782,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n ...@@ -779,7 +782,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n
// unexpected NotifyReply with our notifyUnique, then // unexpected NotifyReply with our notifyUnique, then
// retrieveNext wraps, makes full cycle, and another // retrieveNext wraps, makes full cycle, and another
// retrieve request is made with the same notifyUnique. // retrieve request is made with the same notifyUnique.
log.Printf("W: INODE_RETRIEVE_CACHE: request with notifyUnique=%d mutated", q.NotifyUnique) ms.opts.Logger.Printf("W: INODE_RETRIEVE_CACHE: request with notifyUnique=%d mutated", q.NotifyUnique)
} }
ms.retrieveMu.Unlock() ms.retrieveMu.Unlock()
return 0, result return 0, result
...@@ -840,7 +843,7 @@ func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status ...@@ -840,7 +843,7 @@ func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status
ms.writeMu.Unlock() ms.writeMu.Unlock()
if ms.opts.Debug { if ms.opts.Debug {
log.Printf("Response: DELETE_NOTIFY: %v", result) ms.opts.Logger.Printf("Response: DELETE_NOTIFY: %v", result)
} }
return result return result
} }
...@@ -876,7 +879,7 @@ func (ms *Server) EntryNotify(parent uint64, name string) Status { ...@@ -876,7 +879,7 @@ func (ms *Server) EntryNotify(parent uint64, name string) Status {
ms.writeMu.Unlock() ms.writeMu.Unlock()
if ms.opts.Debug { if ms.opts.Debug {
log.Printf("Response: ENTRY_NOTIFY: %v", result) ms.opts.Logger.Printf("Response: ENTRY_NOTIFY: %v", result)
} }
return result return result
} }
...@@ -903,6 +906,12 @@ func (in *InitIn) SupportsNotify(notifyType int) bool { ...@@ -903,6 +906,12 @@ func (in *InitIn) SupportsNotify(notifyType int) bool {
return false 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 // WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty or not yet mounted) // avoid racing between accessing the (empty or not yet mounted)
// mountpoint, and the OS trying to setup the user-space mount. // mountpoint, and the OS trying to setup the user-space mount.
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package fuse package fuse
import ( import (
"log"
"syscall" "syscall"
) )
...@@ -25,7 +24,7 @@ func (ms *Server) systemWrite(req *request, header []byte) Status { ...@@ -25,7 +24,7 @@ func (ms *Server) systemWrite(req *request, header []byte) Status {
req.readResult.Done() req.readResult.Done()
return OK return OK
} }
log.Println("trySplice:", err) ms.opts.Logger.Println("trySplice:", err)
} }
sz := req.flatDataSize() sz := req.flatDataSize()
......
...@@ -32,6 +32,17 @@ func TestFlockExclusive(t *testing.T) { ...@@ -32,6 +32,17 @@ func TestFlockExclusive(t *testing.T) {
contents := []byte{1, 2, 3} contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700) tc.WriteFile(tc.origFile, []byte(contents), 0700)
for {
f, err := os.Open("/dev/null")
if err != nil {
t.Fatalf("Open(/dev/null): %v", err)
}
defer f.Close()
if f.Fd() > 3 {
break
}
}
f, err := os.OpenFile(tc.mountFile, os.O_WRONLY, 0) f, err := os.OpenFile(tc.mountFile, os.O_WRONLY, 0)
if err != nil { if err != nil {
t.Fatalf("OpenFile(%q): %v", tc.mountFile, err) t.Fatalf("OpenFile(%q): %v", tc.mountFile, err)
......
...@@ -126,17 +126,18 @@ type Owner struct { ...@@ -126,17 +126,18 @@ type Owner struct {
} }
const ( // SetAttrIn.Valid const ( // SetAttrIn.Valid
FATTR_MODE = (1 << 0) FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1) FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2) FATTR_GID = (1 << 2)
FATTR_SIZE = (1 << 3) FATTR_SIZE = (1 << 3)
FATTR_ATIME = (1 << 4) FATTR_ATIME = (1 << 4)
FATTR_MTIME = (1 << 5) FATTR_MTIME = (1 << 5)
FATTR_FH = (1 << 6) FATTR_FH = (1 << 6)
FATTR_ATIME_NOW = (1 << 7) FATTR_ATIME_NOW = (1 << 7)
FATTR_MTIME_NOW = (1 << 8) FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9) FATTR_LOCKOWNER = (1 << 9)
FATTR_CTIME = (1 << 10) FATTR_CTIME = (1 << 10)
FATTR_KILL_SUIDGID = (1 << 11)
) )
type SetAttrInCommon struct { type SetAttrInCommon struct {
...@@ -251,11 +252,13 @@ type OpenIn struct { ...@@ -251,11 +252,13 @@ type OpenIn struct {
const ( const (
// OpenOut.Flags // OpenOut.Flags
FOPEN_DIRECT_IO = (1 << 0) FOPEN_DIRECT_IO = (1 << 0)
FOPEN_KEEP_CACHE = (1 << 1) FOPEN_KEEP_CACHE = (1 << 1)
FOPEN_NONSEEKABLE = (1 << 2) FOPEN_NONSEEKABLE = (1 << 2)
FOPEN_CACHE_DIR = (1 << 3) FOPEN_CACHE_DIR = (1 << 3)
FOPEN_STREAM = (1 << 4) FOPEN_STREAM = (1 << 4)
FOPEN_NOFLUSH = (1 << 5)
FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6)
) )
type OpenOut struct { type OpenOut struct {
...@@ -273,32 +276,37 @@ type OpenOut struct { ...@@ -273,32 +276,37 @@ type OpenOut struct {
// * https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h // * https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// This file has CAP_HANDLE_KILLPRIV and CAP_POSIX_ACL reversed! // This file has CAP_HANDLE_KILLPRIV and CAP_POSIX_ACL reversed!
const ( const (
CAP_ASYNC_READ = (1 << 0) CAP_ASYNC_READ = (1 << 0)
CAP_POSIX_LOCKS = (1 << 1) CAP_POSIX_LOCKS = (1 << 1)
CAP_FILE_OPS = (1 << 2) CAP_FILE_OPS = (1 << 2)
CAP_ATOMIC_O_TRUNC = (1 << 3) CAP_ATOMIC_O_TRUNC = (1 << 3)
CAP_EXPORT_SUPPORT = (1 << 4) CAP_EXPORT_SUPPORT = (1 << 4)
CAP_BIG_WRITES = (1 << 5) CAP_BIG_WRITES = (1 << 5)
CAP_DONT_MASK = (1 << 6) CAP_DONT_MASK = (1 << 6)
CAP_SPLICE_WRITE = (1 << 7) CAP_SPLICE_WRITE = (1 << 7)
CAP_SPLICE_MOVE = (1 << 8) CAP_SPLICE_MOVE = (1 << 8)
CAP_SPLICE_READ = (1 << 9) CAP_SPLICE_READ = (1 << 9)
CAP_FLOCK_LOCKS = (1 << 10) CAP_FLOCK_LOCKS = (1 << 10)
CAP_IOCTL_DIR = (1 << 11) CAP_IOCTL_DIR = (1 << 11)
CAP_AUTO_INVAL_DATA = (1 << 12) CAP_AUTO_INVAL_DATA = (1 << 12)
CAP_READDIRPLUS = (1 << 13) CAP_READDIRPLUS = (1 << 13)
CAP_READDIRPLUS_AUTO = (1 << 14) CAP_READDIRPLUS_AUTO = (1 << 14)
CAP_ASYNC_DIO = (1 << 15) CAP_ASYNC_DIO = (1 << 15)
CAP_WRITEBACK_CACHE = (1 << 16) CAP_WRITEBACK_CACHE = (1 << 16)
CAP_NO_OPEN_SUPPORT = (1 << 17) CAP_NO_OPEN_SUPPORT = (1 << 17)
CAP_PARALLEL_DIROPS = (1 << 18) CAP_PARALLEL_DIROPS = (1 << 18)
CAP_HANDLE_KILLPRIV = (1 << 19) CAP_HANDLE_KILLPRIV = (1 << 19)
CAP_POSIX_ACL = (1 << 20) CAP_POSIX_ACL = (1 << 20)
CAP_ABORT_ERROR = (1 << 21) CAP_ABORT_ERROR = (1 << 21)
CAP_MAX_PAGES = (1 << 22) CAP_MAX_PAGES = (1 << 22)
CAP_CACHE_SYMLINKS = (1 << 23) CAP_CACHE_SYMLINKS = (1 << 23)
CAP_NO_OPENDIR_SUPPORT = (1 << 24)
CAP_EXPLICIT_INVAL_DATA = (1 << 25) /* 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_RELAX = (1 << 36)
) )
type InitIn struct { type InitIn struct {
...@@ -308,6 +316,8 @@ type InitIn struct { ...@@ -308,6 +316,8 @@ type InitIn struct {
Minor uint32 Minor uint32
MaxReadAhead uint32 MaxReadAhead uint32
Flags uint32 Flags uint32
Flags2 uint32
Unused [11]uint32
} }
type InitOut struct { type InitOut struct {
...@@ -321,7 +331,8 @@ type InitOut struct { ...@@ -321,7 +331,8 @@ type InitOut struct {
TimeGran uint32 TimeGran uint32
MaxPages uint16 MaxPages uint16
Padding uint16 Padding uint16
Unused [8]uint32 Flags2 uint32
Unused [7]uint32
} }
type _CuseInitIn struct { type _CuseInitIn struct {
...@@ -645,8 +656,9 @@ const ( ...@@ -645,8 +656,9 @@ const (
) )
const ( const (
WRITE_CACHE = (1 << 0) WRITE_CACHE = (1 << 0)
WRITE_LOCKOWNER = (1 << 1) WRITE_LOCKOWNER = (1 << 1)
WRITE_KILL_SUIDGID = (1 << 2)
) )
type FallocateIn struct { type FallocateIn struct {
......
...@@ -145,9 +145,17 @@ type GetXAttrIn struct { ...@@ -145,9 +145,17 @@ type GetXAttrIn struct {
} }
const ( 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_CASE_INSENSITIVE = (1 << 29)
CAP_VOL_RENAME = (1 << 30) CAP_VOL_RENAME = (1 << 30)
CAP_XTIMES = (1 << 31) CAP_XTIMES = (1 << 31)
// CAP_EXPLICIT_INVAL_DATA is not supported on Darwin.
CAP_EXPLICIT_INVAL_DATA = 0x0
) )
type GetxtimesOut struct { type GetxtimesOut struct {
......
...@@ -15,6 +15,24 @@ const ( ...@@ -15,6 +15,24 @@ const (
EREMOTEIO = Status(syscall.EREMOTEIO) 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
)
type Attr struct { type Attr struct {
Ino uint64 Ino uint64
Size uint64 Size uint64
......
package renameat
// Renameat is a wrapper around renameat syscall.
// On Linux, it is a wrapper around renameat2(2).
// On Darwin, it is a wrapper around renameatx_np(2).
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return renameat(olddirfd, oldpath, newdirfd, newpath, flags)
}
package renameat
import (
"syscall"
"unsafe"
)
const (
SYS_RENAMEATX_NP = 488
RENAME_SWAP = 0x2
RENAME_EXCHANGE = RENAME_SWAP
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
oldpathCString, err := syscall.BytePtrFromString(oldpath)
if err != nil {
return err
}
newpathCString, err := syscall.BytePtrFromString(newpath)
if err != nil {
return err
}
_, _, errno := syscall.Syscall6(
SYS_RENAMEATX_NP,
uintptr(olddirfd),
uintptr(unsafe.Pointer(oldpathCString)),
uintptr(newdirfd),
uintptr(unsafe.Pointer(newpathCString)),
uintptr(flags),
0,
)
if errno != 0 {
return errno
}
return nil
}
package renameat
import "golang.org/x/sys/unix"
const (
RENAME_EXCHANGE = unix.RENAME_EXCHANGE
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return unix.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}
// Copyright 2023 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package posixtest
import "testing"
func TestAll(t *testing.T) {
for k, fn := range All {
if k == "FcntlFlockLocksFile" {
// TODO - fix this test.
continue
}
t.Run(k, func(t *testing.T) {
dir := t.TempDir()
fn(t, dir)
})
}
}
...@@ -7,6 +7,7 @@ package posixtest ...@@ -7,6 +7,7 @@ package posixtest
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
...@@ -34,6 +35,7 @@ var All = map[string]func(*testing.T, string){ ...@@ -34,6 +35,7 @@ var All = map[string]func(*testing.T, string){
"Link": Link, "Link": Link,
"LinkUnlinkRename": LinkUnlinkRename, "LinkUnlinkRename": LinkUnlinkRename,
"LseekHoleSeeksToEOF": LseekHoleSeeksToEOF, "LseekHoleSeeksToEOF": LseekHoleSeeksToEOF,
"LseekEnxioCheck": LseekEnxioCheck,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist, "RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist, "RenameOverwriteDestExist": RenameOverwriteDestExist,
"RenameOpenDir": RenameOpenDir, "RenameOpenDir": RenameOpenDir,
...@@ -720,3 +722,48 @@ func LseekHoleSeeksToEOF(t *testing.T, mnt string) { ...@@ -720,3 +722,48 @@ func LseekHoleSeeksToEOF(t *testing.T, mnt string) {
t.Errorf("got offset %d, want %d", off, len(content)) t.Errorf("got offset %d, want %d", off, len(content))
} }
} }
func LseekEnxioCheck(t *testing.T, mnt string) {
fn := filepath.Join(mnt, "file.bin")
content := bytes.Repeat([]byte("abcxyz\n"), 1024)
if err := ioutil.WriteFile(fn, content, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
fd, err := syscall.Open(fn, syscall.O_RDONLY, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer syscall.Close(fd)
testCases := []struct {
name string
offset int64
whence int
}{
{
name: "Lseek SEEK_DATA where offset is at EOF returns ENXIO",
offset: int64(len(content)),
whence: unix.SEEK_DATA,
},
{
name: "Lseek SEEK_DATA where offset greater than EOF returns ENXIO",
offset: int64(len(content)) + 1,
whence: unix.SEEK_DATA,
},
{
name: "Lseek SEEK_HOLE where offset is greater than EOF returns ENXIO",
offset: int64(len(content)) + 1,
whence: unix.SEEK_HOLE,
},
}
for _, tc := range testCases {
_, err := unix.Seek(fd, tc.offset, tc.whence)
if err != nil {
if !errors.Is(err, syscall.ENXIO) {
t.Errorf("Failed test case: %s; got %v, want %v", tc.name, err, syscall.ENXIO)
}
}
}
}
...@@ -40,7 +40,7 @@ func (p *Pair) WriteTo(fd uintptr, n int) (int, error) { ...@@ -40,7 +40,7 @@ func (p *Pair) WriteTo(fd uintptr, n int) (int, error) {
const _SPLICE_F_NONBLOCK = 0x2 const _SPLICE_F_NONBLOCK = 0x2
func (p *Pair) discard() { func (p *Pair) discard() {
_, err := syscall.Splice(p.r, nil, int(devNullFD), nil, int(p.size), _SPLICE_F_NONBLOCK) _, err := syscall.Splice(p.r, nil, devNullFD(), nil, int(p.size), _SPLICE_F_NONBLOCK)
if err == syscall.EAGAIN { if err == syscall.EAGAIN {
// all good. // all good.
} else if err != nil { } else if err != nil {
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"sync"
"syscall" "syscall"
) )
...@@ -30,8 +31,10 @@ func MaxPipeSize() int { ...@@ -30,8 +31,10 @@ func MaxPipeSize() int {
// Since Linux 2.6.11, the pipe capacity is 65536 bytes. // Since Linux 2.6.11, the pipe capacity is 65536 bytes.
const DefaultPipeSize = 16 * 4096 const DefaultPipeSize = 16 * 4096
// We empty pipes by splicing to /dev/null. var (
var devNullFD uintptr devNullFDOnce sync.Once
devNullFDValue int
)
func init() { func init() {
content, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size") content, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size")
...@@ -51,13 +54,18 @@ func init() { ...@@ -51,13 +54,18 @@ func init() {
resizable = resizable && (errNo == 0) resizable = resizable && (errNo == 0)
r.Close() r.Close()
w.Close() w.Close()
}
fd, err := syscall.Open("/dev/null", os.O_WRONLY, 0) // We empty pipes by splicing to /dev/null.
if err != nil { func devNullFD() int {
log.Panicf("splice: %v", err) devNullFDOnce.Do(func() {
} fd, err := syscall.Open("/dev/null", os.O_WRONLY, 0)
if err != nil {
devNullFD = uintptr(fd) panic(fmt.Sprintf("failed to open /dev/null: %s", err))
}
devNullFDValue = fd
})
return devNullFDValue
} }
// copy & paste from syscall. // copy & paste from syscall.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment