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