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 @@
// system issuing file operations in parallel, and using the race
// detector to weed out data races.
//
// # Deadlocks
//
// The Go runtime multiplexes Goroutines onto operating system
// threads, and makes assumptions that some system calls do not
// block. When accessing a file system from the same process that
// serves the file system (e.g. in unittests), this can lead to
// deadlocks, especially when GOMAXPROCS=1, when the Go runtime
// assumes a system call does not block, but actually is served by the
// Go-FUSE process.
//
// The following deadlocks are known:
//
// 1. Spawning a subprocess uses a fork/exec sequence: the process
// forks itself into a parent and child. The parent waits for the
// child to signal that the exec failed or succeeded, while the child
// prepares for calling exec(). Any setup step in the child that
// triggers a FUSE request can cause a deadlock.
//
// 1a. If the subprocess has a directory specified, the child will
// chdir into that directory. This generates an ACCESS operation on
// the directory.
//
// This deadlock can be avoided by disabling the ACCESS
// operation: return syscall.ENOSYS in the Access implementation, and
// ensure it is triggered called before initiating the subprocess.
//
// 1b. If the subprocess inherits files, the child process uses dup3()
// to remap file descriptors. If the destination fd happens to be
// backed by Go-FUSE, the dup3() call will implicitly close the fd,
// generating a FLUSH operation, eg.
//
// f1, err := os.Open("/fusemnt/file1")
// // f1.Fd() == 3
// f2, err := os.Open("/fusemnt/file1")
// // f2.Fd() == 4
//
// cmd := exec.Command("/bin/true")
// cmd.ExtraFiles = []*os.File{f2}
// // f2 (fd 4) is moved to fd 3. Deadlocks with GOMAXPROCS=1.
// cmd.Start()
//
// This deadlock can be avoided by ensuring that file descriptors
// pointing into FUSE mounts and file descriptors passed into
// subprocesses do not overlap, e.g. inserting the following before
// the above example:
//
// for {
// f, _ := os.Open("/dev/null")
// defer f.Close()
// if f.Fd() > 3 {
// break
// }
// }
//
// 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
//
// File system data usually cannot fit all in RAM, so the kernel must
......@@ -233,7 +294,7 @@ type NodeGetattrer interface {
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 {
Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
}
......@@ -409,14 +470,14 @@ type NodeLookuper interface {
}
// 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
// checks. The default is to return success.
type NodeOpendirer interface {
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
// for Readdir to return different results from Lookup. For example,
......@@ -435,25 +496,25 @@ type NodeReaddirer interface {
}
// 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 {
Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}
// Mknod is similar to Lookup, but must create a device entry and Inode.
// Default is to return EROFS.
// Default is to return ENOTSUP.
type NodeMknoder interface {
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}
// Link is similar to Lookup, but must create a new link to an existing Inode.
// Default is to return EROFS.
// Default is to return ENOTSUP.
type NodeLinker interface {
Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}
// Symlink is similar to Lookup, but must create a new symbolic link.
// Default is to return EROFS.
// Default is to return ENOTSUP.
type NodeSymlinker interface {
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}
......@@ -468,20 +529,20 @@ type NodeCreater interface {
// Unlink should remove a child from this directory. If the
// return status is OK, the Inode is removed as child in the
// FS tree automatically. Default is to return EROFS.
// FS tree automatically. Default is to return success.
type NodeUnlinker interface {
Unlink(ctx context.Context, name string) syscall.Errno
}
// Rmdir is like Unlink but for directories.
// Default is to return EROFS.
// Default is to return success.
type NodeRmdirer interface {
Rmdir(ctx context.Context, name string) syscall.Errno
}
// Rename should move a child from one directory to a different
// one. The change is effected in the FS tree if the return status is
// OK. Default is to return EROFS.
// OK. Default is to return ENOTSUP.
type NodeRenamer interface {
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
errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name)
}
// TODO - this should not succeed silently.
if errno == 0 {
parent.RmChild(name)
}
......@@ -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)
}
// TODO - this should not succeed silently.
if errno == 0 {
parent.RmChild(name)
}
......@@ -1171,18 +1175,22 @@ func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.Ls
out.Offset = off
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.Offset >= attr.Size {
return errnoToStatus(syscall.ENXIO)
}
out.Offset = in.Offset
return fuse.OK
}
if in.Whence == _SEEK_HOLE {
var attr fuse.AttrOut
if s := b.getattr(ctx, n, nil, &attr); s != 0 {
return errnoToStatus(s)
if in.Offset > attr.Size {
return errnoToStatus(syscall.ENXIO)
}
out.Offset = attr.Size
return fuse.OK
}
......
......@@ -11,6 +11,7 @@ import (
"syscall"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/renameat"
)
// 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
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))
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
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 {
// TODO: Handle `mode` parameter.
......
......@@ -9,7 +9,6 @@ package fs
import (
"context"
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
......@@ -43,41 +42,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc
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))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
......
......@@ -8,7 +8,6 @@ import (
"bytes"
"io/ioutil"
"os"
"reflect"
"sync"
"syscall"
"testing"
......@@ -16,82 +15,9 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"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)
}
}
func TestRenameNoOverwrite(t *testing.T) {
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 @@
// [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts
package fuse
import "log"
// Types for users to implement.
// The result of Read is an array of bytes, but for performance
......@@ -219,6 +221,9 @@ type MountOptions struct {
// If set, print debugging information.
Debug bool
// If set, sink for debug statements.
Logger *log.Logger
// If set, ask kernel to forward file locks to FUSE. If using,
// you must implement the GetLk/SetLk/SetLkw methods.
EnableLocks bool
......
......@@ -10,10 +10,8 @@ import (
"fmt"
"log"
"os"
"reflect"
"syscall"
"time"
"unsafe"
)
func (code Status) String() string {
......@@ -65,15 +63,6 @@ func ToStatus(err error) Status {
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 {
return &Owner{
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 (
"os/exec"
"strings"
"syscall"
"unsafe"
)
func unixgramSocketpair() (l, r *os.File, err error) {
......@@ -90,33 +89,6 @@ func unmount(dir string, opts *MountOptions) error {
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) {
binPaths := []string{
"/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
......
......@@ -7,13 +7,11 @@ package fuse
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path"
"strings"
"syscall"
"unsafe"
)
func unixgramSocketpair() (l, r *os.File, err error) {
......@@ -69,7 +67,7 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
}
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, ","))
}
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) {
cmd = append(cmd, "-o", strings.Join(s, ","))
}
if opts.Debug {
log.Printf("callFusermount: executing %q", cmd)
opts.Logger.Printf("callFusermount: executing %q", cmd)
}
proc, err := os.StartProcess(bin,
cmd,
......@@ -145,7 +143,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
if err == nil {
return fd, nil
} 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 {
return -1, err
......@@ -157,7 +155,7 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
fd = parseFuseFd(mountPoint)
if fd >= 0 {
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 {
// Usual case: mount via the `fusermount` suid helper
......@@ -194,7 +192,7 @@ func unmount(mountPoint string, opts *MountOptions) (err error) {
cmd := exec.Command(bin, "-u", mountPoint)
cmd.Stderr = &errBuf
if opts.Debug {
log.Printf("unmount: executing %q", cmd.Args)
opts.Logger.Printf("unmount: executing %q", cmd.Args)
}
err = cmd.Run()
if errBuf.Len() > 0 {
......@@ -204,33 +202,6 @@ func unmount(mountPoint string, opts *MountOptions) (err error) {
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,
// in fallbackDir. This is useful if PATH is possible empty.
func lookPathFallback(file string, fallbackDir string) (string, error) {
......
......@@ -62,6 +62,12 @@ const (
_OP_LSEEK = uint32(46) // protocol version 24
_OP_COPY_FILE_RANGE = uint32(47) // protocol version 28.
_OP_SETUPMAPPING = 48
_OP_REMOVEMAPPING = 49
_OP_SYNCFS = 50
_OP_TMPFILE = 51
_OP_STATX = 52
// The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_INVAL_ENTRY = uint32(100)
_OP_NOTIFY_INVAL_INODE = uint32(101)
......@@ -96,7 +102,7 @@ func doInit(server *Server, req *request) {
server.reqMu.Lock()
server.kernelSettings = *input
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 {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
......@@ -229,7 +235,7 @@ func doNotifyReply(server *Server, req *request) {
server.retrieveMu.Unlock()
badf := func(format string, argv ...interface{}) {
log.Printf("notify reply: "+format, argv...)
server.opts.Logger.Printf("notify reply: "+format, argv...)
}
if reading == nil {
......@@ -328,7 +334,7 @@ func doBatchForget(server *Server, req *request) {
wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{})
if uintptr(len(req.arg)) < wantBytes {
// 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)
}
......@@ -341,7 +347,7 @@ func doBatchForget(server *Server, req *request) {
forgets := *(*[]_ForgetOne)(unsafe.Pointer(h))
for i, f := range forgets {
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)
}
if f.NodeId == pollHackInode {
......@@ -437,6 +443,10 @@ func doSymlink(server *Server, req *request) {
}
func doRename(server *Server, req *request) {
if server.kernelSettings.supportsRenameSwap() {
doRename2(server, req)
return
}
in1 := (*Rename1In)(req.inData)
in := RenameIn{
InHeader: in1.InHeader,
......@@ -692,6 +702,10 @@ func init() {
_OP_RENAME2: "RENAME2",
_OP_LSEEK: "LSEEK",
_OP_COPY_FILE_RANGE: "COPY_FILE_RANGE",
_OP_SETUPMAPPING: "SETUPMAPPING",
_OP_REMOVEMAPPING: "REMOVEMAPPING",
_OP_SYNCFS: "SYNCFS",
_OP_TMPFILE: "TMPFILE",
} {
operationHandlers[op].Name = v
}
......
......@@ -44,8 +44,11 @@ var (
CAP_ABORT_ERROR: "ABORT_ERROR",
CAP_MAX_PAGES: "MAX_PAGES",
CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS",
CAP_NO_OPENDIR_SUPPORT: "NO_OPENDIR_SUPPORT",
CAP_EXPLICIT_INVAL_DATA: "EXPLICIT_INVAL_DATA",
CAP_SECURITY_CTX: "SECURITY_CTX",
CAP_HAS_INODE_DAX: "HAS_INODE_DAX",
CAP_CREATE_SUPP_GROUP: "CREATE_SUPP_GROUP",
CAP_HAS_EXPIRE_ONLY: "HAS_EXPIRE_ONLY",
CAP_DIRECT_IO_RELAX: "DIRECT_IO_RELAX",
})
releaseFlagNames = newFlagNames(map[int64]string{
RELEASE_FLUSH: "FLUSH",
......@@ -61,7 +64,7 @@ var (
int64(syscall.O_NONBLOCK): "NONBLOCK",
int64(os.O_SYNC): "SYNC",
int64(os.O_TRUNC): "TRUNC",
0x8000: "LARGEFILE",
int64(syscall.O_CLOEXEC): "CLOEXEC",
int64(syscall.O_DIRECTORY): "DIRECTORY",
})
......@@ -209,7 +212,7 @@ func (in *OpenOut) string() string {
func (in *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s}",
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 {
......
......@@ -9,6 +9,11 @@ import (
)
func init() {
initFlagNames.set(CAP_NODE_RWLOCK, "NODE_RWLOCK")
initFlagNames.set(CAP_RENAME_SWAP, "RENAME_SWAP")
initFlagNames.set(CAP_RENAME_EXCL, "RENAME_EXCL")
initFlagNames.set(CAP_ALLOCATE, "ALLOCATE")
initFlagNames.set(CAP_EXCHANGE_DATA, "EXCHANGE_DATA")
initFlagNames.set(CAP_XTIMES, "XTIMES")
initFlagNames.set(CAP_VOL_RENAME, "VOL_RENAME")
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE")
......
......@@ -13,6 +13,14 @@ func init() {
openFlagNames.set(syscall.O_DIRECT, "DIRECT")
openFlagNames.set(syscall.O_LARGEFILE, "LARGEFILE")
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 {
......
......@@ -172,7 +172,7 @@ func (r *request) parseHeader() Status {
return OK
}
func (r *request) parse() {
func (r *request) parse(kernelSettings InitIn) {
r.arg = r.inputBuf[:]
r.handler = getHandler(r.inHeader.Opcode)
if r.handler == nil {
......@@ -181,7 +181,15 @@ func (r *request) parse() {
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)
r.status = EIO
return
......@@ -189,7 +197,7 @@ func (r *request) parse() {
if r.handler.InputSize > 0 {
r.inData = unsafe.Pointer(&r.arg[0])
r.arg = r.arg[r.handler.InputSize:]
r.arg = r.arg[inSz:]
} else {
r.arg = r.arg[unsafe.Sizeof(InHeader{}):]
}
......
......@@ -9,5 +9,5 @@ const outputHeaderSize = 200
const (
_FUSE_KERNEL_VERSION = 7
_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
}
}
o := *opts
if o.Logger == nil {
o.Logger = log.Default()
}
if o.MaxWrite < 0 {
o.MaxWrite = 0
}
......@@ -323,8 +325,10 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
ms.reqReaders++
ms.reqMu.Unlock()
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
reqIface := ms.reqPool.Get()
req = reqIface.(*request)
destIface := ms.readPool.Get()
dest := destIface.([]byte)
var n int
err := handleEINTR(func() error {
......@@ -334,7 +338,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
})
if err != nil {
code = ToStatus(err)
ms.reqPool.Put(req)
ms.reqPool.Put(reqIface)
ms.reqMu.Lock()
ms.reqReaders--
ms.reqMu.Unlock()
......@@ -355,8 +359,7 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
req.inflightIndex = len(ms.reqInflight)
ms.reqInflight = append(ms.reqInflight, req)
if !gobbled {
ms.readPool.Put(dest)
dest = nil
ms.readPool.Put(destIface)
}
ms.reqReaders--
if !ms.singleReader && ms.reqReaders <= 0 {
......@@ -483,11 +486,11 @@ exit:
case ENODEV:
// unmount
if ms.opts.Debug {
log.Printf("received ENODEV (unmount request), thread exiting")
ms.opts.Logger.Printf("received ENODEV (unmount request), thread exiting")
}
break exit
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
}
......@@ -505,20 +508,20 @@ func (ms *Server) handleRequest(req *request) Status {
defer ms.requestProcessingMu.Unlock()
}
req.parse()
req.parse(ms.kernelSettings)
if req.handler == nil {
req.status = ENOSYS
}
if req.status.Ok() && ms.opts.Debug {
log.Println(req.InputDebug())
ms.opts.Logger.Println(req.InputDebug())
}
if req.inHeader.NodeId == pollHackInode ||
req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName {
doPollHackLookup(ms, req)
} 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
} else if req.status.Ok() {
req.handler.Func(ms, req)
......@@ -531,7 +534,7 @@ func (ms *Server) handleRequest(req *request) Status {
// kernel. This is a normal if the referred request already has
// completed.
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))
}
......@@ -577,7 +580,7 @@ func (ms *Server) write(req *request) Status {
header := req.serializeHeader(req.flatDataSize())
if ms.opts.Debug {
log.Println(req.OutputDebug())
ms.opts.Logger.Println(req.OutputDebug())
}
if header == nil {
......@@ -614,7 +617,7 @@ func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status {
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Println("Response: INODE_NOTIFY", result)
ms.opts.Logger.Println("Response: INODE_NOTIFY", result)
}
return result
}
......@@ -673,7 +676,7 @@ func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte
ms.writeMu.Unlock()
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
}
......@@ -765,7 +768,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n
ms.writeMu.Unlock()
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 {
ms.retrieveMu.Lock()
......@@ -779,7 +782,7 @@ func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n
// unexpected NotifyReply with our notifyUnique, then
// retrieveNext wraps, makes full cycle, and another
// 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()
return 0, result
......@@ -840,7 +843,7 @@ func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Printf("Response: DELETE_NOTIFY: %v", result)
ms.opts.Logger.Printf("Response: DELETE_NOTIFY: %v", result)
}
return result
}
......@@ -876,7 +879,7 @@ func (ms *Server) EntryNotify(parent uint64, name string) Status {
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Printf("Response: ENTRY_NOTIFY: %v", result)
ms.opts.Logger.Printf("Response: ENTRY_NOTIFY: %v", result)
}
return result
}
......@@ -903,6 +906,12 @@ func (in *InitIn) SupportsNotify(notifyType int) bool {
return false
}
// supportsRenameSwap returns whether the kernel supports the
// renamex_np(2) syscall. This is only supported on OS X.
func (in *InitIn) supportsRenameSwap() bool {
return in.Flags&CAP_RENAME_SWAP != 0
}
// WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty or not yet mounted)
// mountpoint, and the OS trying to setup the user-space mount.
......
......@@ -5,7 +5,6 @@
package fuse
import (
"log"
"syscall"
)
......@@ -25,7 +24,7 @@ func (ms *Server) systemWrite(req *request, header []byte) Status {
req.readResult.Done()
return OK
}
log.Println("trySplice:", err)
ms.opts.Logger.Println("trySplice:", err)
}
sz := req.flatDataSize()
......
......@@ -32,6 +32,17 @@ func TestFlockExclusive(t *testing.T) {
contents := []byte{1, 2, 3}
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)
if err != nil {
t.Fatalf("OpenFile(%q): %v", tc.mountFile, err)
......
......@@ -137,6 +137,7 @@ const ( // SetAttrIn.Valid
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
FATTR_CTIME = (1 << 10)
FATTR_KILL_SUIDGID = (1 << 11)
)
type SetAttrInCommon struct {
......@@ -256,6 +257,8 @@ const (
FOPEN_NONSEEKABLE = (1 << 2)
FOPEN_CACHE_DIR = (1 << 3)
FOPEN_STREAM = (1 << 4)
FOPEN_NOFLUSH = (1 << 5)
FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6)
)
type OpenOut struct {
......@@ -297,8 +300,13 @@ const (
CAP_ABORT_ERROR = (1 << 21)
CAP_MAX_PAGES = (1 << 22)
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 {
......@@ -308,6 +316,8 @@ type InitIn struct {
Minor uint32
MaxReadAhead uint32
Flags uint32
Flags2 uint32
Unused [11]uint32
}
type InitOut struct {
......@@ -321,7 +331,8 @@ type InitOut struct {
TimeGran uint32
MaxPages uint16
Padding uint16
Unused [8]uint32
Flags2 uint32
Unused [7]uint32
}
type _CuseInitIn struct {
......@@ -647,6 +658,7 @@ const (
const (
WRITE_CACHE = (1 << 0)
WRITE_LOCKOWNER = (1 << 1)
WRITE_KILL_SUIDGID = (1 << 2)
)
type FallocateIn struct {
......
......@@ -145,9 +145,17 @@ type GetXAttrIn struct {
}
const (
CAP_NODE_RWLOCK = (1 << 24)
CAP_RENAME_SWAP = (1 << 25)
CAP_RENAME_EXCL = (1 << 26)
CAP_ALLOCATE = (1 << 27)
CAP_EXCHANGE_DATA = (1 << 28)
CAP_CASE_INSENSITIVE = (1 << 29)
CAP_VOL_RENAME = (1 << 30)
CAP_XTIMES = (1 << 31)
// CAP_EXPLICIT_INVAL_DATA is not supported on Darwin.
CAP_EXPLICIT_INVAL_DATA = 0x0
)
type GetxtimesOut struct {
......
......@@ -15,6 +15,24 @@ const (
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 {
Ino 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
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
......@@ -34,6 +35,7 @@ var All = map[string]func(*testing.T, string){
"Link": Link,
"LinkUnlinkRename": LinkUnlinkRename,
"LseekHoleSeeksToEOF": LseekHoleSeeksToEOF,
"LseekEnxioCheck": LseekEnxioCheck,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist,
"RenameOpenDir": RenameOpenDir,
......@@ -720,3 +722,48 @@ func LseekHoleSeeksToEOF(t *testing.T, mnt string) {
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) {
const _SPLICE_F_NONBLOCK = 0x2
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 {
// all good.
} else if err != nil {
......
......@@ -11,6 +11,7 @@ import (
"io/ioutil"
"log"
"os"
"sync"
"syscall"
)
......@@ -30,8 +31,10 @@ func MaxPipeSize() int {
// Since Linux 2.6.11, the pipe capacity is 65536 bytes.
const DefaultPipeSize = 16 * 4096
// We empty pipes by splicing to /dev/null.
var devNullFD uintptr
var (
devNullFDOnce sync.Once
devNullFDValue int
)
func init() {
content, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size")
......@@ -51,13 +54,18 @@ func init() {
resizable = resizable && (errNo == 0)
r.Close()
w.Close()
}
// We empty pipes by splicing to /dev/null.
func devNullFD() int {
devNullFDOnce.Do(func() {
fd, err := syscall.Open("/dev/null", os.O_WRONLY, 0)
if err != nil {
log.Panicf("splice: %v", err)
panic(fmt.Sprintf("failed to open /dev/null: %s", err))
}
devNullFD = uintptr(fd)
devNullFDValue = fd
})
return devNullFDValue
}
// 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