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: ...@@ -11,7 +11,6 @@ jobs:
strategy: strategy:
matrix: matrix:
go: go:
- "1.16.x" # Golang upstream stable
- "1.17.x" # Golang upstream stable - "1.17.x" # Golang upstream stable
- "1.18.x" # Golang upstream stable - "1.18.x" # Golang upstream stable
- "1.19.x" # Golang upstream stable - "1.19.x" # Golang upstream stable
......
...@@ -106,6 +106,7 @@ output. Here is how to read it: ...@@ -106,6 +106,7 @@ output. Here is how to read it:
- `tA` and `tE` means timeout for attributes and directory entry correspondingly; - `tA` and `tE` means timeout for attributes and directory entry correspondingly;
- `[<off> +<size>)` means data range from `<off>` inclusive till `<off>+<size>` exclusive; - `[<off> +<size>)` means data range from `<off>` inclusive till `<off>+<size>` exclusive;
- `Xb` means `X bytes`. - `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 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 whether it was for kernel request, which Go-FUSE received, or reply, which
...@@ -114,24 +115,24 @@ Go-FUSE sent back to kernel. ...@@ -114,24 +115,24 @@ Go-FUSE sent back to kernel.
Example debug log output: 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}} 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}} 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 } 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:///"... 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}} 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 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}} 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}} 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 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}} 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" ...@@ -17,7 +17,7 @@ GO_TEST="go test -timeout 5m -p 1 -count 1"
# Run all tests as current user # Run all tests as current user
$GO_TEST ./... $GO_TEST ./...
# Direct-mount tests need to run as root # 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 make -C benchmark
go test ./benchmark -test.bench '.*' -test.cpu 1,2 go test ./benchmark -test.bench '.*' -test.cpu 1,2
...@@ -7,32 +7,22 @@ package benchmark ...@@ -7,32 +7,22 @@ package benchmark
// Routines for benchmarking fuse. // Routines for benchmarking fuse.
import ( import (
"bufio" "bytes"
"log" "log"
"os" "os"
) )
func ReadLines(name string) []string { func ReadLines(name string) []string {
f, err := os.Open(name) data, err := os.ReadFile(name)
if err != nil { if err != nil {
log.Fatal("ReadLines: ", err) log.Fatal("ReadFile: ", err)
} }
defer f.Close()
r := bufio.NewReader(f)
l := []string{} var lines []string
for { for _, l := range bytes.Split(data, []byte("\n")) {
line, _, err := r.ReadLine() if len(l) > 0 {
if line == nil || err != nil { lines = append(lines, string(l))
break
} }
fn := string(line)
l = append(l, fn)
}
if len(l) == 0 {
log.Fatal("no files added")
} }
return lines
return l
} }
...@@ -5,54 +5,130 @@ ...@@ -5,54 +5,130 @@
package benchmark package benchmark
import ( import (
"bytes"
"flag"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
"time"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"golang.org/x/sync/errgroup"
) )
func BenchmarkGoFuseRead(b *testing.B) { func BenchmarkGoFuseMemoryRead(b *testing.B) {
fs := &readFS{} root := &readFS{}
wd, clean := setupFs(fs, b.N) mnt := setupFS(root, b.N, b)
defer clean() benchmarkRead(mnt, b, 32, "direct")
}
jobs := 32
blockSize := 64 * 1024
cmds := make([]*exec.Cmd, jobs) const blockSize = 64 * 1024
for i := 0; i < jobs; i++ { func benchmarkRead(mnt string, b *testing.B, readers int, ddflag string) {
cmds[i] = exec.Command("dd", var cmds []*exec.Cmd
fmt.Sprintf("if=%s/foo.txt", wd), for i := 0; i < readers; i++ {
"iflag=direct", cmd := exec.Command("dd",
fmt.Sprintf("if=%s/foo.txt", mnt),
"of=/dev/null", "of=/dev/null",
fmt.Sprintf("bs=%d", blockSize), fmt.Sprintf("bs=%d", blockSize),
fmt.Sprintf("count=%d", b.N)) fmt.Sprintf("count=%d", b.N))
if ddflag != "" {
cmd.Args = append(cmd.Args, "iflag="+ddflag)
}
if testutil.VerboseTest() { if testutil.VerboseTest() {
cmds[i].Stdout = os.Stdout cmd.Stderr = os.Stderr
cmds[i].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.ReportAllocs()
b.ResetTimer() 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()
}
for i := 0; i < jobs; i++ { func BenchmarkGoFuseFDRead(b *testing.B) {
i := i orig := b.TempDir()
eg.Go(func() error { fn := orig + "/foo.txt"
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 { var libfusePath = flag.String("passthrough_hp", "", "path to libfuse's passthrough_hp")
b.Fatalf("dd failed: %v", err)
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 ( ...@@ -22,22 +22,19 @@ import (
"github.com/hanwen/go-fuse/v2/internal/testutil" "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 := &fs.Options{}
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
mountPoint, err := os.MkdirTemp("", "") mountPoint := tb.TempDir()
if err != nil {
log.Panicf("TempDir: %v", err)
}
server, err := fs.Mount(mountPoint, node, opts) server, err := fs.Mount(mountPoint, node, opts)
if err != nil { if err != nil {
log.Panicf("cannot mount %v", err) tb.Fatalf("cannot mount %v", err)
} }
lmap := NewLatencyMap() lmap := NewLatencyMap()
if testutil.VerboseTest() { if testutil.VerboseTest() {
server.RecordLatencies(lmap) server.RecordLatencies(lmap)
} }
return mountPoint, func() { tb.Cleanup(func() {
if testutil.VerboseTest() { if testutil.VerboseTest() {
var total time.Duration var total time.Duration
for _, n := range []string{"LOOKUP", "GETATTR", "OPENDIR", "READDIR", for _, n := range []string{"LOOKUP", "GETATTR", "OPENDIR", "READDIR",
...@@ -57,7 +54,8 @@ func setupFs(node fs.InodeEmbedder, N int) (string, func()) { ...@@ -57,7 +54,8 @@ func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
if err != nil { if err != nil {
log.Println("error during unmount", err) log.Println("error during unmount", err)
} }
} })
return mountPoint
} }
func TestNewStatFs(t *testing.T) { func TestNewStatFs(t *testing.T) {
...@@ -68,8 +66,7 @@ func TestNewStatFs(t *testing.T) { ...@@ -68,8 +66,7 @@ func TestNewStatFs(t *testing.T) {
fs.AddFile(n, fuse.Attr{Mode: syscall.S_IFREG}) fs.AddFile(n, fuse.Attr{Mode: syscall.S_IFREG})
} }
wd, clean := setupFs(fs, 1) wd := setupFS(fs, 1, t)
defer clean()
names, err := ioutil.ReadDir(wd) names, err := ioutil.ReadDir(wd)
if err != nil { if err != nil {
...@@ -121,15 +118,14 @@ func BenchmarkGoFuseStat(b *testing.B) { ...@@ -121,15 +118,14 @@ func BenchmarkGoFuseStat(b *testing.B) {
fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG}) fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG})
} }
wd, clean := setupFs(fs, b.N) mnt := setupFS(fs, b.N, b)
defer clean()
for i, l := range files { for i, l := range files {
files[i] = filepath.Join(wd, l) files[i] = filepath.Join(mnt, l)
} }
threads := runtime.GOMAXPROCS(0) 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) b.Fatalf("TestingBOnePass %v8", err)
} }
} }
...@@ -157,12 +153,11 @@ func BenchmarkGoFuseReaddir(b *testing.B) { ...@@ -157,12 +153,11 @@ func BenchmarkGoFuseReaddir(b *testing.B) {
dirSet[filepath.Dir(fn)] = struct{}{} dirSet[filepath.Dir(fn)] = struct{}{}
} }
wd, clean := setupFs(fs, b.N) mnt := setupFS(fs, b.N, b)
defer clean()
var dirs []string var dirs []string
for dir := range dirSet { for dir := range dirSet {
dirs = append(dirs, filepath.Join(wd, dir)) dirs = append(dirs, filepath.Join(mnt, dir))
} }
b.StartTimer() b.StartTimer()
todo := b.N todo := b.N
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
// To create a file system, you should first define types for the // To create a file system, you should first define types for the
// nodes of the file system tree. // nodes of the file system tree.
// //
// struct myNode { // type myNode struct {
// fs.Inode // fs.Inode
// } // }
// //
...@@ -568,6 +568,17 @@ type NodeRenamer interface { ...@@ -568,6 +568,17 @@ type NodeRenamer interface {
type FileHandle 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. // See NodeReleaser.
type FileReleaser interface { type FileReleaser interface {
Release(ctx context.Context) syscall.Errno Release(ctx context.Context) syscall.Errno
......
This diff is collapsed.
// 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 @@ ...@@ -4,7 +4,7 @@
package fs package fs
import "syscall" import "golang.org/x/sys/unix"
// ENOATTR indicates that an extended attribute was not present. // 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) { ...@@ -45,7 +45,7 @@ func (ds *errDirStream) Next() (fuse.DirEntry, syscall.Errno) {
Mode: fuse.S_IFREG, Mode: fuse.S_IFREG,
Name: "last", Name: "last",
Ino: 3, Ino: 3,
}, syscall.EKEYEXPIRED }, syscall.EBADMSG
} }
panic("boom") panic("boom")
...@@ -76,9 +76,14 @@ func TestDirStreamError(t *testing.T) { ...@@ -76,9 +76,14 @@ func TestDirStreamError(t *testing.T) {
} else if e.Name != "first" { } else if e.Name != "first" {
t.Errorf("got %q want 'first'", e.Name) t.Errorf("got %q want 'first'", e.Name)
} }
// Here we need choose a errno to test if errno could be passed and handled
if _, errno := ds.Next(); errno != syscall.EKEYEXPIRED { // correctly by the fuse library. To build the test on different platform,
t.Errorf("got errno %v, want EKEYEXPIRED", errno) // 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 ( ...@@ -12,6 +12,16 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "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) { func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) {
f, err := os.Open(nm) f, err := os.Open(nm)
if err != nil { 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 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. // Like syscall.Dirent, but without the [256]byte name.
type dirent struct { type dirent struct {
Ino uint64 Ino uint64
...@@ -64,50 +8,3 @@ type dirent struct { ...@@ -64,50 +8,3 @@ type dirent struct {
Type uint8 Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits. 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 ...@@ -7,12 +7,10 @@ package fs
import ( import (
"context" "context"
"sync" "sync"
// "time"
"syscall" "syscall"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/fallocate"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
...@@ -42,6 +40,14 @@ var _ = (FileFlusher)((*loopbackFile)(nil)) ...@@ -42,6 +40,14 @@ var _ = (FileFlusher)((*loopbackFile)(nil))
var _ = (FileFsyncer)((*loopbackFile)(nil)) var _ = (FileFsyncer)((*loopbackFile)(nil))
var _ = (FileSetattrer)((*loopbackFile)(nil)) var _ = (FileSetattrer)((*loopbackFile)(nil))
var _ = (FileAllocater)((*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) { func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
f.mu.Lock() f.mu.Lock()
...@@ -241,3 +247,13 @@ func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (ui ...@@ -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)) n, err := unix.Seek(f.fd, int64(off), int(whence))
return uint64(n), ToErrno(err) 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 @@ ...@@ -4,7 +4,29 @@
package fs 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) { 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 @@ ...@@ -5,32 +5,9 @@
package fs package fs
import ( import (
"context"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse" "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) { func setBlocks(out *fuse.Attr) {
if out.Blksize > 0 { if out.Blksize > 0 {
return return
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved. //go:build !darwin
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs package fs
import ( import (
"syscall" "syscall"
"time"
"unsafe" "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 // futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
// "flags" set to zero // "flags" set to zero
func futimens(fd int, times *[2]syscall.Timespec) (err error) { func futimens(fd int, times *[2]syscall.Timespec) (err error) {
......
...@@ -69,6 +69,11 @@ type Inode struct { ...@@ -69,6 +69,11 @@ type Inode struct {
// protected by bridge.mu // protected by bridge.mu
openFiles []uint32 openFiles []uint32
// backing files, protected by bridge.mu
backingIDRefcount int
backingID int32
backingFd int
// mu protects the following mutable fields. When locking // mu protects the following mutable fields. When locking
// multiple Inodes, locks must be acquired using // multiple Inodes, locks must be acquired using
// lockNodes/unlockNodes // lockNodes/unlockNodes
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/renameat" "github.com/hanwen/go-fuse/v2/internal/renameat"
"golang.org/x/sys/unix"
) )
// LoopbackRoot holds the parameters for creating a new loopback // LoopbackRoot holds the parameters for creating a new loopback
...@@ -123,7 +124,7 @@ var _ = (NodeMknoder)((*LoopbackNode)(nil)) ...@@ -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) { func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := syscall.Mknod(p, mode, int(rdev)) err := syscall.Mknod(p, mode, intDev(rdev))
if err != nil { if err != nil {
return nil, ToErrno(err) return nil, ToErrno(err)
} }
...@@ -402,20 +403,23 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt ...@@ -402,20 +403,23 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
atime, aok := in.GetATime() atime, aok := in.GetATime()
if mok || aok { if mok || aok {
ta := unix.Timespec{Nsec: unix_UTIME_OMIT}
ap := &atime tm := unix.Timespec{Nsec: unix_UTIME_OMIT}
mp := &mtime var err error
if !aok { if aok {
ap = nil ta, err = unix.TimeToTimespec(atime)
if err != nil {
return ToErrno(err)
} }
if !mok {
mp = nil
} }
var ts [2]syscall.Timespec if mok {
ts[0] = fuse.UtimeToTimespec(ap) tm, err = unix.TimeToTimespec(mtime)
ts[1] = fuse.UtimeToTimespec(mp) if err != nil {
return ToErrno(err)
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) return ToErrno(err)
} }
} }
...@@ -441,6 +445,46 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt ...@@ -441,6 +445,46 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
return OK 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 // NewLoopbackRoot returns a root node for a loopback file system whose
// root is at the given root. This node implements all NodeXxxxer // root is at the given root. This node implements all NodeXxxxer
// operations available. // operations available.
......
...@@ -8,86 +8,11 @@ ...@@ -8,86 +8,11 @@
package fs package fs
import ( import (
"context"
"syscall" "syscall"
"time" "time"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
) )
var _ = (NodeGetxattrer)((*LoopbackNode)(nil)) const unix_UTIME_OMIT = 0x0
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
}
// timeToTimeval - Convert time.Time to syscall.Timeval // timeToTimeval - Convert time.Time to syscall.Timeval
// //
...@@ -101,25 +26,11 @@ func timeToTimeval(t *time.Time) syscall.Timeval { ...@@ -101,25 +26,11 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv return tv
} }
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT. func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
// We emulate using utimes() and extra Getattr() calls. len int, flags int) (uint32, syscall.Errno) {
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { return 0, syscall.ENOSYS
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)
} }
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil)) func intDev(dev uint32) int {
return int(dev)
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
} }
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 @@ ...@@ -8,56 +8,19 @@
package fs package fs
import ( import (
"context"
"syscall" "syscall"
"golang.org/x/sys/unix" "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) { func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
sz, err := unix.Lgetxattr(n.path(), attr, dest) len int, flags int) (uint32, syscall.Errno) {
return uint32(sz), ToErrno(err) count, err := unix.CopyFileRange(fdIn, &offIn, fdOut, &offOut, len, flags)
} return uint32(count), 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)
} }
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil)) func intDev(dev uint32) int {
return int(dev)
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)
} }
...@@ -45,63 +45,6 @@ func TestRenameNoOverwrite(t *testing.T) { ...@@ -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 // 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 Getxattr. This test is Linux-specific because it depends on the behavoir
// of the `security` namespace. // of the `security` namespace.
......
package fs package fs
import ( import (
"bytes"
"fmt"
"os" "os"
"reflect" "reflect"
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/v2/internal/renameat"
"github.com/kylelemons/godebug/pretty" "github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
...@@ -38,9 +41,13 @@ func TestRenameExchange(t *testing.T) { ...@@ -38,9 +41,13 @@ func TestRenameExchange(t *testing.T) {
t.Fatalf("Fstatat: %v", err) t.Fatalf("Fstatat: %v", err)
} }
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil { 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) t.Errorf("rename EXCHANGE: %v", err)
} }
}
var after1, after2 unix.Stat_t var after1, after2 unix.Stat_t
if err := unix.Fstatat(f1, "file", &after1, 0); err != nil { if err := unix.Fstatat(f1, "file", &after1, 0); err != nil {
...@@ -81,3 +88,62 @@ func TestRenameExchange(t *testing.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) 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 { ...@@ -244,7 +244,7 @@ func bdiReadahead(mnt string) int {
if err != nil { if err != nil {
panic(err) 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) buf, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
panic(err) panic(err)
......
...@@ -137,8 +137,7 @@ func TestDataFile(t *testing.T) { ...@@ -137,8 +137,7 @@ func TestDataFile(t *testing.T) {
if err := syscall.Lstat(mntDir+"/file", &st); err != nil { if err := syscall.Lstat(mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat: %v", err) t.Fatalf("Lstat: %v", err)
} }
if want := uint(syscall.S_IFREG | 0464); uint(st.Mode) != want {
if want := uint32(syscall.S_IFREG | 0464); st.Mode != want {
t.Errorf("got mode %o, want %o", 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 ( ...@@ -11,14 +11,14 @@ import (
"testing" "testing"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
) )
func TestReadonlyCreate(t *testing.T) { func TestReadonlyCreate(t *testing.T) {
root := &Inode{} root := &Inode{}
mntDir, _ := testMount(t, root, nil) mntDir, _ := testMount(t, root, nil)
_, err := unix.Open(mntDir+"/test", unix.O_CREAT, 0644)
_, err := syscall.Creat(mntDir+"/test", 0644)
if want := syscall.EROFS; want != err { if want := syscall.EROFS; want != err {
t.Fatalf("got err %v, want %v", err, want) t.Fatalf("got err %v, want %v", err, want)
} }
...@@ -44,7 +44,7 @@ func TestDefaultPermissions(t *testing.T) { ...@@ -44,7 +44,7 @@ func TestDefaultPermissions(t *testing.T) {
var st syscall.Stat_t var st syscall.Stat_t
if err := syscall.Lstat(filepath.Join(mntDir, k), &st); err != nil { if err := syscall.Lstat(filepath.Join(mntDir, k), &st); err != nil {
t.Error("Lstat", err) 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) t.Errorf("got %o want %o", st.Mode, v)
} }
} }
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/v2/posixtest" "github.com/hanwen/go-fuse/v2/posixtest"
"golang.org/x/sys/unix"
) )
type testCase struct { type testCase struct {
...@@ -339,7 +340,7 @@ func TestMknod(t *testing.T) { ...@@ -339,7 +340,7 @@ func TestMknod(t *testing.T) {
var st syscall.Stat_t var st syscall.Stat_t
if err := syscall.Stat(p, &st); err != nil { if err := syscall.Stat(p, &st); err != nil {
got := st.Mode &^ 07777 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) t.Fatalf("stat(%s): got %o want %o", nm, got, want)
} }
} }
...@@ -646,28 +647,38 @@ func TestStaleHardlinks(t *testing.T) { ...@@ -646,28 +647,38 @@ func TestStaleHardlinks(t *testing.T) {
// Disable all caches we can disable // Disable all caches we can disable
tc := newTestCase(t, &testOptions{attrCache: false, entryCache: false}) 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" is original file
link0 := tc.mntDir + "/link0" link0 := tc.mntDir + "/x/link0"
if fd, err := syscall.Creat(link0, 0600); err != nil { if fd, err := unix.Open(link0, unix.O_CREAT, 0600); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
syscall.Close(fd) syscall.Close(fd)
} }
// Create hardlinks via mntDir // Create hardlinks via mntDir
t.Logf("create link1...20, pid=%d", os.Getpid())
for i := 1; i < 20; i++ { 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 { if err := syscall.Link(link0, linki); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
// Delete hardlinks via origDir (behind loopback fs's back) // Delete hardlinks via origDir (behind loopback fs's back)
t.Log("delete link1...20 behind loopback's back")
for i := 1; i < 20; i++ { 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 { if err := syscall.Unlink(linki); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
// Try to open link0 via mntDir // Try to open link0 via mntDir
t.Log("open link0")
fd, err := syscall.Open(link0, syscall.O_RDONLY, 0) fd, err := syscall.Open(link0, syscall.O_RDONLY, 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
...@@ -680,3 +691,47 @@ func TestStaleHardlinks(t *testing.T) { ...@@ -680,3 +691,47 @@ func TestStaleHardlinks(t *testing.T) {
func init() { func init() {
syscall.Umask(0) 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 { ...@@ -289,9 +289,10 @@ type MountOptions struct {
// RawFileSystem is an interface close to the FUSE wire protocol. // RawFileSystem is an interface close to the FUSE wire protocol.
// //
// Unless you really know what you are doing, you should not implement // Unless you really know what you are doing, you should not implement
// this, but rather the nodefs.Node or pathfs.FileSystem interfaces; the // this, but rather the interfaces associated with
// details of getting interactions with open files, renames, and threading // fs.InodeEmbedder. The details of getting interactions with open
// right etc. are somewhat tricky and not very interesting. // files, renames, and threading right etc. are somewhat tricky and
// not very interesting.
// //
// Each FUSE request results in a corresponding method called by Server. // Each FUSE request results in a corresponding method called by Server.
// Several calls may be made simultaneously, because the server typically calls // 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
......
...@@ -70,11 +70,11 @@ func CurrentOwner() *Owner { ...@@ -70,11 +70,11 @@ func CurrentOwner() *Owner {
} }
} }
const _UTIME_OMIT = ((1 << 30) - 2)
// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a // UtimeToTimespec converts a "Time" pointer as passed to Utimens to a
// "Timespec" that can be passed to the utimensat syscall. // "Timespec" that can be passed to the utimensat syscall.
// A nil pointer is converted to the special UTIME_OMIT value. // 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) { func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) {
if t == nil { if t == nil {
ts.Nsec = _UTIME_OMIT 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 ( ...@@ -7,6 +7,30 @@ import (
"syscall" "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) { func getConnection(local *os.File) (int, error) {
conn, err := net.FileConn(local) conn, err := net.FileConn(local)
if err != nil { 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) { ...@@ -99,40 +99,41 @@ func doInit(server *Server, req *request) {
return return
} }
kernelFlags := input.Flags64()
server.reqMu.Lock() server.reqMu.Lock()
server.kernelSettings = *input server.kernelSettings = *input
server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS | 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_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES | CAP_RENAME_SWAP | CAP_PASSTHROUGH)
if server.opts.EnableLocks { if server.opts.EnableLocks {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS kernelFlags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
} }
if server.opts.EnableSymlinkCaching { if server.opts.EnableSymlinkCaching {
server.kernelSettings.Flags |= CAP_CACHE_SYMLINKS kernelFlags |= CAP_CACHE_SYMLINKS
} }
if server.opts.EnableAcl { if server.opts.EnableAcl {
server.kernelSettings.Flags |= CAP_POSIX_ACL kernelFlags |= CAP_POSIX_ACL
} }
if server.opts.SyncRead { if server.opts.SyncRead {
// Clear CAP_ASYNC_READ // Clear CAP_ASYNC_READ
server.kernelSettings.Flags &= ^uint32(CAP_ASYNC_READ) kernelFlags &= ^uint64(CAP_ASYNC_READ)
} }
if server.opts.DisableReadDirPlus { if server.opts.DisableReadDirPlus {
// Clear CAP_READDIRPLUS // 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 { if server.opts.ExplicitDataCacheControl {
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode // we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
dataCacheMode = 0 dataCacheMode = 0
explicit := input.Flags & CAP_EXPLICIT_INVAL_DATA explicit := kernelFlags & CAP_EXPLICIT_INVAL_DATA
if explicit != 0 { if explicit != 0 {
dataCacheMode = explicit dataCacheMode = explicit
} }
} }
server.kernelSettings.Flags |= dataCacheMode kernelFlags |= dataCacheMode
if input.Minor >= 13 { if input.Minor >= 13 {
server.setSplice() server.setSplice()
...@@ -149,13 +150,13 @@ func doInit(server *Server, req *request) { ...@@ -149,13 +150,13 @@ func doInit(server *Server, req *request) {
Major: _FUSE_KERNEL_VERSION, Major: _FUSE_KERNEL_VERSION,
Minor: _OUR_MINOR_VERSION, Minor: _OUR_MINOR_VERSION,
MaxReadAhead: input.MaxReadAhead, MaxReadAhead: input.MaxReadAhead,
Flags: server.kernelSettings.Flags,
MaxWrite: uint32(server.opts.MaxWrite), MaxWrite: uint32(server.opts.MaxWrite),
CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4), CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4),
MaxBackground: uint16(server.opts.MaxBackground), MaxBackground: uint16(server.opts.MaxBackground),
MaxPages: uint16(maxPages), MaxPages: uint16(maxPages),
MaxStackDepth: 1,
} }
out.setFlags(kernelFlags)
if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead { if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead {
out.MaxReadAhead = uint32(server.opts.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 package fuse
import ( import (
......
...@@ -12,76 +12,82 @@ import ( ...@@ -12,76 +12,82 @@ import (
) )
var ( var (
writeFlagNames = newFlagNames(map[int64]string{ isTest bool
WRITE_CACHE: "CACHE", writeFlagNames = newFlagNames([]flagNameEntry{
WRITE_LOCKOWNER: "LOCKOWNER", {WRITE_CACHE, "CACHE"},
{WRITE_LOCKOWNER, "LOCKOWNER"},
}) })
readFlagNames = newFlagNames(map[int64]string{ readFlagNames = newFlagNames([]flagNameEntry{
READ_LOCKOWNER: "LOCKOWNER", {READ_LOCKOWNER, "LOCKOWNER"},
}) })
initFlagNames = newFlagNames(map[int64]string{ initFlagNames = newFlagNames([]flagNameEntry{
CAP_ASYNC_READ: "ASYNC_READ", {CAP_ASYNC_READ, "ASYNC_READ"},
CAP_POSIX_LOCKS: "POSIX_LOCKS", {CAP_POSIX_LOCKS, "POSIX_LOCKS"},
CAP_FILE_OPS: "FILE_OPS", {CAP_FILE_OPS, "FILE_OPS"},
CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC", {CAP_ATOMIC_O_TRUNC, "ATOMIC_O_TRUNC"},
CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT", {CAP_EXPORT_SUPPORT, "EXPORT_SUPPORT"},
CAP_BIG_WRITES: "BIG_WRITES", {CAP_BIG_WRITES, "BIG_WRITES"},
CAP_DONT_MASK: "DONT_MASK", {CAP_DONT_MASK, "DONT_MASK"},
CAP_SPLICE_WRITE: "SPLICE_WRITE", {CAP_SPLICE_WRITE, "SPLICE_WRITE"},
CAP_SPLICE_MOVE: "SPLICE_MOVE", {CAP_SPLICE_MOVE, "SPLICE_MOVE"},
CAP_SPLICE_READ: "SPLICE_READ", {CAP_SPLICE_READ, "SPLICE_READ"},
CAP_FLOCK_LOCKS: "FLOCK_LOCKS", {CAP_FLOCK_LOCKS, "FLOCK_LOCKS"},
CAP_IOCTL_DIR: "IOCTL_DIR", {CAP_IOCTL_DIR, "IOCTL_DIR"},
CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA", {CAP_AUTO_INVAL_DATA, "AUTO_INVAL_DATA"},
CAP_READDIRPLUS: "READDIRPLUS", {CAP_READDIRPLUS, "READDIRPLUS"},
CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO", {CAP_READDIRPLUS_AUTO, "READDIRPLUS_AUTO"},
CAP_ASYNC_DIO: "ASYNC_DIO", {CAP_ASYNC_DIO, "ASYNC_DIO"},
CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE", {CAP_WRITEBACK_CACHE, "WRITEBACK_CACHE"},
CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT", {CAP_NO_OPEN_SUPPORT, "NO_OPEN_SUPPORT"},
CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS", {CAP_PARALLEL_DIROPS, "PARALLEL_DIROPS"},
CAP_POSIX_ACL: "POSIX_ACL", {CAP_POSIX_ACL, "POSIX_ACL"},
CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV", {CAP_HANDLE_KILLPRIV, "HANDLE_KILLPRIV"},
CAP_ABORT_ERROR: "ABORT_ERROR", {CAP_ABORT_ERROR, "ABORT_ERROR"},
CAP_MAX_PAGES: "MAX_PAGES", {CAP_MAX_PAGES, "MAX_PAGES"},
CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS", {CAP_CACHE_SYMLINKS, "CACHE_SYMLINKS"},
CAP_SECURITY_CTX: "SECURITY_CTX", {CAP_SECURITY_CTX, "SECURITY_CTX"},
CAP_HAS_INODE_DAX: "HAS_INODE_DAX", {CAP_HAS_INODE_DAX, "HAS_INODE_DAX"},
CAP_CREATE_SUPP_GROUP: "CREATE_SUPP_GROUP", {CAP_CREATE_SUPP_GROUP, "CREATE_SUPP_GROUP"},
CAP_HAS_EXPIRE_ONLY: "HAS_EXPIRE_ONLY", {CAP_HAS_EXPIRE_ONLY, "HAS_EXPIRE_ONLY"},
CAP_DIRECT_IO_RELAX: "DIRECT_IO_RELAX", {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{ releaseFlagNames = newFlagNames([]flagNameEntry{
RELEASE_FLUSH: "FLUSH", {RELEASE_FLUSH, "FLUSH"},
}) })
openFlagNames = newFlagNames(map[int64]string{ openFlagNames = newFlagNames([]flagNameEntry{
int64(os.O_WRONLY): "WRONLY", {int64(os.O_WRONLY), "WRONLY"},
int64(os.O_RDWR): "RDWR", {int64(os.O_RDWR), "RDWR"},
int64(os.O_APPEND): "APPEND", {int64(os.O_APPEND), "APPEND"},
int64(syscall.O_ASYNC): "ASYNC", {int64(syscall.O_ASYNC), "ASYNC"},
int64(os.O_CREATE): "CREAT", {int64(os.O_CREATE), "CREAT"},
int64(os.O_EXCL): "EXCL", {int64(os.O_EXCL), "EXCL"},
int64(syscall.O_NOCTTY): "NOCTTY", {int64(syscall.O_NOCTTY), "NOCTTY"},
int64(syscall.O_NONBLOCK): "NONBLOCK", {int64(syscall.O_NONBLOCK), "NONBLOCK"},
int64(os.O_SYNC): "SYNC", {int64(os.O_SYNC), "SYNC"},
int64(os.O_TRUNC): "TRUNC", {int64(os.O_TRUNC), "TRUNC"},
0x8000: "LARGEFILE", {int64(syscall.O_CLOEXEC), "CLOEXEC"},
int64(syscall.O_CLOEXEC): "CLOEXEC", {int64(syscall.O_DIRECTORY), "DIRECTORY"},
int64(syscall.O_DIRECTORY): "DIRECTORY",
}) })
fuseOpenFlagNames = newFlagNames(map[int64]string{ fuseOpenFlagNames = newFlagNames([]flagNameEntry{
FOPEN_DIRECT_IO: "DIRECT", {FOPEN_DIRECT_IO, "DIRECT"},
FOPEN_KEEP_CACHE: "CACHE", {FOPEN_KEEP_CACHE, "CACHE"},
FOPEN_NONSEEKABLE: "NONSEEK", {FOPEN_NONSEEKABLE, "NONSEEK"},
FOPEN_CACHE_DIR: "CACHE_DIR", {FOPEN_CACHE_DIR, "CACHE_DIR"},
FOPEN_STREAM: "STREAM", {FOPEN_STREAM, "STREAM"},
{FOPEN_NOFLUSH, "NOFLUSH"},
{FOPEN_PARALLEL_DIRECT_WRITES, "PARALLEL_DIRECT_WRITES"},
{FOPEN_PASSTHROUGH, "PASSTHROUGH"},
}) })
accessFlagName = newFlagNames(map[int64]string{ accessFlagName = newFlagNames([]flagNameEntry{
X_OK: "x", {X_OK, "x"},
W_OK: "w", {W_OK, "w"},
R_OK: "r", {R_OK, "r"},
}) })
getAttrFlagNames = newFlagNames(map[int64]string{ getAttrFlagNames = newFlagNames([]flagNameEntry{
FUSE_GETATTR_FH: "FH", {FUSE_GETATTR_FH, "FH"},
}) })
) )
...@@ -99,10 +105,10 @@ type flagNameEntry struct { ...@@ -99,10 +105,10 @@ type flagNameEntry struct {
} }
// newFlagNames creates flagNames from flag->name map. // newFlagNames creates flagNames from flag->name map.
func newFlagNames(names map[int64]string) *flagNames { func newFlagNames(names []flagNameEntry) *flagNames {
var v flagNames var v flagNames
for flag, name := range names { for _, e := range names {
v.set(flag, name) v.set(e.bits, e.name)
} }
return &v return &v
} }
...@@ -112,7 +118,7 @@ func (names *flagNames) set(flag int64, name string) { ...@@ -112,7 +118,7 @@ func (names *flagNames) set(flag int64, name string) {
entry := flagNameEntry{bits: flag, name: name} entry := flagNameEntry{bits: flag, name: name}
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
if flag&(1<<i) != 0 { 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)) panic(fmt.Sprintf("%s (%x) overlaps with %s (%x)", name, flag, ie.name, ie.bits))
} }
names[i] = entry names[i] = entry
...@@ -205,22 +211,26 @@ func (in *OpenIn) string() string { ...@@ -205,22 +211,26 @@ func (in *OpenIn) string() string {
} }
func (in *OpenOut) 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), "")) flagString(fuseOpenFlagNames, int64(in.OpenFlags), ""))
} }
func (in *InitIn) string() string { func (in *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra %d %s}", return fmt.Sprintf("{%d.%d Ra %d %s}",
in.Major, in.Minor, in.MaxReadAhead, in.Major, in.Minor, in.MaxReadAhead,
flagString(initFlagNames, int64(in.Flags)|(int64(in.Flags2)<<32), "")) flagString(initFlagNames, int64(in.Flags64()), ""))
} }
func (o *InitOut) string() string { 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, o.Major, o.Minor, o.MaxReadAhead,
flagString(initFlagNames, int64(o.Flags), ""), flagString(initFlagNames, int64(o.Flags64()), ""),
o.CongestionThreshold, o.MaxBackground, o.MaxWrite, o.CongestionThreshold, o.MaxBackground, o.MaxWrite,
o.TimeGran, o.MaxPages) o.TimeGran, o.MaxPages, o.MaxStackDepth)
} }
func (s *FsyncIn) string() string { func (s *FsyncIn) string() string {
...@@ -269,7 +279,7 @@ func (o *EntryOut) string() string { ...@@ -269,7 +279,7 @@ func (o *EntryOut) string() string {
} }
func (o *CreateOut) 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 { func (o *StatfsOut) string() string {
...@@ -352,3 +362,22 @@ func Print(obj interface{}) string { ...@@ -352,3 +362,22 @@ func Print(obj interface{}) string {
} }
return fmt.Sprintf("%T: %v", obj, obj) 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() { ...@@ -19,21 +19,6 @@ func init() {
initFlagNames.set(CAP_CASE_INSENSITIVE, "CASE_INSENSITIVE") 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 { func (me *CreateIn) string() string {
return fmt.Sprintf( return fmt.Sprintf(
"{0%o [%s]}", me.Mode, "{0%o [%s]}", me.Mode,
......
package fuse
func init() {
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
}
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
package fuse package fuse
import ( import (
"fmt" "runtime"
"strings"
"syscall" "syscall"
) )
func init() { 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_DIRECT, "DIRECT")
openFlagNames.set(syscall.O_LARGEFILE, "LARGEFILE")
openFlagNames.set(syscall_O_NOATIME, "NOATIME") openFlagNames.set(syscall_O_NOATIME, "NOATIME")
initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT") initFlagNames.set(CAP_NO_OPENDIR_SUPPORT, "NO_OPENDIR_SUPPORT")
initFlagNames.set(CAP_EXPLICIT_INVAL_DATA, "EXPLICIT_INVAL_DATA") initFlagNames.set(CAP_EXPLICIT_INVAL_DATA, "EXPLICIT_INVAL_DATA")
...@@ -22,48 +29,3 @@ func init() { ...@@ -22,48 +29,3 @@ func init() {
initFlagNames.set(CAP_INIT_EXT, "INIT_EXT") initFlagNames.set(CAP_INIT_EXT, "INIT_EXT")
initFlagNames.set(CAP_INIT_RESERVED, "INIT_RESERVED") 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 ( ...@@ -8,6 +8,10 @@ import (
"testing" "testing"
) )
func init() {
isTest = true
}
// verify that flagString always formats flags in the same order. // verify that flagString always formats flags in the same order.
func TestFlagStringOrder(t *testing.T) { func TestFlagStringOrder(t *testing.T) {
var flags int64 = CAP_ASYNC_READ | CAP_SPLICE_WRITE | CAP_READDIRPLUS | CAP_MAX_PAGES | CAP_EXPLICIT_INVAL_DATA 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) { ...@@ -23,10 +27,10 @@ func TestFlagStringOrder(t *testing.T) {
// verify how flagString handles provided default. // verify how flagString handles provided default.
func TestFlagStringDefault(t *testing.T) { func TestFlagStringDefault(t *testing.T) {
names := newFlagNames(map[int64]string{ names := newFlagNames([]flagNameEntry{
1: "AAA", {1, "AAA"},
2: "BBB", {2, "BBB"},
4: "CCC", {4, "CCC"},
}) })
testv := []struct { 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 { ...@@ -103,9 +103,9 @@ func (r *request) InputDebug() string {
names += fmt.Sprintf("%s %db", data, len(r.arg)) 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, r.inHeader.Unique, operationName(r.inHeader.Opcode), r.inHeader.NodeId,
val, names) val, names, r.inHeader.Caller.Pid)
} }
func (r *request) OutputDebug() string { 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 { ...@@ -76,6 +76,7 @@ type Server struct {
singleReader bool singleReader bool
canSplice bool canSplice bool
loops sync.WaitGroup loops sync.WaitGroup
serving bool // for preventing duplicate Serve() calls
ready chan error ready chan error
...@@ -199,10 +200,7 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -199,10 +200,7 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
opts: &o, opts: &o,
maxReaders: maxReaders, maxReaders: maxReaders,
retrieveTab: make(map[uint64]*retrieveCacheRequest), retrieveTab: make(map[uint64]*retrieveCacheRequest),
// OSX has races when multiple routines read from the singleReader: useSingleReader,
// FUSE device: on unmount, sometime some reads do not
// error-out, meaning that unmount will hang.
singleReader: runtime.GOOS == "darwin",
ready: make(chan error, 1), ready: make(chan error, 1),
} }
ms.reqPool.New = func() interface{} { ms.reqPool.New = func() interface{} {
...@@ -419,6 +417,14 @@ func (ms *Server) recordStats(req *request) { ...@@ -419,6 +417,14 @@ func (ms *Server) recordStats(req *request) {
// //
// Each filesystem operation executes in a separate goroutine. // Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() { 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.loop(false)
ms.loops.Wait() ms.loops.Wait()
...@@ -471,6 +477,33 @@ func (ms *Server) handleInit() Status { ...@@ -471,6 +477,33 @@ func (ms *Server) handleInit() Status {
return OK 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) { func (ms *Server) loop(exitIdle bool) {
defer ms.loops.Done() defer ms.loops.Done()
exit: exit:
...@@ -529,15 +562,22 @@ func (ms *Server) handleRequest(req *request) Status { ...@@ -529,15 +562,22 @@ func (ms *Server) handleRequest(req *request) Status {
errNo := ms.write(req) errNo := ms.write(req)
if errNo != 0 { if errNo != 0 {
// Unless debugging is enabled, ignore ENOENT for INTERRUPT responses // Ignore ENOENT for INTERRUPT responses which
// which indicates that the referred request is no longer known by the // indicates that the referred request is no longer
// kernel. This is a normal if the referred request already has // known by the kernel. This is a normal if the
// completed. // referred request already has completed.
if ms.opts.Debug || !(req.inHeader.Opcode == _OP_INTERRUPT && errNo == ENOENT) { //
// 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", ms.opts.Logger.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode)) errNo, operationName(req.inHeader.Opcode))
} }
} }
ms.returnRequest(req) ms.returnRequest(req)
return Status(errNo) return Status(errNo)
......
...@@ -8,6 +8,8 @@ import ( ...@@ -8,6 +8,8 @@ import (
"syscall" "syscall"
) )
const useSingleReader = false
func (ms *Server) systemWrite(req *request, header []byte) Status { func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 { if req.flatDataSize() == 0 {
err := handleEINTR(func() error { err := handleEINTR(func() error {
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved. //go:build !linux
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fuse package fuse
import ( 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 { func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 { if req.flatDataSize() == 0 {
err := handleEINTR(func() error { err := handleEINTR(func() error {
_, err := syscall.Write(ms.mountFd, header) _, err := unix.Write(ms.mountFd, header)
return err return err
}) })
return ToStatus(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 ...@@ -6,49 +6,10 @@ package fuse
import ( import (
"bytes" "bytes"
"os"
"syscall" "syscall"
"unsafe" "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) { func getxattr(path string, attr string, dest []byte) (sz int, errno int) {
pathBs := syscall.StringBytePtr(path) pathBs := syscall.StringBytePtr(path)
attrBs := syscall.StringBytePtr(attr) attrBs := syscall.StringBytePtr(attr)
......
...@@ -5,45 +5,10 @@ ...@@ -5,45 +5,10 @@
package fuse package fuse
import ( import (
"os" "golang.org/x/sys/unix"
"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 n, err
}
func writev(fd int, packet [][]byte) (n int, err error) { func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet)) n, err = unix.Writev(fd, packet)
return
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
} }
//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 ( ...@@ -44,9 +44,6 @@ const (
// ENOSYS Function not implemented // ENOSYS Function not implemented
ENOSYS = Status(syscall.ENOSYS) ENOSYS = Status(syscall.ENOSYS)
// ENODATA No data available
ENODATA = Status(syscall.ENODATA)
// ENOTDIR Not a directory // ENOTDIR Not a directory
ENOTDIR = Status(syscall.ENOTDIR) ENOTDIR = Status(syscall.ENOTDIR)
...@@ -259,12 +256,13 @@ const ( ...@@ -259,12 +256,13 @@ const (
FOPEN_STREAM = (1 << 4) FOPEN_STREAM = (1 << 4)
FOPEN_NOFLUSH = (1 << 5) FOPEN_NOFLUSH = (1 << 5)
FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6) FOPEN_PARALLEL_DIRECT_WRITES = (1 << 6)
FOPEN_PASSTHROUGH = (1 << 7)
) )
type OpenOut struct { type OpenOut struct {
Fh uint64 Fh uint64
OpenFlags uint32 OpenFlags uint32
Padding uint32 BackingID int32
} }
// To be set in InitIn/InitOut.Flags. // To be set in InitIn/InitOut.Flags.
...@@ -301,12 +299,16 @@ const ( ...@@ -301,12 +299,16 @@ const (
CAP_MAX_PAGES = (1 << 22) CAP_MAX_PAGES = (1 << 22)
CAP_CACHE_SYMLINKS = (1 << 23) 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 */ /* bits 32..63 get shifted down 32 bits into the Flags2 field */
CAP_SECURITY_CTX = (1 << 32) CAP_SECURITY_CTX = (1 << 32)
CAP_HAS_INODE_DAX = (1 << 33) CAP_HAS_INODE_DAX = (1 << 33)
CAP_CREATE_SUPP_GROUP = (1 << 34) CAP_CREATE_SUPP_GROUP = (1 << 34)
CAP_HAS_EXPIRE_ONLY = (1 << 35) CAP_HAS_EXPIRE_ONLY = (1 << 35)
CAP_DIRECT_IO_RELAX = (1 << 36) 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 { type InitIn struct {
...@@ -320,6 +322,10 @@ type InitIn struct { ...@@ -320,6 +322,10 @@ type InitIn struct {
Unused [11]uint32 Unused [11]uint32
} }
func (i *InitIn) Flags64() uint64 {
return uint64(i.Flags) | uint64(i.Flags2)<<32
}
type InitOut struct { type InitOut struct {
Major uint32 Major uint32
Minor uint32 Minor uint32
...@@ -332,7 +338,12 @@ type InitOut struct { ...@@ -332,7 +338,12 @@ type InitOut struct {
MaxPages uint16 MaxPages uint16
Padding uint16 Padding uint16
Flags2 uint32 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 { type _CuseInitIn struct {
...@@ -519,6 +530,7 @@ const ( ...@@ -519,6 +530,7 @@ const (
NOTIFY_STORE_CACHE = -4 // store data into kernel cache of an inode 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_RETRIEVE_CACHE = -5 // retrieve data from kernel cache of an inode
NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted
NOTIFY_RESEND = -7
// NOTIFY_CODE_MAX = -6 // NOTIFY_CODE_MAX = -6
) )
...@@ -693,3 +705,79 @@ func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) { ...@@ -693,3 +705,79 @@ func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) {
} }
lk.Pid = uint32(flockT.Pid) 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 ( ...@@ -9,6 +9,7 @@ import (
) )
const ( const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS. ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS.
// EREMOTEIO is not supported on Darwin. // EREMOTEIO is not supported on Darwin.
...@@ -61,73 +62,6 @@ const ( ...@@ -61,73 +62,6 @@ const (
FOPEN_PURGE_UBC = (1 << 31) 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 { type SetXAttrIn struct {
InHeader InHeader
Size uint32 Size uint32
...@@ -191,3 +125,7 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { ...@@ -191,3 +125,7 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Bavail /= adj 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 ( ...@@ -9,6 +9,7 @@ import (
) )
const ( const (
ENODATA = Status(syscall.ENODATA)
ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA. ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA.
// EREMOTEIO Remote I/O error // EREMOTEIO Remote I/O error
...@@ -22,6 +23,7 @@ const ( ...@@ -22,6 +23,7 @@ const (
const ( const (
CAP_NO_OPENDIR_SUPPORT = (1 << 24) CAP_NO_OPENDIR_SUPPORT = (1 << 24)
CAP_EXPLICIT_INVAL_DATA = (1 << 25) CAP_EXPLICIT_INVAL_DATA = (1 << 25)
CAP_MAP_ALIGNMENT = (1 << 26) CAP_MAP_ALIGNMENT = (1 << 26)
CAP_SUBMOUNTS = (1 << 27) CAP_SUBMOUNTS = (1 << 27)
CAP_HANDLE_KILLPRIV_V2 = (1 << 28) CAP_HANDLE_KILLPRIV_V2 = (1 << 28)
...@@ -33,113 +35,6 @@ const ( ...@@ -33,113 +35,6 @@ const (
CAP_RENAME_SWAP = 0x0 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) { func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Blocks = statfs.Blocks s.Blocks = statfs.Blocks
s.Bsize = uint32(statfs.Bsize) s.Bsize = uint32(statfs.Bsize)
...@@ -150,3 +45,8 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { ...@@ -150,3 +45,8 @@ func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) {
s.Frsize = uint32(statfs.Frsize) s.Frsize = uint32(statfs.Frsize)
s.NameLen = uint32(statfs.Namelen) 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 ( ...@@ -7,4 +7,4 @@ require (
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a 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 ...@@ -3,6 +3,7 @@ package renameat
// Renameat is a wrapper around renameat syscall. // Renameat is a wrapper around renameat syscall.
// On Linux, it is a wrapper around renameat2(2). // On Linux, it is a wrapper around renameat2(2).
// On Darwin, it is a wrapper around renameatx_np(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) { func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return renameat(olddirfd, oldpath, newdirfd, newpath, flags) 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 ( ...@@ -17,6 +17,7 @@ import (
"testing" "testing"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/fallocate"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
...@@ -47,6 +48,32 @@ var All = map[string]func(*testing.T, string){ ...@@ -47,6 +48,32 @@ var All = map[string]func(*testing.T, string){
"DirSeek": DirSeek, "DirSeek": DirSeek,
"FcntlFlockSetLk": FcntlFlockSetLk, "FcntlFlockSetLk": FcntlFlockSetLk,
"FcntlFlockLocksFile": FcntlFlockLocksFile, "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) { func DirectIO(t *testing.T, mnt string) {
...@@ -272,7 +299,7 @@ func FstatDeleted(t *testing.T, mnt string) { ...@@ -272,7 +299,7 @@ func FstatDeleted(t *testing.T, mnt string) {
const iMax = 9 const iMax = 9
type file struct { type file struct {
fd int fd int
st syscall.Stat_t st unix.Stat_t
} }
files := make(map[int]file) files := make(map[int]file)
for i := 0; i <= iMax; i++ { for i := 0; i <= iMax; i++ {
...@@ -283,8 +310,8 @@ func FstatDeleted(t *testing.T, mnt string) { ...@@ -283,8 +310,8 @@ func FstatDeleted(t *testing.T, mnt string) {
if err != nil { if err != nil {
t.Fatalf("WriteFile: %v", err) t.Fatalf("WriteFile: %v", err)
} }
var st syscall.Stat_t var st unix.Stat_t
err = syscall.Stat(path, &st) err = unix.Stat(path, &st)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -303,14 +330,14 @@ func FstatDeleted(t *testing.T, mnt string) { ...@@ -303,14 +330,14 @@ func FstatDeleted(t *testing.T, mnt string) {
} }
// Fstat in random order // Fstat in random order
for _, v := range files { for _, v := range files {
var st syscall.Stat_t var st unix.Stat_t
err := syscall.Fstat(v.fd, &st) err := unix.Fstat(v.fd, &st)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Ignore ctime, changes on unlink // Ignore ctime, changes on unlink
v.st.Ctim = syscall.Timespec{} v.st.Ctim = unix.Timespec{}
st.Ctim = syscall.Timespec{} st.Ctim = unix.Timespec{}
// Nlink value should have dropped to zero // Nlink value should have dropped to zero
v.st.Nlink = 0 v.st.Nlink = 0
// Rest should stay the same // Rest should stay the same
...@@ -607,7 +634,7 @@ func OpenAt(t *testing.T, mnt string) { ...@@ -607,7 +634,7 @@ func OpenAt(t *testing.T, mnt string) {
if err != nil { if err != nil {
t.Fatal(err) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -624,7 +651,7 @@ func Fallocate(t *testing.T, mnt string) { ...@@ -624,7 +651,7 @@ func Fallocate(t *testing.T, mnt string) {
t.Fatalf("OpenFile failed: %v", err) t.Fatalf("OpenFile failed: %v", err)
} }
defer rwFile.Close() defer rwFile.Close()
err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096) err = fallocate.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
if err != nil { if err != nil {
t.Fatalf("Fallocate failed: %v", err) t.Fatalf("Fallocate failed: %v", err)
} }
...@@ -661,12 +688,11 @@ func FcntlFlockSetLk(t *testing.T, mnt string) { ...@@ -661,12 +688,11 @@ func FcntlFlockSetLk(t *testing.T, mnt string) {
} }
defer f2.Close() defer f2.Close()
lk := syscall.Flock_t{} 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) t.Errorf("FcntlFlock failed: %v", err)
} }
if lk.Type != syscall.F_WRLCK { if lk.Type != syscall.F_WRLCK {
t.Errorf("got lk.Type=%v, want %v", 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 ( ...@@ -11,6 +11,10 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type dirent struct {
unix.Dirent
}
// DirSeek tests that seeking on a directory works for // DirSeek tests that seeking on a directory works for
// https://github.com/hanwen/go-fuse/issues/344 . // https://github.com/hanwen/go-fuse/issues/344 .
// //
...@@ -47,7 +51,7 @@ func DirSeek(t *testing.T, mnt string) { ...@@ -47,7 +51,7 @@ func DirSeek(t *testing.T, mnt string) {
total := 0 total := 0
for { for {
n, err := unix.Getdents(fd, buf) n, err := unix.ReadDirent(fd, buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -55,24 +59,24 @@ func DirSeek(t *testing.T, mnt string) { ...@@ -55,24 +59,24 @@ func DirSeek(t *testing.T, mnt string) {
break break
} }
for bpos := 0; bpos < n; total++ { for bpos := 0; bpos < n; total++ {
d := (*unix.Dirent)(unsafe.Pointer(&buf[bpos])) d := (*dirent)(unsafe.Pointer(&buf[bpos]))
if total > historyLen { if total > historyLen {
t.Fatal("too many files") t.Fatal("too many files")
} }
for i := 0; i < total; i++ { 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", t.Errorf("entries %d and %d gave duplicate d.Off %d",
i, total, d.Off) i, total, d.off())
} }
} }
offHistory[total] = d.Off offHistory[total] = d.off()
inoHistory[total] = d.Ino inoHistory[total] = d.ino()
bpos += int(d.Reclen) bpos += int(d.Reclen)
} }
} }
// check if seek works correctly // 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-- { for i := total - 1; i >= 0; i-- {
var seekTo int64 var seekTo int64
if i > 0 { if i > 0 {
...@@ -83,7 +87,7 @@ func DirSeek(t *testing.T, mnt string) { ...@@ -83,7 +87,7 @@ func DirSeek(t *testing.T, mnt string) {
t.Fatal(err) t.Fatal(err)
} }
n, err := unix.Getdents(fd, buf) n, err := unix.ReadDirent(fd, buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -92,9 +96,9 @@ func DirSeek(t *testing.T, mnt string) { ...@@ -92,9 +96,9 @@ func DirSeek(t *testing.T, mnt string) {
continue continue
} }
if d.Ino != inoHistory[i] { if d.ino() != inoHistory[i] {
t.Errorf("entry %d has inode %d, expected %d", 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. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
......
//go:build linux
// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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