Commit 9546fc23 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into y/nodefs-cancel

Some notable patches, nothing major:

- 36b35911 "fuse: fix deadlock with parallel mounts"

- f91e3081 "fuse: debug log: print PID"

- 334a5a5f "fuse: update for kernel API version 40"

- afb9051a "fuse: provide InitIn/InitOut.Flags64()"

There are other changes which add e.g. passthrough support and other
features, but they apply mostly to fs package, not nodefs, and I
reviewed them to be safe and orthogonal to what WCFS uses and needs.

* master: (53 commits)
  fs: skip passthrough test for older kernels
  fuse: provide InitIn/InitOut.Flags64()
  fs: reorganize code
  fs: support passthrough mode
  fuse: expose BackingFdMap
  fuse: fix printing of new constant
  fuse: remove deprecated CAP_DIRECT_IO_RELAX
  fuse: print tweaks for passthrough support
  fs: return fileEntry from registerFile and addNewChild
  benchmark: add a test reading from libfuse passthrough_hp
  benchmark: modernize setupFS
  benchmark: introduce benchmark for loopback reads
  benchmark: simplify ReadLines
  fs: fix stray comment
  fuse: wire up passthrough support
  fuse: point comment to fs API rather than nodefs/pathfs
  fuse: move Flags2 handling to platform specific code
  fuse: update for kernel API version 40
  fuse: fix CreateOut print
  fuse: document singleReader background
  ...
parents 9f9ad4a1 1a7d98b0
......@@ -11,7 +11,6 @@ jobs:
strategy:
matrix:
go:
- "1.16.x" # Golang upstream stable
- "1.17.x" # Golang upstream stable
- "1.18.x" # Golang upstream stable
- "1.19.x" # Golang upstream stable
......
......@@ -106,6 +106,7 @@ output. Here is how to read it:
- `tA` and `tE` means timeout for attributes and directory entry correspondingly;
- `[<off> +<size>)` means data range from `<off>` inclusive till `<off>+<size>` exclusive;
- `Xb` means `X bytes`.
- `pX` means the request originated from PID `x`. 0 means the request originated from the kernel.
Every line is prefixed with either `rx <unique>` or `tx <unique>` to denote
whether it was for kernel request, which Go-FUSE received, or reply, which
......@@ -114,24 +115,24 @@ Go-FUSE sent back to kernel.
Example debug log output:
```
rx 2: LOOKUP i1 [".wcfs"] 6b
rx 2: LOOKUP i1 [".wcfs"] 6b p5874
tx 2: OK, {i3 g2 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:3 A 0.000000 M 0.000000 C 0.000000}}
rx 3: LOOKUP i3 ["zurl"] 5b
rx 3: LOOKUP i3 ["zurl"] 5b p5874
tx 3: OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
rx 4: OPEN i4 {O_RDONLY,0x8000}
rx 4: OPEN i4 {O_RDONLY,0x8000} p5874
tx 4: 38=function not implemented, {Fh 0 }
rx 5: READ i4 {Fh 0 [0 +4096) L 0 RDONLY,0x8000}
rx 5: READ i4 {Fh 0 [0 +4096) L 0 RDONLY,0x8000} p5874
tx 5: OK, 33b data "file:///"...
rx 6: GETATTR i4 {Fh 0}
rx 6: GETATTR i4 {Fh 0} p5874
tx 6: OK, {tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}}
rx 7: FLUSH i4 {Fh 0}
rx 7: FLUSH i4 {Fh 0} p5874
tx 7: OK
rx 8: LOOKUP i1 ["head"] 5b
rx 8: LOOKUP i1 ["head"] 5b p5874
tx 8: OK, {i5 g4 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:5 A 0.000000 M 0.000000 C 0.000000}}
rx 9: LOOKUP i5 ["bigfile"] 8b
rx 9: LOOKUP i5 ["bigfile"] 8b p5874
tx 9: OK, {i6 g5 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:6 A 0.000000 M 0.000000 C 0.000000}}
rx 10: FLUSH i4 {Fh 0}
rx 10: FLUSH i4 {Fh 0} p5874
tx 10: OK
rx 11: GETATTR i1 {Fh 0}
rx 11: GETATTR i1 {Fh 0} p5874
tx 11: OK, {tA=1s {M040755 SZ=0 L=1 1000:1000 B0*0 i0:1 A 0.000000 M 0.000000 C 0.000000}}
```
......@@ -17,7 +17,7 @@ GO_TEST="go test -timeout 5m -p 1 -count 1"
# Run all tests as current user
$GO_TEST ./...
# Direct-mount tests need to run as root
sudo env PATH=$PATH $GO_TEST -run TestDirectMount ./fs ./fuse
sudo env PATH=$PATH $GO_TEST -run 'Test(DirectMount|Passthrough)' ./fs ./fuse
make -C benchmark
go test ./benchmark -test.bench '.*' -test.cpu 1,2
......@@ -7,32 +7,22 @@ package benchmark
// Routines for benchmarking fuse.
import (
"bufio"
"bytes"
"log"
"os"
)
func ReadLines(name string) []string {
f, err := os.Open(name)
data, err := os.ReadFile(name)
if err != nil {
log.Fatal("ReadLines: ", err)
log.Fatal("ReadFile: ", err)
}
defer f.Close()
r := bufio.NewReader(f)
l := []string{}
for {
line, _, err := r.ReadLine()
if line == nil || err != nil {
break
var lines []string
for _, l := range bytes.Split(data, []byte("\n")) {
if len(l) > 0 {
lines = append(lines, string(l))
}
fn := string(line)
l = append(l, fn)
}
if len(l) == 0 {
log.Fatal("no files added")
}
return l
return lines
}
......@@ -5,54 +5,130 @@
package benchmark
import (
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"testing"
"time"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"golang.org/x/sync/errgroup"
)
func BenchmarkGoFuseRead(b *testing.B) {
fs := &readFS{}
wd, clean := setupFs(fs, b.N)
defer clean()
jobs := 32
blockSize := 64 * 1024
func BenchmarkGoFuseMemoryRead(b *testing.B) {
root := &readFS{}
mnt := setupFS(root, b.N, b)
benchmarkRead(mnt, b, 32, "direct")
}
cmds := make([]*exec.Cmd, jobs)
const blockSize = 64 * 1024
for i := 0; i < jobs; i++ {
cmds[i] = exec.Command("dd",
fmt.Sprintf("if=%s/foo.txt", wd),
"iflag=direct",
func benchmarkRead(mnt string, b *testing.B, readers int, ddflag string) {
var cmds []*exec.Cmd
for i := 0; i < readers; i++ {
cmd := exec.Command("dd",
fmt.Sprintf("if=%s/foo.txt", mnt),
"of=/dev/null",
fmt.Sprintf("bs=%d", blockSize),
fmt.Sprintf("count=%d", b.N))
if ddflag != "" {
cmd.Args = append(cmd.Args, "iflag="+ddflag)
}
if testutil.VerboseTest() {
cmds[i].Stdout = os.Stdout
cmds[i].Stderr = os.Stderr
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
} else {
buf := &bytes.Buffer{}
cmd.Stderr = buf
cmd.Stdout = buf
}
cmds = append(cmds, cmd)
}
b.SetBytes(int64(jobs * blockSize))
b.SetBytes(int64(readers * blockSize))
b.ReportAllocs()
b.ResetTimer()
var eg errgroup.Group
result := make(chan error, readers)
for _, cmd := range cmds {
go func(cmd *exec.Cmd) {
err := cmd.Run()
if buf, ok := cmd.Stdout.(*bytes.Buffer); ok && err != nil {
err = fmt.Errorf("%v: output=%s", err, buf.String())
}
result <- err
}(cmd)
}
failures := 0
for range cmds {
if err := <-result; err != nil {
b.Errorf("dd failed: %v", err)
failures++
}
}
if failures > 0 {
b.Errorf("%d out of %d commands", failures, readers)
}
b.StopTimer()
}
func BenchmarkGoFuseFDRead(b *testing.B) {
orig := b.TempDir()
fn := orig + "/foo.txt"
for i := 0; i < jobs; i++ {
i := i
eg.Go(func() error {
return cmds[i].Run()
})
data := bytes.Repeat([]byte{42}, blockSize*b.N)
if err := os.WriteFile(fn, data, 0666); err != nil {
b.Fatal(err)
}
root, err := fs.NewLoopbackRoot(orig)
if err != nil {
b.Fatal(err)
}
mnt := setupFS(root, b.N, b)
benchmarkRead(mnt, b, 1, "")
}
if err := eg.Wait(); err != nil {
b.Fatalf("dd failed: %v", err)
var libfusePath = flag.String("passthrough_hp", "", "path to libfuse's passthrough_hp")
func BenchmarkLibfuseHP(b *testing.B) {
orig := b.TempDir()
mnt := b.TempDir()
if *libfusePath == "" {
b.Skip("must set --passthrough_hp")
}
b.StopTimer()
origFN := orig + "/foo.txt"
data := bytes.Repeat([]byte{42}, blockSize*b.N)
if err := os.WriteFile(origFN, data, 0666); err != nil {
b.Fatal(err)
}
fn := mnt + "/foo.txt"
cmd := exec.Command(*libfusePath, "--foreground")
if testutil.VerboseTest() {
cmd.Args = append(cmd.Args, "--debug", "--debug-fuse")
}
cmd.Args = append(cmd.Args, orig, mnt)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
b.Fatal(err)
}
b.Cleanup(func() { exec.Command("fusermount", "-u", mnt).Run() })
dt := time.Millisecond
for {
if _, err := os.Stat(fn); err == nil {
break
}
time.Sleep(dt)
dt *= 2
if dt > time.Second {
b.Fatal("file did not appear")
}
}
benchmarkRead(mnt, b, 1, "")
}
......@@ -22,22 +22,19 @@ import (
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
func setupFS(node fs.InodeEmbedder, N int, tb testing.TB) string {
opts := &fs.Options{}
opts.Debug = testutil.VerboseTest()
mountPoint, err := os.MkdirTemp("", "")
if err != nil {
log.Panicf("TempDir: %v", err)
}
mountPoint := tb.TempDir()
server, err := fs.Mount(mountPoint, node, opts)
if err != nil {
log.Panicf("cannot mount %v", err)
tb.Fatalf("cannot mount %v", err)
}
lmap := NewLatencyMap()
if testutil.VerboseTest() {
server.RecordLatencies(lmap)
}
return mountPoint, func() {
tb.Cleanup(func() {
if testutil.VerboseTest() {
var total time.Duration
for _, n := range []string{"LOOKUP", "GETATTR", "OPENDIR", "READDIR",
......@@ -57,7 +54,8 @@ func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
if err != nil {
log.Println("error during unmount", err)
}
}
})
return mountPoint
}
func TestNewStatFs(t *testing.T) {
......@@ -68,8 +66,7 @@ func TestNewStatFs(t *testing.T) {
fs.AddFile(n, fuse.Attr{Mode: syscall.S_IFREG})
}
wd, clean := setupFs(fs, 1)
defer clean()
wd := setupFS(fs, 1, t)
names, err := ioutil.ReadDir(wd)
if err != nil {
......@@ -121,15 +118,14 @@ func BenchmarkGoFuseStat(b *testing.B) {
fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG})
}
wd, clean := setupFs(fs, b.N)
defer clean()
mnt := setupFS(fs, b.N, b)
for i, l := range files {
files[i] = filepath.Join(wd, l)
files[i] = filepath.Join(mnt, l)
}
threads := runtime.GOMAXPROCS(0)
if err := TestingBOnePass(b, threads, fileList, wd); err != nil {
if err := TestingBOnePass(b, threads, fileList, mnt); err != nil {
b.Fatalf("TestingBOnePass %v8", err)
}
}
......@@ -157,12 +153,11 @@ func BenchmarkGoFuseReaddir(b *testing.B) {
dirSet[filepath.Dir(fn)] = struct{}{}
}
wd, clean := setupFs(fs, b.N)
defer clean()
mnt := setupFS(fs, b.N, b)
var dirs []string
for dir := range dirSet {
dirs = append(dirs, filepath.Join(wd, dir))
dirs = append(dirs, filepath.Join(mnt, dir))
}
b.StartTimer()
todo := b.N
......
......@@ -9,8 +9,8 @@
// To create a file system, you should first define types for the
// nodes of the file system tree.
//
// struct myNode {
// fs.Inode
// type myNode struct {
// fs.Inode
// }
//
// // Node types must be InodeEmbedders
......@@ -20,10 +20,10 @@
// var _ = (fs.NodeLookuper)((*myNode)(nil))
//
// func (n *myNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
// ops := myNode{}
// out.Mode = 0755
// out.Size = 42
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
// ops := myNode{}
// out.Mode = 0755
// out.Size = 42
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
// }
//
// The method names are inspired on the system call names, so we have
......@@ -568,6 +568,17 @@ type NodeRenamer interface {
type FileHandle interface {
}
// FilePassthroughFder is a file backed by a physical
// file. PassthroughFd should return an open file descriptor (and
// true), and the kernel will execute read/write operations directly
// on the backing file, bypassing the FUSE process. This function will
// be called once when processing the Create or Open operation, so
// there is no concern about concurrent access to the Fd. If the
// function returns false, passthrough will not be used for this file.
type FilePassthroughFder interface {
PassthroughFd() (int, bool)
}
// See NodeReleaser.
type FileReleaser interface {
Release(ctx context.Context) syscall.Errno
......
......@@ -26,6 +26,9 @@ type fileEntry struct {
// index into Inode.openFiles
nodeIndex int
// Handle number which we communicate to the kernel.
fh uint32
// Protects directory fields. Must be acquired before bridge.mu
mu sync.Mutex
......@@ -42,6 +45,10 @@ type fileEntry struct {
// directory seek has taken place.
dirOffset uint64
// We try to associate a file for stat() calls, but the kernel
// can issue a RELEASE and GETATTR in parallel. This waitgroup
// avoids that the RELEASE will invalidate the file descriptor
// before we finish processing GETATTR.
wg sync.WaitGroup
}
......@@ -56,6 +63,12 @@ type ServerCallbacks interface {
InodeNotifyStoreCache(node uint64, offset int64, data []byte) fuse.Status
}
// TODO: fold serverBackingFdCallbacks into ServerCallbacks and bump API version
type serverBackingFdCallbacks interface {
RegisterBackingFd(*fuse.BackingMap) (int32, syscall.Errno)
UnregisterBackingFd(id int32) syscall.Errno
}
type rawBridge struct {
options Options
root *Inode
......@@ -91,8 +104,13 @@ type rawBridge struct {
// estimate for stableAttrs.
nodeCountHigh int
files []*fileEntry
files []*fileEntry
// indices of files that are not allocated.
freeFiles []uint32
// If set, don't try to register backing file for Create/Open calls.
disableBackingFiles bool
}
// newInode creates creates new inode pointing to ops.
......@@ -154,7 +172,7 @@ func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAt
// Unless fileFlags has the syscall.O_EXCL bit set, child.stableAttr will be used
// to find an already-known node. If one is found, `child` is ignored and the
// already-known one is used. The node that was actually used is returned.
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) (selected *Inode, fh uint32) {
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) (selected *Inode, fe *fileEntry) {
if name == "." || name == ".." {
log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name)
}
......@@ -216,7 +234,7 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file F
// Any node that might be there is overwritten - it is obsolete now
b.stableAttrs[id] = child
if file != nil {
fh = b.registerFile(child, file, fileFlags)
fe = b.registerFile(child, file, fileFlags)
}
parent.setEntry(name, child)
......@@ -228,7 +246,7 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file F
b.mu.Unlock()
unlockNodes(parent, child)
return child, fh
return child, fe
}
func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
......@@ -404,13 +422,12 @@ func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name s
func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name string, out *fuse.EntryOut) fuse.Status {
parent, _ := b.inode(input.NodeId, 0)
var child *Inode
var errno syscall.Errno
if mops, ok := parent.ops.(NodeMkdirer); ok {
child, errno = mops.Mkdir(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, out)
} else {
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
mops, ok := parent.ops.(NodeMkdirer)
if !ok {
return fuse.ENOTSUP
}
child, errno := mops.Mkdir(ctx, name, input.Mode, out)
if errno != 0 {
return errnoToStatus(errno)
......@@ -433,14 +450,12 @@ func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name stri
func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name string, out *fuse.EntryOut) fuse.Status {
parent, _ := b.inode(input.NodeId, 0)
var child *Inode
var errno syscall.Errno
if mops, ok := parent.ops.(NodeMknoder); ok {
child, errno = mops.Mknod(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, input.Rdev, out)
} else {
mops, ok := parent.ops.(NodeMknoder)
if !ok {
return fuse.ENOTSUP
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
child, errno := mops.Mknod(ctx, name, input.Mode, input.Rdev, out)
if errno != 0 {
return errnoToStatus(errno)
}
......@@ -452,18 +467,14 @@ func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name stri
}
func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name string, out *fuse.CreateOut) fuse.Status {
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
parent, _ := b.inode(input.NodeId, 0)
var child *Inode
var errno syscall.Errno
var f FileHandle
var flags uint32
if mops, ok := parent.ops.(NodeCreater); ok {
child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut)
} else {
mops, ok := parent.ops.(NodeCreater)
if !ok {
return fuse.EROFS
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
child, f, flags, errno := mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut)
if errno != 0 {
if b.options.NegativeTimeout != nil {
......@@ -472,11 +483,11 @@ func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name st
return errnoToStatus(errno)
}
child, fh := b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT|syscall.O_EXCL, &out.EntryOut)
out.Fh = uint64(fh)
child, fe := b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT|syscall.O_EXCL, &out.EntryOut)
out.Fh = uint64(fe.fh)
out.OpenFlags = flags
b.addBackingID(child, f, &out.OpenOut)
child.setEntryOut(&out.EntryOut)
b.setEntryOutTimeout(&out.EntryOut)
return fuse.OK
......@@ -614,51 +625,56 @@ func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string
parent, _ := b.inode(input.NodeId, 0)
target, _ := b.inode(input.Oldnodeid, 0)
if mops, ok := parent.ops.(NodeLinker); ok {
child, errno := mops.Link(&fuse.Context{Caller: input.Caller, Cancel: cancel}, target.ops, name, out)
if errno != 0 {
return errnoToStatus(errno)
}
mops, ok := parent.ops.(NodeLinker)
if !ok {
return fuse.ENOTSUP
}
child, _ = b.addNewChild(parent, name, child, nil, 0, out)
child.setEntryOut(out)
b.setEntryOutTimeout(out)
return fuse.OK
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
child, errno := mops.Link(ctx, target.ops, name, out)
if errno != 0 {
return errnoToStatus(errno)
}
return fuse.ENOTSUP
child, _ = b.addNewChild(parent, name, child, nil, 0, out)
child.setEntryOut(out)
b.setEntryOutTimeout(out)
return fuse.OK
}
func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) fuse.Status {
parent, _ := b.inode(header.NodeId, 0)
if mops, ok := parent.ops.(NodeSymlinker); ok {
child, status := mops.Symlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, target, name, out)
if status != 0 {
return errnoToStatus(status)
}
child, _ = b.addNewChild(parent, name, child, nil, syscall.O_EXCL, out)
child.setEntryOut(out)
b.setEntryOutTimeout(out)
return fuse.OK
mops, ok := parent.ops.(NodeSymlinker)
if !ok {
return fuse.ENOTSUP
}
return fuse.ENOTSUP
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
child, status := mops.Symlink(ctx, target, name, out)
if status != 0 {
return errnoToStatus(status)
}
child, _ = b.addNewChild(parent, name, child, nil, syscall.O_EXCL, out)
child.setEntryOut(out)
b.setEntryOutTimeout(out)
return fuse.OK
}
func (b *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, status fuse.Status) {
n, _ := b.inode(header.NodeId, 0)
if linker, ok := n.ops.(NodeReadlinker); ok {
result, errno := linker.Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel})
if errno != 0 {
return nil, errnoToStatus(errno)
}
return result, fuse.OK
linker, ok := n.ops.(NodeReadlinker)
if !ok {
return nil, fuse.ENOTSUP
}
ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel}
result, errno := linker.Readlink(ctx)
if errno != 0 {
return nil, errnoToStatus(errno)
}
return nil, fuse.ENOTSUP
return result, fuse.OK
}
func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.Status {
......@@ -724,42 +740,105 @@ func (b *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, a
func (b *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status {
n, _ := b.inode(input.NodeId, 0)
if op, ok := n.ops.(NodeOpener); ok {
f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags)
op, ok := n.ops.(NodeOpener)
if !ok {
return fuse.ENOTSUP
}
f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags)
if errno != 0 {
return errnoToStatus(errno)
}
out.OpenFlags = flags
if f != nil {
b.mu.Lock()
defer b.mu.Unlock()
fe := b.registerFile(n, f, input.Flags)
out.Fh = uint64(fe.fh)
b.addBackingID(n, f, out)
}
return fuse.OK
}
// must hold bridge.mu
func (b *rawBridge) addBackingID(n *Inode, f FileHandle, out *fuse.OpenOut) {
if b.disableBackingFiles {
return
}
bc, ok := b.server.(serverBackingFdCallbacks)
if !ok {
b.disableBackingFiles = true
return
}
pth, ok := f.(FilePassthroughFder)
if !ok {
return
}
if n.backingID == 0 {
fd, ok := pth.PassthroughFd()
if !ok {
return
}
m := fuse.BackingMap{
Fd: int32(fd),
}
id, errno := bc.RegisterBackingFd(&m)
if errno != 0 {
return errnoToStatus(errno)
// This happens if we're not root or CAP_PASSTHROUGH is missing.
b.disableBackingFiles = true
} else {
n.backingID = id
}
}
if f != nil {
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = uint64(b.registerFile(n, f, input.Flags))
}
out.OpenFlags = flags
return fuse.OK
if n.backingID != 0 {
out.BackingID = n.backingID
out.OpenFlags |= fuse.FOPEN_PASSTHROUGH
out.OpenFlags &= ^uint32(fuse.FOPEN_KEEP_CACHE)
n.backingIDRefcount++
}
}
return fuse.ENOTSUP
// must hold bridge.mu
func (b *rawBridge) releaseBackingIDRef(n *Inode) {
if n.backingID == 0 {
return
}
n.backingIDRefcount--
if n.backingIDRefcount == 0 {
errno := b.server.(serverBackingFdCallbacks).UnregisterBackingFd(n.backingID)
if errno != 0 {
b.logf("UnregisterBackingFd: %v", errno)
}
n.backingID = 0
n.backingIDRefcount = 0
} else if n.backingIDRefcount < 0 {
log.Panic("backingIDRefcount underflow")
}
}
// registerFile hands out a file handle. Must have bridge.mu
func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) uint32 {
var fh uint32
func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) *fileEntry {
fe := &fileEntry{}
if len(b.freeFiles) > 0 {
last := len(b.freeFiles) - 1
fh = b.freeFiles[last]
fe.fh = b.freeFiles[last]
b.freeFiles = b.freeFiles[:last]
b.files[fe.fh] = fe
} else {
fh = uint32(len(b.files))
b.files = append(b.files, &fileEntry{})
fe.fh = uint32(len(b.files))
b.files = append(b.files, fe)
}
fileEntry := b.files[fh]
fileEntry.nodeIndex = len(n.openFiles)
fileEntry.file = f
fe.nodeIndex = len(n.openFiles)
fe.file = f
n.openFiles = append(n.openFiles, fe.fh)
n.openFiles = append(n.openFiles, fh)
return fh
return fe
}
func (b *rawBridge) Read(cancel <-chan struct{}, input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) {
......@@ -831,11 +910,13 @@ func (b *rawBridge) Release(cancel <-chan struct{}, input *fuse.ReleaseIn) {
b.mu.Lock()
defer b.mu.Unlock()
b.releaseBackingIDRef(n)
b.freeFiles = append(b.freeFiles, uint32(input.Fh))
}
func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
_, f := b.releaseFileEntry(input.NodeId, input.Fh)
n, f := b.releaseFileEntry(input.NodeId, input.Fh)
f.wg.Wait()
f.mu.Lock()
......@@ -847,6 +928,7 @@ func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
b.mu.Lock()
defer b.mu.Unlock()
b.releaseBackingIDRef(n)
b.freeFiles = append(b.freeFiles, uint32(input.Fh))
}
......@@ -933,7 +1015,8 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus
b.mu.Lock()
defer b.mu.Unlock()
out.Fh = uint64(b.registerFile(n, nil, 0))
fe := b.registerFile(n, nil, 0)
out.Fh = uint64(fe.fh)
return fuse.OK
}
......
// Copyright 2019 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 fs
import "syscall"
// ENOATTR indicates that an extended attribute was not present.
var ENOATTR = syscall.ENOATTR
......@@ -4,7 +4,7 @@
package fs
import "syscall"
import "golang.org/x/sys/unix"
// ENOATTR indicates that an extended attribute was not present.
var ENOATTR = syscall.ENODATA
const ENOATTR = unix.ENODATA
//go:build !linux
package fs
import "golang.org/x/sys/unix"
const ENOATTR = unix.ENOATTR
......@@ -45,7 +45,7 @@ func (ds *errDirStream) Next() (fuse.DirEntry, syscall.Errno) {
Mode: fuse.S_IFREG,
Name: "last",
Ino: 3,
}, syscall.EKEYEXPIRED
}, syscall.EBADMSG
}
panic("boom")
......@@ -76,9 +76,14 @@ func TestDirStreamError(t *testing.T) {
} else if e.Name != "first" {
t.Errorf("got %q want 'first'", e.Name)
}
if _, errno := ds.Next(); errno != syscall.EKEYEXPIRED {
t.Errorf("got errno %v, want EKEYEXPIRED", errno)
// Here we need choose a errno to test if errno could be passed and handled
// correctly by the fuse library. To build the test on different platform,
// an errno which defined on each platform should be chosen. And if the
// chosen integer number is not a valid errno, the fuse in kernel would refuse
// and throw an error, which is observed on Linux.
// Here we choose to use EBADMSG, which is defined on multiple Unix-like OSes.
if _, errno := ds.Next(); errno != syscall.EBADMSG {
t.Errorf("got errno %v, want EBADMSG", errno)
}
})
}
......
......@@ -12,6 +12,16 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
)
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Namlen uint16
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) {
f, err := os.Open(nm)
if err != nil {
......
package fs
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Type uint8
Pad0 uint8
Namlen uint16
Pad1 uint16
Name [1]uint8 // align to 4 bytes for 32 bits.
}
// Copyright 2019 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 fs
import (
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
type loopbackDirStream struct {
buf []byte
todo []byte
todoErrno syscall.Errno
// Protects fd so we can guard against double close
mu sync.Mutex
fd int
}
// NewLoopbackDirStream open a directory for reading as a DirStream
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755)
if err != nil {
return nil, ToErrno(err)
}
ds := &loopbackDirStream{
buf: make([]byte, 4096),
fd: fd,
}
if err := ds.load(); err != 0 {
ds.Close()
return nil, err
}
return ds, OK
}
func (ds *loopbackDirStream) Close() {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.fd != -1 {
syscall.Close(ds.fd)
ds.fd = -1
}
}
func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock()
defer ds.mu.Unlock()
return len(ds.todo) > 0 || ds.todoErrno != 0
}
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
......@@ -64,50 +8,3 @@ type dirent struct {
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.todoErrno != 0 {
return fuse.DirEntry{}, ds.todoErrno
}
// We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic
// "converted pointer straddles multiple allocations"
de := (*dirent)(unsafe.Pointer(&ds.todo[0]))
nameBytes := ds.todo[unsafe.Offsetof(dirent{}.Name):de.Reclen]
ds.todo = ds.todo[de.Reclen:]
// After the loop, l contains the index of the first '\0'.
l := 0
for l = range nameBytes {
if nameBytes[l] == 0 {
break
}
}
nameBytes = nameBytes[:l]
result := fuse.DirEntry{
Ino: de.Ino,
Mode: (uint32(de.Type) << 12),
Name: string(nameBytes),
}
return result, ds.load()
}
func (ds *loopbackDirStream) load() syscall.Errno {
if len(ds.todo) > 0 {
return OK
}
n, err := syscall.Getdents(ds.fd, ds.buf)
if n < 0 {
n = 0
}
ds.todo = ds.buf[:n]
ds.todoErrno = ToErrno(err)
return OK
}
//go:build !darwin
// Copyright 2019 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 fs
import (
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
)
type loopbackDirStream struct {
buf []byte
todo []byte
todoErrno syscall.Errno
// Protects fd so we can guard against double close
mu sync.Mutex
fd int
}
// NewLoopbackDirStream open a directory for reading as a DirStream
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755)
if err != nil {
return nil, ToErrno(err)
}
ds := &loopbackDirStream{
buf: make([]byte, 4096),
fd: fd,
}
if err := ds.load(); err != 0 {
ds.Close()
return nil, err
}
return ds, OK
}
func (ds *loopbackDirStream) Close() {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.fd != -1 {
syscall.Close(ds.fd)
ds.fd = -1
}
}
func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock()
defer ds.mu.Unlock()
return len(ds.todo) > 0 || ds.todoErrno != 0
}
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.todoErrno != 0 {
return fuse.DirEntry{}, ds.todoErrno
}
// We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic
// "converted pointer straddles multiple allocations"
de := (*dirent)(unsafe.Pointer(&ds.todo[0]))
nameBytes := ds.todo[unsafe.Offsetof(dirent{}.Name):de.Reclen]
ds.todo = ds.todo[de.Reclen:]
// After the loop, l contains the index of the first '\0'.
l := 0
for l = range nameBytes {
if nameBytes[l] == 0 {
break
}
}
nameBytes = nameBytes[:l]
result := fuse.DirEntry{
Ino: de.Ino,
Mode: (uint32(de.Type) << 12),
Name: string(nameBytes),
}
return result, ds.load()
}
func (ds *loopbackDirStream) load() syscall.Errno {
if len(ds.todo) > 0 {
return OK
}
n, err := unix.Getdents(ds.fd, ds.buf)
if n < 0 {
n = 0
}
ds.todo = ds.buf[:n]
ds.todoErrno = ToErrno(err)
return OK
}
......@@ -7,12 +7,10 @@ package fs
import (
"context"
"sync"
// "time"
"syscall"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/fallocate"
"golang.org/x/sys/unix"
)
......@@ -42,6 +40,14 @@ var _ = (FileFlusher)((*loopbackFile)(nil))
var _ = (FileFsyncer)((*loopbackFile)(nil))
var _ = (FileSetattrer)((*loopbackFile)(nil))
var _ = (FileAllocater)((*loopbackFile)(nil))
var _ = (FilePassthroughFder)((*loopbackFile)(nil))
func (f *loopbackFile) PassthroughFd() (int, bool) {
// This Fd is not accessed concurrently, but lock anyway for uniformity.
f.mu.Lock()
defer f.mu.Unlock()
return f.fd, true
}
func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
f.mu.Lock()
......@@ -241,3 +247,13 @@ func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (ui
n, err := unix.Seek(f.fd, int64(off), int(whence))
return uint64(n), ToErrno(err)
}
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
err := fallocate.Fallocate(f.fd, mode, int64(off), int64(sz))
if err != nil {
return ToErrno(err)
}
return OK
}
......@@ -4,7 +4,29 @@
package fs
import "github.com/hanwen/go-fuse/v2/fuse"
import (
"context"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
func setBlocks(out *fuse.Attr) {
}
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra Getattr() calls.
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var attr fuse.AttrOut
if a == nil || m == nil {
errno := f.Getattr(context.Background(), &attr)
if errno != 0 {
return errno
}
}
tv := utimens.Fill(a, m, &attr.Attr)
err := syscall.Futimes(int(f.fd), tv)
return ToErrno(err)
}
package fs
import "github.com/hanwen/go-fuse/v2/fuse"
func setBlocks(out *fuse.Attr) {
}
......@@ -5,32 +5,9 @@
package fs
import (
"context"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
)
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
err := syscall.Fallocate(f.fd, mode, int64(off), int64(sz))
if err != nil {
return ToErrno(err)
}
return OK
}
// Utimens - file handle based version of loopbackFileSystem.Utimens()
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var ts [2]syscall.Timespec
ts[0] = fuse.UtimeToTimespec(a)
ts[1] = fuse.UtimeToTimespec(m)
err := futimens(int(f.fd), &ts)
return ToErrno(err)
}
func setBlocks(out *fuse.Attr) {
if out.Blksize > 0 {
return
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !darwin
package fs
import (
"syscall"
"time"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Utimens - file handle based version of loopbackFileSystem.Utimens()
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var ts [2]syscall.Timespec
ts[0] = fuse.UtimeToTimespec(a)
ts[1] = fuse.UtimeToTimespec(m)
err := futimens(int(f.fd), &ts)
return ToErrno(err)
}
// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
// "flags" set to zero
func futimens(fd int, times *[2]syscall.Timespec) (err error) {
......
......@@ -69,6 +69,11 @@ type Inode struct {
// protected by bridge.mu
openFiles []uint32
// backing files, protected by bridge.mu
backingIDRefcount int
backingID int32
backingFd int
// mu protects the following mutable fields. When locking
// multiple Inodes, locks must be acquired using
// lockNodes/unlockNodes
......
......@@ -12,6 +12,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/renameat"
"golang.org/x/sys/unix"
)
// LoopbackRoot holds the parameters for creating a new loopback
......@@ -123,7 +124,7 @@ var _ = (NodeMknoder)((*LoopbackNode)(nil))
func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name)
err := syscall.Mknod(p, mode, int(rdev))
err := syscall.Mknod(p, mode, intDev(rdev))
if err != nil {
return nil, ToErrno(err)
}
......@@ -402,20 +403,23 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
atime, aok := in.GetATime()
if mok || aok {
ap := &atime
mp := &mtime
if !aok {
ap = nil
ta := unix.Timespec{Nsec: unix_UTIME_OMIT}
tm := unix.Timespec{Nsec: unix_UTIME_OMIT}
var err error
if aok {
ta, err = unix.TimeToTimespec(atime)
if err != nil {
return ToErrno(err)
}
}
if !mok {
mp = nil
if mok {
tm, err = unix.TimeToTimespec(mtime)
if err != nil {
return ToErrno(err)
}
}
var ts [2]syscall.Timespec
ts[0] = fuse.UtimeToTimespec(ap)
ts[1] = fuse.UtimeToTimespec(mp)
if err := syscall.UtimesNano(p, ts[:]); err != nil {
ts := []unix.Timespec{ta, tm}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, ts, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return ToErrno(err)
}
}
......@@ -441,6 +445,46 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
return OK
}
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err)
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err)
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err)
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
lfIn, ok := fhIn.(*loopbackFile)
if !ok {
return 0, unix.ENOTSUP
}
lfOut, ok := fhOut.(*loopbackFile)
if !ok {
return 0, unix.ENOTSUP
}
signedOffIn := int64(offIn)
signedOffOut := int64(offOut)
doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags))
return 0, syscall.ENOSYS
}
// NewLoopbackRoot returns a root node for a loopback file system whose
// root is at the given root. This node implements all NodeXxxxer
// operations available.
......
......@@ -8,86 +8,11 @@
package fs
import (
"context"
"syscall"
"time"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.ENOSYS
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
return syscall.ENOSYS
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
// TODO: Handle `mode` parameter.
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(sz),
0,
}
// Linux version for reference:
// err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz))
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(f.fd), uintptr(syscall.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
return errno
}
const unix_UTIME_OMIT = 0x0
// timeToTimeval - Convert time.Time to syscall.Timeval
//
......@@ -101,25 +26,11 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv
}
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra Getattr() calls.
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var attr fuse.AttrOut
if a == nil || m == nil {
errno := f.Getattr(context.Background(), &attr)
if errno != 0 {
return errno
}
}
tv := utimens.Fill(a, m, &attr.Attr)
err := syscall.Futimes(int(f.fd), tv)
return ToErrno(err)
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
func intDev(dev uint32) int {
return int(dev)
}
package fs
import (
"context"
"syscall"
"golang.org/x/sys/unix"
)
const unix_UTIME_OMIT = unix.UTIME_OMIT
// FreeBSD has added copy_file_range(2) since FreeBSD 12. However,
// golang.org/x/sys/unix hasn't add corresponding syscall constant or
// wrap function. Here we define the syscall constant until sys/unix
// provides.
const sys_COPY_FILE_RANGE = 569
// TODO: replace the manual syscall when sys/unix provides CopyFileRange
// for FreeBSD
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
count, _, errno := unix.Syscall6(sys_COPY_FILE_RANGE,
uintptr(fdIn), uintptr(offIn), uintptr(fdOut), uintptr(offOut),
uintptr(len), uintptr(flags),
)
return uint32(count), errno
}
func intDev(dev uint32) uint64 {
return uint64(dev)
}
// BSDs syscall use different convention of data buf retrieved
// through syscall `unix.Listxattr`.
// Ref: extattr_list_file(2)
func retrieveAttrName(buf []byte) [][]byte {
var attrList [][]byte
for p := 0; p < len(buf); {
attrNameLen := int(buf[p])
p++
attrName := buf[p : p+attrNameLen]
attrList = append(attrList, attrName)
p += attrNameLen
}
return attrList
}
// Since FUSE on FreeBSD expect Linux flavor data format of
// listxattr, we should reconstruct it with data returned by
// FreeBSD's syscall. And here we have added a "user." prefix
// to put them under "user" namespace, which is readable and
// writable for normal user, for a userspace implemented FS.
func rebuildAttrBuf(attrList [][]byte) []byte {
ret := make([]byte, 0)
for _, attrName := range attrList {
nsAttrName := append([]byte("user."), attrName...)
ret = append(ret, nsAttrName...)
ret = append(ret, 0x0)
}
return ret
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
// In order to simulate same data format as Linux does,
// and the size of returned buf is required to match, we must
// call unix.Llistxattr twice.
sz, err := unix.Llistxattr(n.path(), nil)
if err != nil {
return uint32(sz), ToErrno(err)
}
rawBuf := make([]byte, sz)
sz, err = unix.Llistxattr(n.path(), rawBuf)
if err != nil {
return uint32(sz), ToErrno(err)
}
attrList := retrieveAttrName(rawBuf)
rebuiltBuf := rebuildAttrBuf(attrList)
sz = len(rebuiltBuf)
if len(dest) != 0 {
// When len(dest) is 0, which means that caller wants to get
// the size. If len(dest) is less than len(rebuiltBuf), but greater
// than 0 dest will be also filled with data from rebuiltBuf,
// but truncated to len(dest). copy() function will do the same.
// And this behaviour is same as FreeBSD's syscall extattr_list_file(2).
sz = copy(dest, rebuiltBuf)
}
return uint32(sz), ToErrno(err)
}
......@@ -8,56 +8,19 @@
package fs
import (
"context"
"syscall"
"golang.org/x/sys/unix"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
const unix_UTIME_OMIT = unix.UTIME_OMIT
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err)
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err)
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err)
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err)
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
count, err := unix.CopyFileRange(fdIn, &offIn, fdOut, &offOut, len, flags)
return uint32(count), ToErrno(err)
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
lfIn, ok := fhIn.(*loopbackFile)
if !ok {
return 0, syscall.ENOTSUP
}
lfOut, ok := fhOut.(*loopbackFile)
if !ok {
return 0, syscall.ENOTSUP
}
signedOffIn := int64(offIn)
signedOffOut := int64(offOut)
count, err := unix.CopyFileRange(lfIn.fd, &signedOffIn, lfOut.fd, &signedOffOut, int(len), int(flags))
return uint32(count), ToErrno(err)
func intDev(dev uint32) int {
return int(dev)
}
......@@ -45,63 +45,6 @@ func TestRenameNoOverwrite(t *testing.T) {
}
}
func TestXAttr(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
tc.writeOrig("file", "", 0644)
buf := make([]byte, 1024)
attr := "user.xattrtest"
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err == syscall.ENOTSUP {
t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override")
}
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA {
t.Fatalf("got %v want ENOATTR", err)
}
value := []byte("value")
if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
t.Fatalf("Setxattr: %v", err)
}
sz, err := syscall.Listxattr(tc.mntDir+"/file", nil)
if err != nil {
t.Fatalf("Listxattr: %v", err)
}
buf = make([]byte, sz)
if _, err := syscall.Listxattr(tc.mntDir+"/file", buf); err != nil {
t.Fatalf("Listxattr: %v", err)
} else {
attributes := bytes.Split(buf[:sz], []byte{0})
found := false
for _, a := range attributes {
if string(a) == attr {
found = true
break
}
}
if !found {
t.Fatalf("Listxattr: %q (not found: %q", buf[:sz], attr)
}
}
sz, err = syscall.Getxattr(tc.mntDir+"/file", attr, buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
if bytes.Compare(buf[:sz], value) != 0 {
t.Fatalf("Getxattr got %q want %q", buf[:sz], value)
}
if err := syscall.Removexattr(tc.mntDir+"/file", attr); err != nil {
t.Fatalf("Removexattr: %v", err)
}
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA {
t.Fatalf("got %v want ENOATTR", err)
}
}
// TestXAttrSymlink verifies that we did not forget to use Lgetxattr instead
// of Getxattr. This test is Linux-specific because it depends on the behavoir
// of the `security` namespace.
......
package fs
import (
"bytes"
"fmt"
"os"
"reflect"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/internal/renameat"
"github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix"
)
......@@ -38,8 +41,12 @@ func TestRenameExchange(t *testing.T) {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil {
t.Errorf("rename EXCHANGE: %v", err)
if err := renameat.Renameat(f1, "file", f2, "file", renameat.RENAME_EXCHANGE); err != nil {
if err == unix.ENOSYS {
t.Skip("rename EXCHANGE not support on current system")
} else {
t.Errorf("rename EXCHANGE: %v", err)
}
}
var after1, after2 unix.Stat_t
......@@ -81,3 +88,62 @@ func TestRenameExchange(t *testing.T) {
t.Errorf("got inode %d for %q want %d", ino2.StableAttr().Ino, "dir/file", after2.Ino)
}
}
func TestXAttr(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
tc.writeOrig("file", "", 0644)
buf := make([]byte, 1024)
attrNameSpace := "user"
attrName := "xattrtest"
attr := fmt.Sprintf("%s.%s", attrNameSpace, attrName)
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err == unix.ENOTSUP {
t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override")
}
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err != ENOATTR {
t.Fatalf("got %v want ENOATTR", err)
}
value := []byte("value")
if err := unix.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
t.Fatalf("Setxattr: %v", err)
}
sz, err := unix.Listxattr(tc.mntDir+"/file", nil)
if err != nil {
t.Fatalf("Listxattr: %v", err)
}
buf = make([]byte, sz)
if _, err := unix.Listxattr(tc.mntDir+"/file", buf); err != nil {
t.Fatalf("Listxattr: %v", err)
} else {
attributes := retrieveAttrName(buf[:sz])
found := false
for _, a := range attributes {
if string(a) == attr || attrNameSpace+string(a) == attr {
found = true
break
}
}
if !found {
t.Fatalf("Listxattr: %q (not found: %q", attributes, attr)
}
}
sz, err = unix.Getxattr(tc.mntDir+"/file", attr, buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
if bytes.Compare(buf[:sz], value) != 0 {
t.Fatalf("Getxattr got %q want %q", buf[:sz], value)
}
if err := unix.Removexattr(tc.mntDir+"/file", attr); err != nil {
t.Fatalf("Removexattr: %v", err)
}
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err != ENOATTR {
t.Fatalf("got %v want ENOATTR", err)
}
}
//go:build !freebsd
package fs
import (
"bytes"
"context"
"syscall"
"golang.org/x/sys/unix"
)
func retrieveAttrName(buf []byte) [][]byte {
attributes := bytes.Split(buf, []byte{0})
return attributes
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err)
}
......@@ -244,7 +244,7 @@ func bdiReadahead(mnt string) int {
if err != nil {
panic(err)
}
path := fmt.Sprintf("/sys/class/bdi/%d:%d/read_ahead_kb", unix.Major(st.Dev), unix.Minor(st.Dev))
path := fmt.Sprintf("/sys/class/bdi/%d:%d/read_ahead_kb", unix.Major(uint64(st.Dev)), unix.Minor(uint64(st.Dev)))
buf, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
......
......@@ -137,8 +137,7 @@ func TestDataFile(t *testing.T) {
if err := syscall.Lstat(mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat: %v", err)
}
if want := uint32(syscall.S_IFREG | 0464); st.Mode != want {
if want := uint(syscall.S_IFREG | 0464); uint(st.Mode) != want {
t.Errorf("got mode %o, want %o", st.Mode, want)
}
......
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"context"
"io"
"os"
"sync"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type rwRegisteringNode struct {
LoopbackNode
mu sync.Mutex
reads int
writes int
}
func (n *rwRegisteringNode) Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
n.mu.Lock()
defer n.mu.Unlock()
n.reads++
return f.(FileReader).Read(ctx, dest, off)
}
func (n *rwRegisteringNode) Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno) {
n.mu.Lock()
defer n.mu.Unlock()
n.writes++
return f.(FileWriter).Write(ctx, data, off)
}
func TestPassthrough(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("passthrough requires CAP_SYS_ADMIN")
}
mnt := t.TempDir()
n := &rwRegisteringNode{}
rootData := &LoopbackRoot{
Path: t.TempDir(),
NewNode: func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder {
return n
},
}
n.RootData = rootData
root := &LoopbackNode{
RootData: rootData,
}
opts := &Options{}
opts.Debug = testutil.VerboseTest()
server, err := Mount(mnt, root, opts)
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
if 0 == server.KernelSettings().Flags64()&fuse.CAP_PASSTHROUGH {
t.Skip("Kernel does not support passthrough")
}
fn := mnt + "/file"
want := "hello there"
if err := os.WriteFile(fn, []byte(want), 0666); err != nil {
t.Fatalf("WriteFile: %v", err)
}
f, err := os.Open(fn)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
got, err := io.ReadAll(f)
if err != nil {
t.Fatalf("Open: %v", err)
}
if want != string(got) {
t.Errorf("got %q want %q", got, want)
}
want2 := "xxxx"
if err := os.WriteFile(fn, []byte(want2), 0666); err != nil {
t.Fatalf("WriteFile: %v", err)
}
got2, err := os.ReadFile(fn)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if string(got2) != want2 {
t.Errorf("got %q want %q", got2, want2)
}
f.Close()
server.Unmount()
if n.reads > 0 {
t.Errorf("got readcount %d want 0", n.reads)
}
if n.writes > 0 {
t.Errorf("got writecount %d want 0", n.writes)
}
}
......@@ -11,14 +11,14 @@ import (
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
)
func TestReadonlyCreate(t *testing.T) {
root := &Inode{}
mntDir, _ := testMount(t, root, nil)
_, err := syscall.Creat(mntDir+"/test", 0644)
_, err := unix.Open(mntDir+"/test", unix.O_CREAT, 0644)
if want := syscall.EROFS; want != err {
t.Fatalf("got err %v, want %v", err, want)
}
......@@ -44,7 +44,7 @@ func TestDefaultPermissions(t *testing.T) {
var st syscall.Stat_t
if err := syscall.Lstat(filepath.Join(mntDir, k), &st); err != nil {
t.Error("Lstat", err)
} else if st.Mode != v {
} else if uint(st.Mode) != uint(v) {
t.Errorf("got %o want %o", st.Mode, v)
}
}
......
......@@ -25,6 +25,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/v2/posixtest"
"golang.org/x/sys/unix"
)
type testCase struct {
......@@ -339,7 +340,7 @@ func TestMknod(t *testing.T) {
var st syscall.Stat_t
if err := syscall.Stat(p, &st); err != nil {
got := st.Mode &^ 07777
if want := mode; got != want {
if want := uint(mode); want != uint(got) {
t.Fatalf("stat(%s): got %o want %o", nm, got, want)
}
}
......@@ -646,28 +647,38 @@ func TestStaleHardlinks(t *testing.T) {
// Disable all caches we can disable
tc := newTestCase(t, &testOptions{attrCache: false, entryCache: false})
// gvfsd-trash sets an inotify watch on mntDir and stat()s every file that is
// created, racing with the test logic ( https://github.com/hanwen/go-fuse/issues/478 ).
// Use a subdir to prevent that.
if err := os.Mkdir(tc.mntDir+"/x", 0755); err != nil {
t.Fatal(err)
}
// "link0" is original file
link0 := tc.mntDir + "/link0"
if fd, err := syscall.Creat(link0, 0600); err != nil {
link0 := tc.mntDir + "/x/link0"
if fd, err := unix.Open(link0, unix.O_CREAT, 0600); err != nil {
t.Fatal(err)
} else {
syscall.Close(fd)
}
// Create hardlinks via mntDir
t.Logf("create link1...20, pid=%d", os.Getpid())
for i := 1; i < 20; i++ {
linki := fmt.Sprintf(tc.mntDir+"/link%d", i)
linki := fmt.Sprintf(tc.mntDir+"/x/link%d", i)
if err := syscall.Link(link0, linki); err != nil {
t.Fatal(err)
}
}
// Delete hardlinks via origDir (behind loopback fs's back)
t.Log("delete link1...20 behind loopback's back")
for i := 1; i < 20; i++ {
linki := fmt.Sprintf(tc.origDir+"/link%d", i)
linki := fmt.Sprintf(tc.origDir+"/x/link%d", i)
if err := syscall.Unlink(linki); err != nil {
t.Fatal(err)
}
}
// Try to open link0 via mntDir
t.Log("open link0")
fd, err := syscall.Open(link0, syscall.O_RDONLY, 0)
if err != nil {
t.Error(err)
......@@ -680,3 +691,47 @@ func TestStaleHardlinks(t *testing.T) {
func init() {
syscall.Umask(0)
}
func testMountDir(dir string) error {
opts := &Options{}
opts.Debug = testutil.VerboseTest()
server, err := Mount(dir, &Inode{}, opts)
if err != nil {
return err
}
server.Unmount()
server.Wait()
return nil
}
func TestParallelMount(t *testing.T) {
before := runtime.GOMAXPROCS(1)
defer runtime.GOMAXPROCS(before)
// Per default, only 1000 FUSE mounts are allowed, then you get
// > /usr/bin/fusermount3: too many FUSE filesystems mounted; mount_max=N can be set in /etc/fuse.conf
// Let's stay well below 1000.
N := 900
todo := make(chan string, N)
result := make(chan error, N)
for i := 0; i < N; i++ {
todo <- t.TempDir()
}
close(todo)
P := 2
for i := 0; i < P; i++ {
go func() {
for d := range todo {
result <- testMountDir(d)
}
}()
}
for i := 0; i < N; i++ {
e := <-result
if e != nil {
t.Error(e)
}
}
}
......@@ -289,9 +289,10 @@ type MountOptions struct {
// RawFileSystem is an interface close to the FUSE wire protocol.
//
// Unless you really know what you are doing, you should not implement
// this, but rather the nodefs.Node or pathfs.FileSystem interfaces; the
// details of getting interactions with open files, renames, and threading
// right etc. are somewhat tricky and not very interesting.
// this, but rather the interfaces associated with
// fs.InodeEmbedder. The details of getting interactions with open
// files, renames, and threading right etc. are somewhat tricky and
// not very interesting.
//
// Each FUSE request results in a corresponding method called by Server.
// Several calls may be made simultaneously, because the server typically calls
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
//go:build !linux
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
......@@ -70,11 +70,11 @@ func CurrentOwner() *Owner {
}
}
const _UTIME_OMIT = ((1 << 30) - 2)
// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a
// "Timespec" that can be passed to the utimensat syscall.
// A nil pointer is converted to the special UTIME_OMIT value.
//
// Deprecated: use unix.TimeToTimespec from the x/sys/unix package instead.
func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) {
if t == nil {
ts.Nsec = _UTIME_OMIT
......
package fuse
// Ref: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/stat.h#L576
const _UTIME_OMIT = -2
//go:build !darwin
package fuse
import "golang.org/x/sys/unix"
const _UTIME_OMIT = unix.UTIME_OMIT
......@@ -7,6 +7,30 @@ import (
"syscall"
)
var reservedFDs []*os.File
func init() {
// Both Darwin and Linux invoke a subprocess with one
// inherited file descriptor to create the mount. To protect
// against deadlock, we must ensure that file descriptor 3
// never points to a FUSE filesystem. We do this by simply
// grabbing fd 3 and never releasing it. (This is not
// completely foolproof: a preceding init routine could grab fd 3,
// and then release it later.)
for {
r, w, err := os.Pipe()
if err != nil {
panic(fmt.Sprintf("os.Pipe(): %v", err))
}
w.Close()
if r.Fd() > 3 {
r.Close()
break
}
reservedFDs = append(reservedFDs, r)
}
}
func getConnection(local *os.File) (int, error) {
conn, err := net.FileConn(local)
if err != nil {
......
package fuse
import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
func callMountFuseFs(mountPoint string, opts *MountOptions) (fd int, err error) {
bin, err := fusermountBinary()
if err != nil {
return 0, err
}
f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0o000)
if err != nil {
return -1, err
}
cmd := exec.Command(
bin,
"--safe",
"-o", strings.Join(opts.optionsStrings(), ","),
"3",
mountPoint,
)
cmd.Env = []string{"MOUNT_FUSEFS_CALL_BY_LIB=1"}
cmd.ExtraFiles = []*os.File{f}
if err := cmd.Start(); err != nil {
f.Close()
return -1, fmt.Errorf("mount_fusefs: %v", err)
}
if err := cmd.Wait(); err != nil {
// see if we have a better error to report
f.Close()
return -1, fmt.Errorf("mount_fusefs: %v", err)
}
return int(f.Fd()), nil
}
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
// Using the same logic from libfuse to prevent chaos
for {
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0o000)
if err != nil {
return -1, err
}
if f.Fd() > 2 {
f.Close()
break
}
}
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
// works.
fd = parseFuseFd(mountPoint)
if fd >= 0 {
if opts.Debug {
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
}
} else {
// Usual case: mount via the `fusermount` suid helper
fd, err = callMountFuseFs(mountPoint, opts)
if err != nil {
return
}
}
// golang sets CLOEXEC on file descriptors when they are
// acquired through normal operations (e.g. open).
// Buf for fd, we have to set CLOEXEC manually
syscall.CloseOnExec(fd)
close(ready)
return fd, err
}
func unmount(mountPoint string, opts *MountOptions) (err error) {
_ = opts
return syscall.Unmount(mountPoint, 0)
}
func fusermountBinary() (string, error) {
binPaths := []string{
"/sbin/mount_fusefs",
}
for _, path := range binPaths {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("no FUSE mount utility found")
}
......@@ -99,40 +99,41 @@ func doInit(server *Server, req *request) {
return
}
kernelFlags := input.Flags64()
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_RENAME_SWAP)
kernelFlags &= (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES | CAP_RENAME_SWAP | CAP_PASSTHROUGH)
if server.opts.EnableLocks {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
kernelFlags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
}
if server.opts.EnableSymlinkCaching {
server.kernelSettings.Flags |= CAP_CACHE_SYMLINKS
kernelFlags |= CAP_CACHE_SYMLINKS
}
if server.opts.EnableAcl {
server.kernelSettings.Flags |= CAP_POSIX_ACL
kernelFlags |= CAP_POSIX_ACL
}
if server.opts.SyncRead {
// Clear CAP_ASYNC_READ
server.kernelSettings.Flags &= ^uint32(CAP_ASYNC_READ)
kernelFlags &= ^uint64(CAP_ASYNC_READ)
}
if server.opts.DisableReadDirPlus {
// Clear CAP_READDIRPLUS
server.kernelSettings.Flags &= ^uint32(CAP_READDIRPLUS)
kernelFlags &= ^uint64(CAP_READDIRPLUS)
}
dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA
dataCacheMode := kernelFlags & CAP_AUTO_INVAL_DATA
if server.opts.ExplicitDataCacheControl {
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
dataCacheMode = 0
explicit := input.Flags & CAP_EXPLICIT_INVAL_DATA
explicit := kernelFlags & CAP_EXPLICIT_INVAL_DATA
if explicit != 0 {
dataCacheMode = explicit
}
}
server.kernelSettings.Flags |= dataCacheMode
kernelFlags |= dataCacheMode
if input.Minor >= 13 {
server.setSplice()
......@@ -149,13 +150,13 @@ func doInit(server *Server, req *request) {
Major: _FUSE_KERNEL_VERSION,
Minor: _OUR_MINOR_VERSION,
MaxReadAhead: input.MaxReadAhead,
Flags: server.kernelSettings.Flags,
MaxWrite: uint32(server.opts.MaxWrite),
CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4),
MaxBackground: uint16(server.opts.MaxBackground),
MaxPages: uint16(maxPages),
MaxStackDepth: 1,
}
out.setFlags(kernelFlags)
if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead {
out.MaxReadAhead = uint32(server.opts.MaxReadAhead)
}
......
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fuse
import (
"syscall"
"unsafe"
)
const (
_DEV_IOC_BACKING_OPEN = 0x4010e501
_DEV_IOC_BACKING_CLOSE = 0x4004e502
)
// RegisterBackingFd registers the given file descriptor in the
// kernel, so the kernel can bypass FUSE and access the backing file
// directly for read and write calls. On success a backing ID is
// returned. The backing ID should unregistered using
// UnregisterBackingFd() once the file is released. Within the
// kernel, an inode can only have a single backing file, so multiple
// Open/Create calls should coordinate to return a consistent backing
// ID.
func (ms *Server) RegisterBackingFd(m *BackingMap) (int32, syscall.Errno) {
ms.writeMu.Lock()
id, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_OPEN), uintptr(unsafe.Pointer(m)))
ms.writeMu.Unlock()
if ms.opts.Debug {
ms.opts.Logger.Printf("ioctl: BACKING_OPEN %v: id %d (%v)", m.string(), id, errno)
}
return int32(id), errno
}
// UnregisterBackingFd unregisters the given ID in the kernel. The ID
// should have been acquired before using RegisterBackingFd.
func (ms *Server) UnregisterBackingFd(id int32) syscall.Errno {
ms.writeMu.Lock()
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ms.mountFd), uintptr(_DEV_IOC_BACKING_CLOSE), uintptr(unsafe.Pointer(&id)))
ms.writeMu.Unlock()
if ms.opts.Debug {
ms.opts.Logger.Printf("ioctl: BACKING_CLOSE id %d: %v", id, errno)
}
return errno
}
//go:build !darwin
package fuse
import (
......
......@@ -12,76 +12,82 @@ import (
)
var (
writeFlagNames = newFlagNames(map[int64]string{
WRITE_CACHE: "CACHE",
WRITE_LOCKOWNER: "LOCKOWNER",
isTest bool
writeFlagNames = newFlagNames([]flagNameEntry{
{WRITE_CACHE, "CACHE"},
{WRITE_LOCKOWNER, "LOCKOWNER"},
})
readFlagNames = newFlagNames(map[int64]string{
READ_LOCKOWNER: "LOCKOWNER",
readFlagNames = newFlagNames([]flagNameEntry{
{READ_LOCKOWNER, "LOCKOWNER"},
})
initFlagNames = newFlagNames(map[int64]string{
CAP_ASYNC_READ: "ASYNC_READ",
CAP_POSIX_LOCKS: "POSIX_LOCKS",
CAP_FILE_OPS: "FILE_OPS",
CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC",
CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT",
CAP_BIG_WRITES: "BIG_WRITES",
CAP_DONT_MASK: "DONT_MASK",
CAP_SPLICE_WRITE: "SPLICE_WRITE",
CAP_SPLICE_MOVE: "SPLICE_MOVE",
CAP_SPLICE_READ: "SPLICE_READ",
CAP_FLOCK_LOCKS: "FLOCK_LOCKS",
CAP_IOCTL_DIR: "IOCTL_DIR",
CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA",
CAP_READDIRPLUS: "READDIRPLUS",
CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO",
CAP_ASYNC_DIO: "ASYNC_DIO",
CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE",
CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT",
CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS",
CAP_POSIX_ACL: "POSIX_ACL",
CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV",
CAP_ABORT_ERROR: "ABORT_ERROR",
CAP_MAX_PAGES: "MAX_PAGES",
CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS",
CAP_SECURITY_CTX: "SECURITY_CTX",
CAP_HAS_INODE_DAX: "HAS_INODE_DAX",
CAP_CREATE_SUPP_GROUP: "CREATE_SUPP_GROUP",
CAP_HAS_EXPIRE_ONLY: "HAS_EXPIRE_ONLY",
CAP_DIRECT_IO_RELAX: "DIRECT_IO_RELAX",
initFlagNames = newFlagNames([]flagNameEntry{
{CAP_ASYNC_READ, "ASYNC_READ"},
{CAP_POSIX_LOCKS, "POSIX_LOCKS"},
{CAP_FILE_OPS, "FILE_OPS"},
{CAP_ATOMIC_O_TRUNC, "ATOMIC_O_TRUNC"},
{CAP_EXPORT_SUPPORT, "EXPORT_SUPPORT"},
{CAP_BIG_WRITES, "BIG_WRITES"},
{CAP_DONT_MASK, "DONT_MASK"},
{CAP_SPLICE_WRITE, "SPLICE_WRITE"},
{CAP_SPLICE_MOVE, "SPLICE_MOVE"},
{CAP_SPLICE_READ, "SPLICE_READ"},
{CAP_FLOCK_LOCKS, "FLOCK_LOCKS"},
{CAP_IOCTL_DIR, "IOCTL_DIR"},
{CAP_AUTO_INVAL_DATA, "AUTO_INVAL_DATA"},
{CAP_READDIRPLUS, "READDIRPLUS"},
{CAP_READDIRPLUS_AUTO, "READDIRPLUS_AUTO"},
{CAP_ASYNC_DIO, "ASYNC_DIO"},
{CAP_WRITEBACK_CACHE, "WRITEBACK_CACHE"},
{CAP_NO_OPEN_SUPPORT, "NO_OPEN_SUPPORT"},
{CAP_PARALLEL_DIROPS, "PARALLEL_DIROPS"},
{CAP_POSIX_ACL, "POSIX_ACL"},
{CAP_HANDLE_KILLPRIV, "HANDLE_KILLPRIV"},
{CAP_ABORT_ERROR, "ABORT_ERROR"},
{CAP_MAX_PAGES, "MAX_PAGES"},
{CAP_CACHE_SYMLINKS, "CACHE_SYMLINKS"},
{CAP_SECURITY_CTX, "SECURITY_CTX"},
{CAP_HAS_INODE_DAX, "HAS_INODE_DAX"},
{CAP_CREATE_SUPP_GROUP, "CREATE_SUPP_GROUP"},
{CAP_HAS_EXPIRE_ONLY, "HAS_EXPIRE_ONLY"},
{CAP_DIRECT_IO_ALLOW_MMAP, "DIRECT_IO_ALLOW_MMAP"},
{CAP_PASSTHROUGH, "PASSTHROUGH"},
{CAP_NO_EXPORT_SUPPORT, "NO_EXPORT_SUPPORT"},
{CAP_HAS_RESEND, "HAS_RESEND"},
})
releaseFlagNames = newFlagNames(map[int64]string{
RELEASE_FLUSH: "FLUSH",
releaseFlagNames = newFlagNames([]flagNameEntry{
{RELEASE_FLUSH, "FLUSH"},
})
openFlagNames = newFlagNames(map[int64]string{
int64(os.O_WRONLY): "WRONLY",
int64(os.O_RDWR): "RDWR",
int64(os.O_APPEND): "APPEND",
int64(syscall.O_ASYNC): "ASYNC",
int64(os.O_CREATE): "CREAT",
int64(os.O_EXCL): "EXCL",
int64(syscall.O_NOCTTY): "NOCTTY",
int64(syscall.O_NONBLOCK): "NONBLOCK",
int64(os.O_SYNC): "SYNC",
int64(os.O_TRUNC): "TRUNC",
0x8000: "LARGEFILE",
int64(syscall.O_CLOEXEC): "CLOEXEC",
int64(syscall.O_DIRECTORY): "DIRECTORY",
openFlagNames = newFlagNames([]flagNameEntry{
{int64(os.O_WRONLY), "WRONLY"},
{int64(os.O_RDWR), "RDWR"},
{int64(os.O_APPEND), "APPEND"},
{int64(syscall.O_ASYNC), "ASYNC"},
{int64(os.O_CREATE), "CREAT"},
{int64(os.O_EXCL), "EXCL"},
{int64(syscall.O_NOCTTY), "NOCTTY"},
{int64(syscall.O_NONBLOCK), "NONBLOCK"},
{int64(os.O_SYNC), "SYNC"},
{int64(os.O_TRUNC), "TRUNC"},
{int64(syscall.O_CLOEXEC), "CLOEXEC"},
{int64(syscall.O_DIRECTORY), "DIRECTORY"},
})
fuseOpenFlagNames = newFlagNames(map[int64]string{
FOPEN_DIRECT_IO: "DIRECT",
FOPEN_KEEP_CACHE: "CACHE",
FOPEN_NONSEEKABLE: "NONSEEK",
FOPEN_CACHE_DIR: "CACHE_DIR",
FOPEN_STREAM: "STREAM",
fuseOpenFlagNames = newFlagNames([]flagNameEntry{
{FOPEN_DIRECT_IO, "DIRECT"},
{FOPEN_KEEP_CACHE, "CACHE"},
{FOPEN_NONSEEKABLE, "NONSEEK"},
{FOPEN_CACHE_DIR, "CACHE_DIR"},
{FOPEN_STREAM, "STREAM"},
{FOPEN_NOFLUSH, "NOFLUSH"},
{FOPEN_PARALLEL_DIRECT_WRITES, "PARALLEL_DIRECT_WRITES"},
{FOPEN_PASSTHROUGH, "PASSTHROUGH"},
})
accessFlagName = newFlagNames(map[int64]string{
X_OK: "x",
W_OK: "w",
R_OK: "r",
accessFlagName = newFlagNames([]flagNameEntry{
{X_OK, "x"},
{W_OK, "w"},
{R_OK, "r"},
})
getAttrFlagNames = newFlagNames(map[int64]string{
FUSE_GETATTR_FH: "FH",
getAttrFlagNames = newFlagNames([]flagNameEntry{
{FUSE_GETATTR_FH, "FH"},
})
)
......@@ -99,10 +105,10 @@ type flagNameEntry struct {
}
// newFlagNames creates flagNames from flag->name map.
func newFlagNames(names map[int64]string) *flagNames {
func newFlagNames(names []flagNameEntry) *flagNames {
var v flagNames
for flag, name := range names {
v.set(flag, name)
for _, e := range names {
v.set(e.bits, e.name)
}
return &v
}
......@@ -112,7 +118,7 @@ func (names *flagNames) set(flag int64, name string) {
entry := flagNameEntry{bits: flag, name: name}
for i := 0; i < 64; i++ {
if flag&(1<<i) != 0 {
if ie := names[i]; ie.bits != 0 {
if ie := names[i]; ie.bits != 0 && isTest {
panic(fmt.Sprintf("%s (%x) overlaps with %s (%x)", name, flag, ie.name, ie.bits))
}
names[i] = entry
......@@ -205,22 +211,26 @@ func (in *OpenIn) string() string {
}
func (in *OpenOut) string() string {
return fmt.Sprintf("{Fh %d %s}", in.Fh,
backing := ""
if in.BackingID != 0 {
backing = fmt.Sprintf("backing=%d ", in.BackingID)
}
return fmt.Sprintf("{Fh %d %s%s}", in.Fh, backing,
flagString(fuseOpenFlagNames, int64(in.OpenFlags), ""))
}
func (in *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s}",
in.Major, in.Minor, in.MaxReadAhead,
flagString(initFlagNames, int64(in.Flags)|(int64(in.Flags2)<<32), ""))
flagString(initFlagNames, int64(in.Flags64()), ""))
}
func (o *InitOut) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s %d/%d Wr %d Tg %d MaxPages %d}",
return fmt.Sprintf("{%d.%d Ra %d %s %d/%d Wr %d Tg %d MaxPages %d MaxStack %d}",
o.Major, o.Minor, o.MaxReadAhead,
flagString(initFlagNames, int64(o.Flags), ""),
flagString(initFlagNames, int64(o.Flags64()), ""),
o.CongestionThreshold, o.MaxBackground, o.MaxWrite,
o.TimeGran, o.MaxPages)
o.TimeGran, o.MaxPages, o.MaxStackDepth)
}
func (s *FsyncIn) string() string {
......@@ -269,7 +279,7 @@ func (o *EntryOut) string() string {
}
func (o *CreateOut) string() string {
return fmt.Sprintf("{n%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, &o.OpenOut)
return fmt.Sprintf("{n%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, o.OpenOut.string())
}
func (o *StatfsOut) string() string {
......@@ -352,3 +362,22 @@ func Print(obj interface{}) string {
}
return fmt.Sprintf("%T: %v", obj, obj)
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"B%d*%d i%d:%d "+
"A %f "+
"M %f "+
"C %f}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
ft(a.Ctime, a.Ctimensec))
}
func (m *BackingMap) string() string {
return fmt.Sprintf("{fd %d, flags 0x%x}", m.Fd, m.Flags)
}
......@@ -19,21 +19,6 @@ func init() {
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE")
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"B%d*%d i%d:%d "+
"A %f "+
"M %f "+
"C %f}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
ft(a.Ctime, a.Ctimensec))
}
func (me *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s]}", me.Mode,
......
package fuse
func init() {
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
}
......@@ -5,13 +5,20 @@
package fuse
import (
"fmt"
"runtime"
"strings"
"syscall"
)
func init() {
// syscall.O_LARGEFILE is 0x0 on x86_64, but the kernel
// supplies 0x8000 anyway, except on mips64el, where 0x8000 is
// used for O_DIRECT.
if !strings.Contains(runtime.GOARCH, "mips64") {
openFlagNames.set(0x8000, "LARGEFILE")
}
openFlagNames.set(syscall.O_DIRECT, "DIRECT")
openFlagNames.set(syscall.O_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")
......@@ -22,48 +29,3 @@ func init() {
initFlagNames.set(CAP_INIT_EXT, "INIT_EXT")
initFlagNames.set(CAP_INIT_RESERVED, "INIT_RESERVED")
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"B%d*%d i%d:%d "+
"A %f "+
"M %f "+
"C %f}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
ft(a.Ctime, a.Ctimensec))
}
func (in *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s] (0%o)}", in.Mode,
flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask)
}
func (in *GetAttrIn) string() string {
return fmt.Sprintf("{Fh %d %s}", in.Fh_, flagString(getAttrFlagNames, int64(in.Flags_), ""))
}
func (in *MknodIn) string() string {
return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev)
}
func (in *ReadIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(readFlagNames, int64(in.ReadFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}
func (in *WriteIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(writeFlagNames, int64(in.WriteFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}
......@@ -8,6 +8,10 @@ import (
"testing"
)
func init() {
isTest = true
}
// verify that flagString always formats flags in the same order.
func TestFlagStringOrder(t *testing.T) {
var flags int64 = CAP_ASYNC_READ | CAP_SPLICE_WRITE | CAP_READDIRPLUS | CAP_MAX_PAGES | CAP_EXPLICIT_INVAL_DATA
......@@ -23,10 +27,10 @@ func TestFlagStringOrder(t *testing.T) {
// verify how flagString handles provided default.
func TestFlagStringDefault(t *testing.T) {
names := newFlagNames(map[int64]string{
1: "AAA",
2: "BBB",
4: "CCC",
names := newFlagNames([]flagNameEntry{
{1, "AAA"},
{2, "BBB"},
{4, "CCC"},
})
testv := []struct {
......
//go:build !darwin
package fuse
import "fmt"
func (in *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s] (0%o)}", in.Mode,
flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask)
}
func (in *GetAttrIn) string() string {
return fmt.Sprintf("{Fh %d %s}", in.Fh_, flagString(getAttrFlagNames, int64(in.Flags_), ""))
}
func (in *MknodIn) string() string {
return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev)
}
func (in *ReadIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(readFlagNames, int64(in.ReadFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}
func (in *WriteIn) string() string {
return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}",
in.Fh, in.Offset, in.Size,
flagString(writeFlagNames, int64(in.WriteFlags), ""),
in.LockOwner,
flagString(openFlagNames, int64(in.Flags), "RDONLY"))
}
......@@ -103,9 +103,9 @@ func (r *request) InputDebug() string {
names += fmt.Sprintf("%s %db", data, len(r.arg))
}
return fmt.Sprintf("rx %d: %s n%d %s%s",
return fmt.Sprintf("rx %d: %s n%d %s%s p%d",
r.inHeader.Unique, operationName(r.inHeader.Opcode), r.inHeader.NodeId,
val, names)
val, names, r.inHeader.Caller.Pid)
}
func (r *request) OutputDebug() string {
......
package fuse
const outputHeaderSize = 160
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 28
)
......@@ -76,6 +76,7 @@ type Server struct {
singleReader bool
canSplice bool
loops sync.WaitGroup
serving bool // for preventing duplicate Serve() calls
ready chan error
......@@ -195,14 +196,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
}
ms := &Server{
fileSystem: fs,
opts: &o,
maxReaders: maxReaders,
retrieveTab: make(map[uint64]*retrieveCacheRequest),
// OSX has races when multiple routines read from the
// FUSE device: on unmount, sometime some reads do not
// error-out, meaning that unmount will hang.
singleReader: runtime.GOOS == "darwin",
fileSystem: fs,
opts: &o,
maxReaders: maxReaders,
retrieveTab: make(map[uint64]*retrieveCacheRequest),
singleReader: useSingleReader,
ready: make(chan error, 1),
}
ms.reqPool.New = func() interface{} {
......@@ -419,6 +417,14 @@ func (ms *Server) recordStats(req *request) {
//
// Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() {
if ms.serving {
// Calling Serve() multiple times leads to a panic on unmount and fun
// debugging sessions ( https://github.com/hanwen/go-fuse/issues/512 ).
// Catch it early.
log.Panic("Serve() must only be called once, you have called it a second time")
}
ms.serving = true
ms.loop(false)
ms.loops.Wait()
......@@ -471,6 +477,33 @@ func (ms *Server) handleInit() Status {
return OK
}
// loop is the FUSE event loop. The simplistic way of calling this is
// with singleReader=true, which has a single goroutine reading the
// device, and spawning a new goroutine for each request. It is
// however 2x slower than processing the request inline with the
// reader. The latter requires more logic, because whenever we start
// processing the request, we have to make sure a new routine is
// spawned to read the device.
//
// Benchmark results i5-8350 pinned at 2Ghz:
//
// singleReader = true
//
// BenchmarkGoFuseRead 954 1137408 ns/op 1843.80 MB/s 5459 B/op 173 allocs/op
// BenchmarkGoFuseRead-2 1327 798635 ns/op 2625.92 MB/s 5072 B/op 169 allocs/op
// BenchmarkGoFuseStat 1530 750944 ns/op
// BenchmarkGoFuseStat-2 8455 120091 ns/op
// BenchmarkGoFuseReaddir 741 1561004 ns/op
// BenchmarkGoFuseReaddir-2 2508 599821 ns/op
//
// singleReader = false
//
// BenchmarkGoFuseRead 1890 671576 ns/op 3122.73 MB/s 5393 B/op 136 allocs/op
// BenchmarkGoFuseRead-2 2948 429578 ns/op 4881.88 MB/s 32235 B/op 157 allocs/op
// BenchmarkGoFuseStat 7886 153546 ns/op
// BenchmarkGoFuseStat-2 9310 121332 ns/op
// BenchmarkGoFuseReaddir 4074 361568 ns/op
// BenchmarkGoFuseReaddir-2 3511 319765 ns/op
func (ms *Server) loop(exitIdle bool) {
defer ms.loops.Done()
exit:
......@@ -529,15 +562,22 @@ func (ms *Server) handleRequest(req *request) Status {
errNo := ms.write(req)
if errNo != 0 {
// Unless debugging is enabled, ignore ENOENT for INTERRUPT responses
// which indicates that the referred request is no longer known by the
// kernel. This is a normal if the referred request already has
// completed.
if ms.opts.Debug || !(req.inHeader.Opcode == _OP_INTERRUPT && errNo == ENOENT) {
// Ignore ENOENT for INTERRUPT responses which
// indicates that the referred request is no longer
// known by the kernel. This is a normal if the
// referred request already has completed.
//
// Ignore ENOENT for RELEASE responses. When the FS
// is unmounted directly after a file close, the
// device can go away while we are still processing
// RELEASE. This is because RELEASE is analogous to
// FORGET, and is not synchronized with the calling
// process, but does require a response.
if ms.opts.Debug || !(errNo == ENOENT && (req.inHeader.Opcode == _OP_INTERRUPT ||
req.inHeader.Opcode == _OP_RELEASE)) {
ms.opts.Logger.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode))
}
}
ms.returnRequest(req)
return Status(errNo)
......
......@@ -8,6 +8,8 @@ import (
"syscall"
)
const useSingleReader = false
func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 {
err := handleEINTR(func() error {
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !linux
package fuse
import (
"syscall"
"golang.org/x/sys/unix"
)
// OSX and FreeBSD has races when multiple routines read
// from the FUSE device: on unmount, sometime some reads
// do not error-out, meaning that unmount will hang.
const useSingleReader = true
func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 {
err := handleEINTR(func() error {
_, err := syscall.Write(ms.mountFd, header)
_, err := unix.Write(ms.mountFd, header)
return err
})
return ToStatus(err)
......
package fuse
import "fmt"
func (s *Server) setSplice() {
s.canSplice = false
}
func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error {
return fmt.Errorf("unimplemented")
}
......@@ -6,49 +6,10 @@ package fuse
import (
"bytes"
"os"
"syscall"
"unsafe"
)
// TODO - move these into Go's syscall package.
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
}
func getxattr(path string, attr string, dest []byte) (sz int, errno int) {
pathBs := syscall.StringBytePtr(path)
attrBs := syscall.StringBytePtr(attr)
......
......@@ -5,45 +5,10 @@
package fuse
import (
"os"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
// TODO - move these into Go's syscall package.
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return n, err
}
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
n, err = unix.Writev(fd, packet)
return
}
//go:build !linux
package fuse
import (
"os"
"syscall"
"unsafe"
)
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return n, err
}
// Until golang.orgx/sys/unix provides Writev for Darwin and FreeBSD,
// keep the syscall wrapping funcitons.
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
}
......@@ -44,9 +44,6 @@ const (
// ENOSYS Function not implemented
ENOSYS = Status(syscall.ENOSYS)
// ENODATA No data available
ENODATA = Status(syscall.ENODATA)
// ENOTDIR Not a directory
ENOTDIR = Status(syscall.ENOTDIR)
......@@ -259,12 +256,13 @@ const (
FOPEN_STREAM = (1 << 4)
FOPEN_NOFLUSH = (1 << 5)
FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6)
FOPEN_PASSTHROUGH = (1 << 7)
)
type OpenOut struct {
Fh uint64
OpenFlags uint32
Padding uint32
BackingID int32
}
// To be set in InitIn/InitOut.Flags.
......@@ -301,12 +299,16 @@ const (
CAP_MAX_PAGES = (1 << 22)
CAP_CACHE_SYMLINKS = (1 << 23)
/* bits 24..31 differ across linux and mac */
/* bits 32..63 get shifted down 32 bits into the Flags2 field */
CAP_SECURITY_CTX = (1 << 32)
CAP_HAS_INODE_DAX = (1 << 33)
CAP_CREATE_SUPP_GROUP = (1 << 34)
CAP_HAS_EXPIRE_ONLY = (1 << 35)
CAP_DIRECT_IO_RELAX = (1 << 36)
CAP_SECURITY_CTX = (1 << 32)
CAP_HAS_INODE_DAX = (1 << 33)
CAP_CREATE_SUPP_GROUP = (1 << 34)
CAP_HAS_EXPIRE_ONLY = (1 << 35)
CAP_DIRECT_IO_ALLOW_MMAP = (1 << 36)
CAP_PASSTHROUGH = (1 << 37)
CAP_NO_EXPORT_SUPPORT = (1 << 38)
CAP_HAS_RESEND = (1 << 39)
)
type InitIn struct {
......@@ -320,6 +322,10 @@ type InitIn struct {
Unused [11]uint32
}
func (i *InitIn) Flags64() uint64 {
return uint64(i.Flags) | uint64(i.Flags2)<<32
}
type InitOut struct {
Major uint32
Minor uint32
......@@ -332,7 +338,12 @@ type InitOut struct {
MaxPages uint16
Padding uint16
Flags2 uint32
Unused [7]uint32
MaxStackDepth uint32
Unused [6]uint32
}
func (o *InitOut) Flags64() uint64 {
return uint64(o.Flags) | uint64(o.Flags2)<<32
}
type _CuseInitIn struct {
......@@ -519,6 +530,7 @@ const (
NOTIFY_STORE_CACHE = -4 // store data into kernel cache of an inode
NOTIFY_RETRIEVE_CACHE = -5 // retrieve data from kernel cache of an inode
NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted
NOTIFY_RESEND = -7
// NOTIFY_CODE_MAX = -6
)
......@@ -693,3 +705,79 @@ func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) {
}
lk.Pid = uint32(flockT.Pid)
}
const (
// Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set.
FUSE_GETATTR_FH = (1 << 0)
)
type GetAttrIn struct {
InHeader
Flags_ uint32
Dummy uint32
Fh_ uint64
}
// Flags accesses the flags. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Flags() uint32 {
return g.Flags_
}
// Fh accesses the file handle. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Fh() uint64 {
return g.Fh_
}
type MknodIn struct {
InHeader
// Mode to use, including the Umask value
Mode uint32
Rdev uint32
Umask uint32
Padding uint32
}
type CreateIn struct {
InHeader
Flags uint32
// Mode for the new file; already takes Umask into account.
Mode uint32
// Umask used for this create call.
Umask uint32
Padding uint32
}
type ReadIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
ReadFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type WriteIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
WriteFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
// Data for registering a file as backing an inode.
type BackingMap struct {
Fd int32
Flags uint32
padding uint64
}
......@@ -9,6 +9,7 @@ import (
)
const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS.
// EREMOTEIO is not supported on Darwin.
......@@ -61,73 +62,6 @@ const (
FOPEN_PURGE_UBC = (1 << 31)
)
// compat with linux.
const (
// Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set.
FUSE_GETATTR_FH = (1 << 0)
)
type GetAttrIn struct {
InHeader
Flags_ uint32
Dummy uint32
Fh_ uint64
}
func (g *GetAttrIn) Flags() uint32 {
return g.Flags_
}
func (g *GetAttrIn) Fh() uint64 {
return g.Fh_
}
// Uses OpenIn struct for create.
type CreateIn struct {
InHeader
Flags uint32
// Mode for the new file; already takes Umask into account.
Mode uint32
// Umask used for this create call.
Umask uint32
Padding uint32
}
type MknodIn struct {
InHeader
// Mode to use, including the Umask value
Mode uint32
Rdev uint32
Umask uint32
Padding uint32
}
type ReadIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
ReadFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type WriteIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
WriteFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type SetXAttrIn struct {
InHeader
Size uint32
......@@ -191,3 +125,7 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Bavail /= adj
}
}
func (o *InitOut) setFlags(flags uint64) {
o.Flags = uint32(flags)
}
package fuse
import "syscall"
const (
ENOATTR = Status(syscall.ENOATTR)
ENODATA = Status(syscall.EIO)
)
const (
CAP_NO_OPENDIR_SUPPORT = (1 << 24)
// The higher capabilities are not currently defined by FreeBSD.
// Ref: https://cgit.freebsd.org/src/tree/sys/fs/fuse/fuse_kernel.h
// CAP_RENAME_SWAP only exists on OSX.
CAP_RENAME_SWAP = 0x0
// CAP_EXPLICIT_INVAL_DATA is not supported on FreeBSD.
CAP_EXPLICIT_INVAL_DATA = 0x0
)
func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks
s.Bsize = uint32(statfs.Bsize)
s.Bfree = statfs.Bfree
s.Bavail = uint64(statfs.Bavail)
s.Files = statfs.Files
s.Ffree = uint64(statfs.Ffree)
s.Frsize = uint32(statfs.Bsize)
s.NameLen = uint32(statfs.Namemax)
}
......@@ -9,6 +9,7 @@ import (
)
const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA.
// EREMOTEIO Remote I/O error
......@@ -22,124 +23,18 @@ const (
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_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
// Blocks is the number of 512-byte blocks that the file occupies on disk.
Blocks uint64
Atime uint64
Mtime uint64
Ctime uint64
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Mode uint32
Nlink uint32
Owner
Rdev uint32
// Blksize is the preferred size for file system operations.
Blksize uint32
Padding uint32
}
type SetAttrIn struct {
SetAttrInCommon
}
const (
// Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set.
FUSE_GETATTR_FH = (1 << 0)
)
type GetAttrIn struct {
InHeader
Flags_ uint32
Dummy uint32
Fh_ uint64
}
// Flags accesses the flags. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Flags() uint32 {
return g.Flags_
}
// Fh accesses the file handle. This is a method, because OSXFuse does not
// have GetAttrIn flags.
func (g *GetAttrIn) Fh() uint64 {
return g.Fh_
}
type CreateIn struct {
InHeader
Flags uint32
// Mode for the new file; already takes Umask into account.
Mode uint32
// Umask used for this create call.
Umask uint32
Padding uint32
}
type MknodIn struct {
InHeader
// Mode to use, including the Umask value
Mode uint32
Rdev uint32
Umask uint32
Padding uint32
}
type ReadIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
ReadFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type WriteIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
WriteFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type SetXAttrIn struct {
InHeader
Size uint32
Flags uint32
}
type GetXAttrIn struct {
InHeader
Size uint32
Padding uint32
}
func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks
s.Bsize = uint32(statfs.Bsize)
......@@ -150,3 +45,8 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Frsize = uint32(statfs.Frsize)
s.NameLen = uint32(statfs.Namelen)
}
func (o *InitOut) setFlags(flags uint64) {
o.Flags = uint32(flags) | CAP_INIT_EXT
o.Flags2 = uint32(flags >> 32)
}
//go:build !darwin
package fuse
type Attr struct {
Ino uint64
Size uint64
// Blocks is the number of 512-byte blocks that the file occupies on disk.
Blocks uint64
Atime uint64
Mtime uint64
Ctime uint64
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Mode uint32
Nlink uint32
Owner
Rdev uint32
// Blksize is the preferred size for file system operations.
Blksize uint32
Padding uint32
}
type SetAttrIn struct {
SetAttrInCommon
}
type SetXAttrIn struct {
InHeader
Size uint32
Flags uint32
}
type GetXAttrIn struct {
InHeader
Size uint32
Padding uint32
}
......@@ -7,4 +7,4 @@ require (
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
)
go 1.13
go 1.17
package fallocate
// Fallocate is a wrapper around fallocate syscall.
// On Linux, it is a wrapper around fallocate(2).
// On Darwin, it is a wrapper around fnctl(2).
// On FreeBSD, it is a wrapper around posix_fallocate(2).
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return fallocate(fd, mode, off, len)
}
package fallocate
import (
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// TODO: Handle `mode` parameter.
_ = mode
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(len),
0,
}
_, _, errno := unix.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(unix.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
return errno
}
return nil
}
package fallocate
import (
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// Ignore mode
_ = mode
ret, _, _ := unix.Syscall(unix.SYS_POSIX_FALLOCATE, uintptr(fd), uintptr(off), uintptr(len))
if ret != 0 {
return unix.Errno(ret)
}
return nil
}
package fallocate
import "golang.org/x/sys/unix"
func fallocate(fd int, mode uint32, off int64, len int64) error {
return unix.Fallocate(fd, mode, off, len)
}
......@@ -3,6 +3,7 @@ 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).
// On FreeBSD, it is a wrapper around renameat(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 "golang.org/x/sys/unix"
const (
// Since FreeBSD does not currently privode renameat syscall
// beyond POSIX standard like Linux and Darwin do, we borrow
// the defination from Linux but reject these non-POSIX flags.
RENAME_EXCHANGE = (1 << 1)
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
if flags != 0 {
return unix.ENOSYS
}
return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
}
......@@ -17,6 +17,7 @@ import (
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/fallocate"
"golang.org/x/sys/unix"
)
......@@ -47,6 +48,32 @@ var All = map[string]func(*testing.T, string){
"DirSeek": DirSeek,
"FcntlFlockSetLk": FcntlFlockSetLk,
"FcntlFlockLocksFile": FcntlFlockLocksFile,
"SetattrSymlink": SetattrSymlink,
}
func SetattrSymlink(t *testing.T, mnt string) {
l := filepath.Join(mnt, "link")
if err := os.Symlink("doesnotexist", l); err != nil {
t.Fatalf("symlink: %v", err)
}
tvs := []unix.Timeval{
{Sec: 42, Usec: 1},
{Sec: 43, Usec: 2},
}
if err := unix.Lutimes(l, tvs); err != nil {
t.Fatalf("Lutimes: %v", err)
}
var st unix.Stat_t
if err := unix.Lstat(l, &st); err != nil {
t.Fatalf("Lstat: %v", err)
}
if st.Mtim.Sec != 43 {
// Can't check atime; it's hard to prevent implicit readlink calls.
t.Fatalf("got mtime %v, want 43", st.Mtim)
}
}
func DirectIO(t *testing.T, mnt string) {
......@@ -272,7 +299,7 @@ func FstatDeleted(t *testing.T, mnt string) {
const iMax = 9
type file struct {
fd int
st syscall.Stat_t
st unix.Stat_t
}
files := make(map[int]file)
for i := 0; i <= iMax; i++ {
......@@ -283,8 +310,8 @@ func FstatDeleted(t *testing.T, mnt string) {
if err != nil {
t.Fatalf("WriteFile: %v", err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
var st unix.Stat_t
err = unix.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
......@@ -303,14 +330,14 @@ func FstatDeleted(t *testing.T, mnt string) {
}
// Fstat in random order
for _, v := range files {
var st syscall.Stat_t
err := syscall.Fstat(v.fd, &st)
var st unix.Stat_t
err := unix.Fstat(v.fd, &st)
if err != nil {
t.Fatal(err)
}
// Ignore ctime, changes on unlink
v.st.Ctim = syscall.Timespec{}
st.Ctim = syscall.Timespec{}
v.st.Ctim = unix.Timespec{}
st.Ctim = unix.Timespec{}
// Nlink value should have dropped to zero
v.st.Nlink = 0
// Rest should stay the same
......@@ -607,7 +634,7 @@ func OpenAt(t *testing.T, mnt string) {
if err != nil {
t.Fatal(err)
}
fd, err := syscall.Openat(dirfd, "file1", syscall.O_CREAT, 0700)
fd, err := unix.Openat(dirfd, "file1", syscall.O_CREAT, 0700)
if err != nil {
t.Fatal(err)
}
......@@ -624,7 +651,7 @@ func Fallocate(t *testing.T, mnt string) {
t.Fatalf("OpenFile failed: %v", err)
}
defer rwFile.Close()
err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
err = fallocate.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
if err != nil {
t.Fatalf("Fallocate failed: %v", err)
}
......@@ -661,12 +688,11 @@ func FcntlFlockSetLk(t *testing.T, mnt string) {
}
defer f2.Close()
lk := syscall.Flock_t{}
if err := syscall.FcntlFlock(f2.Fd(), unix.F_OFD_GETLK, &lk); err != nil {
if err := sysFcntlFlockGetOFDLock(f2.Fd(), &lk); err != nil {
t.Errorf("FcntlFlock failed: %v", err)
}
if lk.Type != syscall.F_WRLCK {
t.Errorf("got lk.Type=%v, want %v", lk.Type, syscall.F_WRLCK)
}
}
}
......
package posixtest
import "syscall"
// Exist at least from macOS Siera 10.12
const sys_F_OFD_GETLK = 92
func sysFcntlFlockGetOFDLock(fd uintptr, lk *syscall.Flock_t) error {
return syscall.FcntlFlock(fd, sys_F_OFD_GETLK, lk)
}
package posixtest
import (
"syscall"
"unsafe"
)
// Since FreeBSD doesn't implement the F_OFD_GETLK, to mimic its behaviour,
// we here first fork the process via syscall, and execute fcntl(2) in the child
// process to get the lock info. Then we send the lock info via pipe back to
// the parent test process.
func sysFcntlFlockGetOFDLock(fd uintptr, lk *syscall.Flock_t) error {
pipefd := make([]int, 2)
err := syscall.Pipe(pipefd)
if err != nil {
return err
}
pid, _, err := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
if pid == 0 { // child process
syscall.Close(pipefd[0]) // close read end
var clk syscall.Flock_t
// Here we must give a vaild lock type, or fcntl(2) will return
// EINVAL. And it should be different from what we set earlier
// in the test. Here we set to F_RDLOCK.
clk.Type = syscall.F_RDLCK
syscall.FcntlFlock(fd, syscall.F_GETLK, &clk)
syscall.Syscall(syscall.SYS_WRITE, uintptr(pipefd[1]), uintptr(unsafe.Pointer(&clk)), unsafe.Sizeof(clk))
syscall.Exit(0)
} else if pid > 0 { // parent process
syscall.Close(pipefd[1]) // close write end
buf := make([]byte, unsafe.Sizeof(*lk))
syscall.Read(pipefd[0], buf)
*lk = *((*syscall.Flock_t)(unsafe.Pointer(&buf[0])))
syscall.Close(pipefd[0]) // close read end
} else {
return err
}
return nil
}
package posixtest
import (
"syscall"
"golang.org/x/sys/unix"
)
func sysFcntlFlockGetOFDLock(fd uintptr, lk *syscall.Flock_t) error {
return syscall.FcntlFlock(fd, unix.F_OFD_GETLK, lk)
}
......@@ -11,6 +11,10 @@ import (
"golang.org/x/sys/unix"
)
type dirent struct {
unix.Dirent
}
// DirSeek tests that seeking on a directory works for
// https://github.com/hanwen/go-fuse/issues/344 .
//
......@@ -47,7 +51,7 @@ func DirSeek(t *testing.T, mnt string) {
total := 0
for {
n, err := unix.Getdents(fd, buf)
n, err := unix.ReadDirent(fd, buf)
if err != nil {
t.Fatal(err)
}
......@@ -55,24 +59,24 @@ func DirSeek(t *testing.T, mnt string) {
break
}
for bpos := 0; bpos < n; total++ {
d := (*unix.Dirent)(unsafe.Pointer(&buf[bpos]))
d := (*dirent)(unsafe.Pointer(&buf[bpos]))
if total > historyLen {
t.Fatal("too many files")
}
for i := 0; i < total; i++ {
if offHistory[i] == d.Off {
if offHistory[i] == d.off() {
t.Errorf("entries %d and %d gave duplicate d.Off %d",
i, total, d.Off)
i, total, d.off())
}
}
offHistory[total] = d.Off
inoHistory[total] = d.Ino
offHistory[total] = d.off()
inoHistory[total] = d.ino()
bpos += int(d.Reclen)
}
}
// check if seek works correctly
d := (*unix.Dirent)(unsafe.Pointer(&buf[0]))
d := (*dirent)(unsafe.Pointer(&buf[0]))
for i := total - 1; i >= 0; i-- {
var seekTo int64
if i > 0 {
......@@ -83,7 +87,7 @@ func DirSeek(t *testing.T, mnt string) {
t.Fatal(err)
}
n, err := unix.Getdents(fd, buf)
n, err := unix.ReadDirent(fd, buf)
if err != nil {
t.Fatal(err)
}
......@@ -92,9 +96,9 @@ func DirSeek(t *testing.T, mnt string) {
continue
}
if d.Ino != inoHistory[i] {
if d.ino() != inoHistory[i] {
t.Errorf("entry %d has inode %d, expected %d",
i, d.Ino, inoHistory[i])
i, d.ino(), inoHistory[i])
}
}
}
package posixtest
func (d *dirent) off() int64 {
return int64(d.Seekoff)
}
func (d *dirent) ino() uint64 {
return d.Ino
}
package posixtest
func (d *dirent) off() int64 {
return d.Off
}
func (d *dirent) ino() uint64 {
return d.Fileno
}
package posixtest
func (d *dirent) off() int64 {
return d.Off
}
func (d *dirent) ino() uint64 {
return d.Ino
}
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package splice
func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) {
panic("not implemented")
return 0, nil
}
func (p *Pair) LoadFrom(fd uintptr, sz int) (int, error) {
panic("not implemented")
return 0, nil
}
func (p *Pair) WriteTo(fd uintptr, n int) (int, error) {
panic("not implemented")
return 0, nil
}
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......
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