Commit eb4d413d authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into y/nodefs-cancel

Notable patches:

- 265a3926 "fuse: Increase MAX_KERNEL_WRITE to 1 MiB & enable CAP_MAX_PAGES"

  this should affect WCFS performance significantly because previously data was
  read from WCFS server in 128KB chunks and now it could be read in 2MB.

  For the reference: WCFS was setting MaxWrite=2MB from the beginning, and
  previously it was capped to 128KB by go-fuse. Now with go-fuse upgrade we
  should hopefully automatically get increase in performance.

- 90b055af "fusermount: Fix option escaping"

  Levin's patch to allow ',' to be present in fsname, so that we can mount
  neo:// with multiple masters.

  See nexedi/wendelin.core!15 for context
  and details.

* master: (45 commits)
  fs: kill subprocess before tearing down test
  fuse/test: Fix TestFopenKeepCache, take 2
  fs: simplify TestReadDirStress
  fuse: tweak Unmount doc comment
  .github: add Go 1.20
  fuse/mount_linux_test/DirectMount: Verify FsName \w comma/backslash works
  fusermount: Fix option escaping
  fuse: fix debug print for FsyncDir
  fuse: print GETATTR flags in input
  fs: document FileHandle argument for Getattr
  README.md: tweak & polish
  Drop "// " from LICENSE
  fs: fix NodeLookuper documentation
  .github: set user_allow_other in /etc/fuse.conf
  .github: drop Go 1.13 / 1.15, add Go 1.18/1.19
  fuse: MountDirect: always pass max_read
  tests: TestDirectMount: better coverage, clearer output
  fs: document Readdir determinism requirement
  fs: fold duplicate fuse.Context{} instantiations
  newunionfs: make readdir deterministic
  ...
parents 6e5a2cc2 255ab741
...@@ -11,10 +11,11 @@ jobs: ...@@ -11,10 +11,11 @@ jobs:
strategy: strategy:
matrix: matrix:
go: go:
- "1.13.x" # Ubuntu 20.04 LTS "focal"
- "1.15.x" # Debian 11 "Bullseye"
- "1.16.x" # Golang upstream stable - "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.19.x" # Golang upstream stable
- "1.20.x" # Golang upstream stable
# Don't cancel everything when one Go version fails # Don't cancel everything when one Go version fails
fail-fast: false fail-fast: false
runs-on: ubuntu-latest runs-on: ubuntu-latest
...@@ -31,6 +32,7 @@ jobs: ...@@ -31,6 +32,7 @@ jobs:
# CI platform specific setup steps happen here # CI platform specific setup steps happen here
- run: sudo apt-get install -qq fuse3 libssl-dev libfuse-dev - run: sudo apt-get install -qq fuse3 libssl-dev libfuse-dev
- run: echo user_allow_other | sudo tee -a /etc/fuse.conf
# Actual test steps are in all.bash # Actual test steps are in all.bash
- run: ./all.bash - run: ./all.bash
// New BSD License New BSD License
//
// Copyright (c) 2010 the Go-FUSE Authors. All rights reserved. Copyright (c) 2010 the Go-FUSE Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
// met: met:
//
// * Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
// distribution. distribution.
// * Neither the name of Ivan Krasin nor the names of its * Neither the name of Ivan Krasin nor the names of its
// contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
// this software without specific prior written permission. this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
# GO-FUSE # Go-FUSE
[![CI](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml/badge.svg)](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml) [![CI](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml/badge.svg)](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml)
[![GoDoc](https://godoc.org/github.com/hanwen/go-fuse?status.svg)](https://godoc.org/github.com/hanwen/go-fuse/v2) [![GoDoc](https://godoc.org/github.com/hanwen/go-fuse/v2/fs?status.svg)](https://godoc.org/github.com/hanwen/go-fuse/v2/fs)
Go native bindings for the FUSE kernel module. Go native bindings for the FUSE kernel module.
...@@ -20,7 +20,7 @@ and ...@@ -20,7 +20,7 @@ and
The FUSE library gained a new, cleaned-up API during a rewrite The FUSE library gained a new, cleaned-up API during a rewrite
completed in 2019. Find extensive documentation completed in 2019. Find extensive documentation
[here](https://godoc.org/github.com/hanwen/go-fuse/v2). [here](https://godoc.org/github.com/hanwen/go-fuse/v2/fs).
Further highlights of this library is Further highlights of this library is
...@@ -31,9 +31,9 @@ Further highlights of this library is ...@@ -31,9 +31,9 @@ Further highlights of this library is
## Examples ## Examples
* `example/hello/main.go` contains a 60-line "hello world" filesystem * [example/hello/](example/hello/main.go) contains a 60-line "hello world" filesystem
* `zipfs/zipfs.go` contains a small and simple read-only filesystem for * [zipfs/zipfs](zipfs/zipfs.go) contains a small and simple read-only filesystem for
zip and tar files. The corresponding command is in example/zipfs/ zip and tar files. The corresponding command is in example/zipfs/
For example, For example,
...@@ -44,10 +44,10 @@ Further highlights of this library is ...@@ -44,10 +44,10 @@ Further highlights of this library is
fusermount -u /tmp/mountpoint fusermount -u /tmp/mountpoint
```` ````
* `zipfs/multizipfs.go` shows how to use in-process mounts to * [zipfs/multizipfs](zipfs/multizipfs.go) shows how to use combine
combine multiple Go-FUSE filesystems into a larger filesystem. simple Go-FUSE filesystems into a larger filesystem.
* `fuse/loopback.go` mounts another piece of the filesystem. * [example/loopback](example/loopback/main.go) mounts another piece of the filesystem.
Functionally, it is similar to a symlink. A binary to run is in Functionally, it is similar to a symlink. A binary to run is in
example/loopback/ . For example example/loopback/ . For example
...@@ -60,7 +60,7 @@ Further highlights of this library is ...@@ -60,7 +60,7 @@ Further highlights of this library is
## macOS Support ## macOS Support
go-fuse works somewhat on OSX. Known limitations: Go-FUSE works somewhat on OSX. Known limitations:
* All of the limitations of OSXFUSE, including lack of support for * All of the limitations of OSXFUSE, including lack of support for
NOTIFY. NOTIFY.
...@@ -82,18 +82,13 @@ go-fuse works somewhat on OSX. Known limitations: ...@@ -82,18 +82,13 @@ go-fuse works somewhat on OSX. Known limitations:
## Bugs ## Bugs
Yes, probably. Report them through Yes, probably. Report them through
https://github.com/hanwen/go-fuse/issues https://github.com/hanwen/go-fuse/issues. Please include a debug trace
(set `fuse.MountOptions.Debug` to `true`).
## Disclaimer ## Disclaimer
This is not an official Google product. This is not an official Google product.
## Known Problems
Grep source code for TODO. Major topics:
* Missing support for `CUSE`, `BMAP`, `IOCTL`
## License ## License
Like Go, this library is distributed under the new BSD license. See Like Go, this library is distributed under the new BSD license. See
......
...@@ -13,7 +13,11 @@ GOOS=darwin go build ./fuse/... ./fs/... ./example/loopback/... ...@@ -13,7 +13,11 @@ GOOS=darwin go build ./fuse/... ./fs/... ./example/loopback/...
# -p 1 .......... Run tests serially, which also means we get live output # -p 1 .......... Run tests serially, which also means we get live output
# instead of per-package buffering. # instead of per-package buffering.
# -count 1 ...... Disable result caching, so we can see flakey tests # -count 1 ...... Disable result caching, so we can see flakey tests
go test -timeout 5m -p 1 -count 1 ./... GO_TEST="go test -timeout 5m -p 1 -count 1"
# Run all tests as current user
$GO_TEST ./...
# Direct-mount tests need to run as root
sudo env PATH=$PATH $GO_TEST -run TestDirectMount ./fs ./fuse
make -C benchmark make -C benchmark
go test ./benchmark -test.bench '.*' -test.cpu 1,2 go test ./benchmark -test.bench '.*' -test.cpu 1,2
...@@ -25,7 +25,10 @@ import ( ...@@ -25,7 +25,10 @@ import (
func setupFs(node fs.InodeEmbedder, N int) (string, func()) { func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
opts := &fs.Options{} opts := &fs.Options{}
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
mountPoint := testutil.TempDir() mountPoint, err := os.MkdirTemp("", "")
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) log.Panicf("cannot mount %v", err)
...@@ -53,8 +56,6 @@ func setupFs(node fs.InodeEmbedder, N int) (string, func()) { ...@@ -53,8 +56,6 @@ func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
err := server.Unmount() err := server.Unmount()
if err != nil { if err != nil {
log.Println("error during unmount", err) log.Println("error during unmount", err)
} else {
os.RemoveAll(mountPoint)
} }
} }
} }
...@@ -251,8 +252,10 @@ func BenchmarkCFuseThreadedStat(b *testing.B) { ...@@ -251,8 +252,10 @@ func BenchmarkCFuseThreadedStat(b *testing.B) {
} }
f.Close() f.Close()
mountPoint := testutil.TempDir() mountPoint, err := os.MkdirTemp("", "")
defer os.RemoveAll(mountPoint) if err != nil {
b.Fatalf("MkdirTemp: %v", err)
}
cmd := exec.Command(wd+"/cstatfs", cmd := exec.Command(wd+"/cstatfs",
"-o", "-o",
......
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"time" "time"
"github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
) )
func writeMemProfile(fn string, sigs <-chan os.Signal) { func writeMemProfile(fn string, sigs <-chan os.Signal) {
...@@ -47,6 +48,8 @@ func main() { ...@@ -47,6 +48,8 @@ func main() {
other := flag.Bool("allow-other", false, "mount with -o allowother.") other := flag.Bool("allow-other", false, "mount with -o allowother.")
quiet := flag.Bool("q", false, "quiet") quiet := flag.Bool("q", false, "quiet")
ro := flag.Bool("ro", false, "mount read-only") ro := flag.Bool("ro", false, "mount read-only")
directmount := flag.Bool("directmount", false, "try to call the mount syscall instead of executing fusermount")
directmountstrict := flag.Bool("directmountstrict", false, "like directmount, but don't fall back to fusermount")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file") cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file") memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse() flag.Parse()
...@@ -90,13 +93,22 @@ func main() { ...@@ -90,13 +93,22 @@ func main() {
sec := time.Second sec := time.Second
opts := &fs.Options{ opts := &fs.Options{
// These options are to be compatible with libfuse defaults, // The timeout options are to be compatible with libfuse defaults,
// making benchmarking easier. // making benchmarking easier.
AttrTimeout: &sec, AttrTimeout: &sec,
EntryTimeout: &sec, EntryTimeout: &sec,
NullPermissions: true, // Leave file permissions on "000" files as-is
MountOptions: fuse.MountOptions{
AllowOther: *other,
Debug: *debug,
DirectMount: *directmount,
DirectMountStrict: *directmountstrict,
FsName: orig, // First column in "df -T": original dir
Name: "loopback", // Second column in "df -T" will be shown as "fuse." + Name
},
} }
opts.Debug = *debug
opts.AllowOther = *other
if opts.AllowOther { if opts.AllowOther {
// Make the kernel check file permissions for us // Make the kernel check file permissions for us
opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions") opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions")
...@@ -104,12 +116,6 @@ func main() { ...@@ -104,12 +116,6 @@ func main() {
if *ro { if *ro {
opts.MountOptions.Options = append(opts.MountOptions.Options, "ro") opts.MountOptions.Options = append(opts.MountOptions.Options, "ro")
} }
// First column in "df -T": original dir
opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig)
// Second column in "df -T" will be shown as "fuse." + Name
opts.MountOptions.Name = "loopback"
// Leave file permissions on "000" files as-is
opts.NullPermissions = true
// Enable diagnostics logging // Enable diagnostics logging
if !*quiet { if !*quiet {
opts.Logger = log.New(os.Stderr, "", 0) opts.Logger = log.New(os.Stderr, "", 0)
...@@ -121,5 +127,13 @@ func main() { ...@@ -121,5 +127,13 @@ func main() {
if !*quiet { if !*quiet {
fmt.Println("Mounted!") fmt.Println("Mounted!")
} }
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
server.Unmount()
}()
server.Wait() server.Wait()
} }
...@@ -4,43 +4,45 @@ ...@@ -4,43 +4,45 @@
// Package fs provides infrastructure to build tree-organized filesystems. // Package fs provides infrastructure to build tree-organized filesystems.
// //
// Structure of a file system implementation // # Structure of a file system implementation
// //
// 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 { // struct myNode {
// fs.Inode // fs.Inode
// } // }
// //
// // Node types must be InodeEmbedders // // Node types must be InodeEmbedders
// var _ = (fs.InodeEmbedder)((*myNode)(nil)) // var _ = (fs.InodeEmbedder)((*myNode)(nil))
// //
// // Node types should implement some file system operations, eg. Lookup // // Node types should implement some file system operations, eg. Lookup
// var _ = (fs.NodeLookuper)((*myNode)(nil)) // var _ = (fs.NodeLookuper)((*myNode)(nil))
// //
// func (n *myNode) Lookup(ctx context.Context, name string, ... ) (*Inode, syscall.Errno) { // func (n *myNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
// ops := myNode{} // ops := myNode{}
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFDIR}), 0 // out.Mode = 0755
// } // out.Size = 42
// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
// }
// //
// The method names are inspired on the system call names, so we have // The method names are inspired on the system call names, so we have
// Listxattr rather than ListXAttr. // Listxattr rather than ListXAttr.
// //
// the file system is mounted by calling mount on the root of the tree, // the file system is mounted by calling mount on the root of the tree,
// //
// server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{}) // server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{})
// .. // ..
// // start serving the file system // // start serving the file system
// server.Wait() // server.Wait()
// //
// Error handling // # Error handling
// //
// All error reporting must use the syscall.Errno type. This is an // All error reporting must use the syscall.Errno type. This is an
// integer with predefined error codes, where the value 0 (`OK`) // integer with predefined error codes, where the value 0 (`OK`)
// should be used to indicate success. // should be used to indicate success.
// //
// File system concepts // # File system concepts
// //
// The FUSE API is very similar to Linux' internal VFS API for // The FUSE API is very similar to Linux' internal VFS API for
// defining file systems in the kernel. It is therefore useful to // defining file systems in the kernel. It is therefore useful to
...@@ -58,11 +60,11 @@ ...@@ -58,11 +60,11 @@
// There can be several paths leading from tree root to a particular node, // There can be several paths leading from tree root to a particular node,
// known as hard-linking, for example // known as hard-linking, for example
// //
// root // root
// / \ // / \
// dir1 dir2 // dir1 dir2
// \ / // \ /
// file // file
// //
// Inode: ("index node") points to the file content, and stores // Inode: ("index node") points to the file content, and stores
// metadata (size, timestamps) about a file or directory. Each // metadata (size, timestamps) about a file or directory. Each
...@@ -87,8 +89,7 @@ ...@@ -87,8 +89,7 @@
// Go-FUSE, but the result of Lookup operation essentially is a // Go-FUSE, but the result of Lookup operation essentially is a
// dirent, which the kernel puts in a cache. // dirent, which the kernel puts in a cache.
// //
// // # Kernel caching
// Kernel caching
// //
// The kernel caches several pieces of information from the FUSE process: // The kernel caches several pieces of information from the FUSE process:
// //
...@@ -117,19 +118,19 @@ ...@@ -117,19 +118,19 @@
// entries. by default. This can be achieve in go-fuse by setting // entries. by default. This can be achieve in go-fuse by setting
// options on mount, eg. // options on mount, eg.
// //
// sec := time.Second // sec := time.Second
// opts := fs.Options{ // opts := fs.Options{
// EntryTimeout: &sec, // EntryTimeout: &sec,
// AttrTimeout: &sec, // AttrTimeout: &sec,
// } // }
// //
// Locking // # Locking
// //
// Locks for networked filesystems are supported through the suite of // Locks for networked filesystems are supported through the suite of
// Getlk, Setlk and Setlkw methods. They alllow locks on regions of // Getlk, Setlk and Setlkw methods. They alllow locks on regions of
// regular files. // regular files.
// //
// Parallelism // # Parallelism
// //
// The VFS layer in the kernel is optimized to be highly parallel, and // The VFS layer in the kernel is optimized to be highly parallel, and
// this parallelism also affects FUSE file systems: many FUSE // this parallelism also affects FUSE file systems: many FUSE
...@@ -138,7 +139,7 @@ ...@@ -138,7 +139,7 @@
// system issuing file operations in parallel, and using the race // system issuing file operations in parallel, and using the race
// detector to weed out data races. // detector to weed out data races.
// //
// Dynamically discovered file systems // # Dynamically discovered file systems
// //
// File system data usually cannot fit all in RAM, so the kernel must // File system data usually cannot fit all in RAM, so the kernel must
// discover the file system dynamically: as you are entering and list // discover the file system dynamically: as you are entering and list
...@@ -151,7 +152,7 @@ ...@@ -151,7 +152,7 @@
// individual children of directories, and 2. Readdir, part of the // individual children of directories, and 2. Readdir, part of the
// NodeReaddirer interface for listing the contents of a directory. // NodeReaddirer interface for listing the contents of a directory.
// //
// Static in-memory file systems // # Static in-memory file systems
// //
// For small, read-only file systems, getting the locking mechanics of // For small, read-only file systems, getting the locking mechanics of
// Lookup correct is tedious, so Go-FUSE provides a feature to // Lookup correct is tedious, so Go-FUSE provides a feature to
...@@ -224,7 +225,10 @@ type NodeAccesser interface { ...@@ -224,7 +225,10 @@ type NodeAccesser interface {
// returning zeroed permissions, the default behavior is to change the // returning zeroed permissions, the default behavior is to change the
// mode of 0755 (directory) or 0644 (files). This can be switched off // mode of 0755 (directory) or 0644 (files). This can be switched off
// with the Options.NullPermissions setting. If blksize is unset, 4096 // with the Options.NullPermissions setting. If blksize is unset, 4096
// is assumed, and the 'blocks' field is set accordingly. // is assumed, and the 'blocks' field is set accordingly. The 'f'
// argument is provided for consistency, however, in practice the
// kernel never sends a file handle, even if the Getattr call
// originated from a fstat system call.
type NodeGetattrer interface { type NodeGetattrer interface {
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
} }
...@@ -400,8 +404,6 @@ type DirStream interface { ...@@ -400,8 +404,6 @@ type DirStream interface {
// example, the Symlink, Create, Mknod, Link methods all create new // example, the Symlink, Create, Mknod, Link methods all create new
// children in directories. Hence, they also return *Inode and must // children in directories. Hence, they also return *Inode and must
// populate their fuse.EntryOut arguments. // populate their fuse.EntryOut arguments.
//
type NodeLookuper interface { type NodeLookuper interface {
Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
} }
...@@ -420,7 +422,10 @@ type NodeOpendirer interface { ...@@ -420,7 +422,10 @@ type NodeOpendirer interface {
// for Readdir to return different results from Lookup. For example, // for Readdir to return different results from Lookup. For example,
// you can return nothing for Readdir ("ls my-fuse-mount" is empty), // you can return nothing for Readdir ("ls my-fuse-mount" is empty),
// while still implementing Lookup ("ls my-fuse-mount/a-specific-file" // while still implementing Lookup ("ls my-fuse-mount/a-specific-file"
// shows a single file). // shows a single file). The DirStream returned must be deterministic;
// a randomized result (e.g. due to map iteration) can lead to entries
// disappearing if multiple processes read the same directory
// concurrently.
// //
// If a directory does not implement NodeReaddirer, a list of // If a directory does not implement NodeReaddirer, a list of
// currently known children from the tree is returned. This means that // currently known children from the tree is returned. This means that
...@@ -610,4 +615,8 @@ type Options struct { ...@@ -610,4 +615,8 @@ type Options struct {
// return error, but want to signal something seems off // return error, but want to signal something seems off
// anyway. If unset, no messages are printed. // anyway. If unset, no messages are printed.
Logger *log.Logger Logger *log.Logger
// RootStableAttr is an optional way to set e.g. Ino and/or Gen for
// the root directory when calling fs.Mount(), Mode is ignored.
RootStableAttr *StableAttr
} }
This diff is collapsed.
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
func TestBridgeReaddirPlusVirtualEntries(t *testing.T) { func TestBridgeReaddirPlusVirtualEntries(t *testing.T) {
// Set suppressDebug as we do our own logging // Set suppressDebug as we do our own logging
tc := newTestCase(t, &testOptions{suppressDebug: true}) tc := newTestCase(t, &testOptions{suppressDebug: true})
defer tc.Clean()
rb := tc.rawFS.(*rawBridge) rb := tc.rawFS.(*rawBridge)
...@@ -92,8 +91,7 @@ func TestBridgeReaddirPlusVirtualEntries(t *testing.T) { ...@@ -92,8 +91,7 @@ func TestBridgeReaddirPlusVirtualEntries(t *testing.T) {
// we just have not received the FORGET yet. // we just have not received the FORGET yet.
func TestTypeChange(t *testing.T) { func TestTypeChange(t *testing.T) {
rootNode := testTypeChangeIno{} rootNode := testTypeChangeIno{}
mnt, _, clean := testMount(t, &rootNode, nil) mnt, _ := testMount(t, &rootNode, nil)
defer clean()
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
fi, _ := os.Stat(mnt + "/file") fi, _ := os.Stat(mnt + "/file")
...@@ -141,8 +139,7 @@ func (fn *testTypeChangeIno) Lookup(ctx context.Context, name string, out *fuse. ...@@ -141,8 +139,7 @@ func (fn *testTypeChangeIno) Lookup(ctx context.Context, name string, out *fuse.
// disconnected from the hierarchy (=orphaned) // disconnected from the hierarchy (=orphaned)
func TestDeletedInodePath(t *testing.T) { func TestDeletedInodePath(t *testing.T) {
rootNode := testDeletedIno{} rootNode := testDeletedIno{}
mnt, _, clean := testMount(t, &rootNode, &Options{Logger: log.New(os.Stderr, "", 0)}) mnt, _ := testMount(t, &rootNode, &Options{Logger: log.New(os.Stderr, "", 0)})
defer clean()
// Open a file handle so the kernel cannot FORGET the inode // Open a file handle so the kernel cannot FORGET the inode
fd, err := os.Open(mnt + "/dir") fd, err := os.Open(mnt + "/dir")
...@@ -206,12 +203,10 @@ func (n *testDeletedIno) Getattr(ctx context.Context, f FileHandle, out *fuse.At ...@@ -206,12 +203,10 @@ func (n *testDeletedIno) Getattr(ctx context.Context, f FileHandle, out *fuse.At
// //
// We used to panic like this because inode number 1 was special: // We used to panic like this because inode number 1 was special:
// //
// panic: using reserved ID 1 for inode number // panic: using reserved ID 1 for inode number
//
func TestIno1(t *testing.T) { func TestIno1(t *testing.T) {
rootNode := testIno1{} rootNode := testIno1{}
mnt, _, clean := testMount(t, &rootNode, nil) mnt, _ := testMount(t, &rootNode, nil)
defer clean()
var st syscall.Stat_t var st syscall.Stat_t
err := syscall.Stat(mnt+"/ino1", &st) err := syscall.Stat(mnt+"/ino1", &st)
......
...@@ -9,11 +9,15 @@ import ( ...@@ -9,11 +9,15 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os"
"sync" "sync"
"syscall" "syscall"
"testing" "testing"
"time"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type keepCacheFile struct { type keepCacheFile struct {
...@@ -92,8 +96,8 @@ func (r *keepCacheRoot) OnAdd(ctx context.Context) { ...@@ -92,8 +96,8 @@ func (r *keepCacheRoot) OnAdd(ctx context.Context) {
// change content but no metadata. // change content but no metadata.
func TestKeepCache(t *testing.T) { func TestKeepCache(t *testing.T) {
root := &keepCacheRoot{} root := &keepCacheRoot{}
mntDir, _, clean := testMount(t, root, nil) mntDir, _ := testMount(t, root, nil)
defer clean()
c1, err := ioutil.ReadFile(mntDir + "/keep") c1, err := ioutil.ReadFile(mntDir + "/keep")
if err != nil { if err != nil {
t.Fatalf("read keep 1: %v", err) t.Fatalf("read keep 1: %v", err)
...@@ -134,3 +138,103 @@ func TestKeepCache(t *testing.T) { ...@@ -134,3 +138,103 @@ func TestKeepCache(t *testing.T) {
t.Errorf("nokeep read 2 got %q want read 1 %q", c2, c1) t.Errorf("nokeep read 2 got %q want read 1 %q", c2, c1)
} }
} }
type countingSymlink struct {
Inode
mu sync.Mutex
readCount int
data []byte
}
var _ = (NodeGetattrer)((*countingSymlink)(nil))
func (l *countingSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
l.mu.Lock()
defer l.mu.Unlock()
out.Attr.Size = uint64(len(l.data))
return 0
}
var _ = (NodeReadlinker)((*countingSymlink)(nil))
func (l *countingSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
l.mu.Lock()
defer l.mu.Unlock()
l.readCount++
return l.data, 0
}
func (l *countingSymlink) count() int {
l.mu.Lock()
defer l.mu.Unlock()
return l.readCount
}
func TestSymlinkCaching(t *testing.T) {
mnt := t.TempDir()
want := "target"
link := countingSymlink{
data: []byte(want),
}
sz := len(link.data)
root := &Inode{}
dt := 10 * time.Millisecond
opts := &Options{
EntryTimeout: &dt,
AttrTimeout: &dt,
OnAdd: func(ctx context.Context) {
root.AddChild("link",
root.NewPersistentInode(ctx, &link, StableAttr{Mode: syscall.S_IFLNK}), false)
},
}
opts.Debug = testutil.VerboseTest()
opts.EnableSymlinkCaching = true
server, err := Mount(mnt, root, opts)
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
for i := 0; i < 2; i++ {
if got, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
} else if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
if c := link.count(); c != 1 {
t.Errorf("got %d want 1", c)
}
if errno := link.NotifyContent(0, int64(sz)); errno != 0 {
t.Fatalf("NotifyContent: %v", errno)
}
if _, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
}
if c := link.count(); c != 2 {
t.Errorf("got %d want 2", c)
}
// The actual test goes till here. The below is just to
// clarify behavior of the feature: changed attributes do not
// trigger reread, and the Attr.Size is used to truncate a
// previous read result.
link.mu.Lock()
link.data = []byte("x")
link.mu.Unlock()
time.Sleep((3 * dt) / 2)
if l, err := os.Readlink(mnt + "/link"); err != nil {
t.Fatal(err)
} else if l != want[:1] {
log.Printf("got %q want %q", l, want[:1])
}
if c := link.count(); c != 2 {
t.Errorf("got %d want 2", c)
}
}
// Copyright 2023 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"context"
"fmt"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
)
type dirStreamErrorNode struct {
Inode
}
var _ = (NodeReaddirer)((*dirStreamErrorNode)(nil))
func (n *dirStreamErrorNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
return &errDirStream{}, 0
}
type errDirStream struct {
num int
}
func (ds *errDirStream) HasNext() bool {
return ds.num < 2
}
func (ds *errDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.num++
if ds.num == 1 {
return fuse.DirEntry{
Mode: fuse.S_IFREG,
Name: "first",
Ino: 2,
}, 0
}
if ds.num == 2 {
return fuse.DirEntry{
Mode: fuse.S_IFREG,
Name: "last",
Ino: 3,
}, syscall.EKEYEXPIRED
}
panic("boom")
}
func (ds *errDirStream) Close() {
}
func TestDirStreamError(t *testing.T) {
for _, disableReaddirplus := range []bool{false, true} {
t.Run(fmt.Sprintf("disableReaddirplus=%v", disableReaddirplus),
func(t *testing.T) {
root := &dirStreamErrorNode{}
opts := Options{}
opts.DisableReadDirPlus = disableReaddirplus
mnt, _ := testMount(t, root, &opts)
ds, errno := NewLoopbackDirStream(mnt)
if errno != 0 {
t.Fatalf("NewLoopbackDirStream: %v", errno)
}
defer ds.Close()
if e, errno := ds.Next(); errno != 0 {
t.Errorf("ds.Next: %v", errno)
} else if e.Name != "first" {
t.Errorf("got %q want 'first'", e.Name)
}
if _, errno := ds.Next(); errno != syscall.EKEYEXPIRED {
t.Errorf("got errno %v, want EKEYEXPIRED", errno)
}
})
}
}
...@@ -55,8 +55,7 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl ...@@ -55,8 +55,7 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl
// this tests FOPEN_DIRECT_IO (as opposed to O_DIRECTIO) // this tests FOPEN_DIRECT_IO (as opposed to O_DIRECTIO)
func TestFUSEDirectIO(t *testing.T) { func TestFUSEDirectIO(t *testing.T) {
root := &dioRoot{} root := &dioRoot{}
mntDir, server, clean := testMount(t, root, nil) mntDir, server := testMount(t, root, nil)
defer clean()
f, err := os.Open(mntDir + "/file") f, err := os.Open(mntDir + "/file")
if err != nil { if err != nil {
......
...@@ -13,8 +13,9 @@ import ( ...@@ -13,8 +13,9 @@ import (
) )
type loopbackDirStream struct { type loopbackDirStream struct {
buf []byte buf []byte
todo []byte todo []byte
todoErrno syscall.Errno
// Protects fd so we can guard against double close // Protects fd so we can guard against double close
mu sync.Mutex mu sync.Mutex
...@@ -52,7 +53,7 @@ func (ds *loopbackDirStream) Close() { ...@@ -52,7 +53,7 @@ func (ds *loopbackDirStream) Close() {
func (ds *loopbackDirStream) HasNext() bool { func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock() ds.mu.Lock()
defer ds.mu.Unlock() defer ds.mu.Unlock()
return len(ds.todo) > 0 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.
...@@ -68,6 +69,10 @@ func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) { ...@@ -68,6 +69,10 @@ func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock() ds.mu.Lock()
defer ds.mu.Unlock() defer ds.mu.Unlock()
if ds.todoErrno != 0 {
return fuse.DirEntry{}, ds.todoErrno
}
// We can't use syscall.Dirent here, because it declares a // We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo. // [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic // when that happens in the race detector, it causes a panic
...@@ -99,9 +104,10 @@ func (ds *loopbackDirStream) load() syscall.Errno { ...@@ -99,9 +104,10 @@ func (ds *loopbackDirStream) load() syscall.Errno {
} }
n, err := syscall.Getdents(ds.fd, ds.buf) n, err := syscall.Getdents(ds.fd, ds.buf)
if err != nil { if n < 0 {
return ToErrno(err) n = 0
} }
ds.todo = ds.buf[:n] ds.todo = ds.buf[:n]
ds.todoErrno = ToErrno(err)
return OK return OK
} }
...@@ -19,17 +19,16 @@ import ( ...@@ -19,17 +19,16 @@ import (
// numbers are regular files, while composite numbers are directories // numbers are regular files, while composite numbers are directories
// containing all smaller numbers, eg. // containing all smaller numbers, eg.
// //
// $ ls -F /tmp/x/6 // $ ls -F /tmp/x/6
// 2 3 4/ 5 // 2 3 4/ 5
// //
// the file system nodes are deduplicated using inode numbers. The // the file system nodes are deduplicated using inode numbers. The
// number 2 appears in many directories, but it is actually the represented // number 2 appears in many directories, but it is actually the represented
// by the same numberNode{} object, with inode number 2. // by the same numberNode{} object, with inode number 2.
// //
// $ ls -i1 /tmp/x/2 /tmp/x/8/6/4/2 // $ ls -i1 /tmp/x/2 /tmp/x/8/6/4/2
// 2 /tmp/x/2 // 2 /tmp/x/2
// 2 /tmp/x/8/6/4/2 // 2 /tmp/x/8/6/4/2
//
type numberNode struct { type numberNode struct {
// Must embed an Inode for the struct to work as a node. // Must embed an Inode for the struct to work as a node.
fs.Inode fs.Inode
......
...@@ -155,13 +155,26 @@ func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fus ...@@ -155,13 +155,26 @@ func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fus
return f.Getattr(ctx, out) return f.Getattr(ctx, out)
} }
func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno { func (f *loopbackFile) fchmod(mode uint32) syscall.Errno {
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()
return ToErrno(syscall.Fchmod(f.fd, mode))
}
func (f *loopbackFile) fchown(uid, gid int) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
return ToErrno(syscall.Fchown(f.fd, uid, gid))
}
func (f *loopbackFile) ftruncate(sz uint64) syscall.Errno {
return ToErrno(syscall.Ftruncate(f.fd, int64(sz)))
}
func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno {
var errno syscall.Errno var errno syscall.Errno
if mode, ok := in.GetMode(); ok { if mode, ok := in.GetMode(); ok {
errno = ToErrno(syscall.Fchmod(f.fd, mode)) if errno := f.fchmod(mode); errno != 0 {
if errno != 0 {
return errno return errno
} }
} }
...@@ -178,8 +191,7 @@ func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall. ...@@ -178,8 +191,7 @@ func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.
if gOk { if gOk {
gid = int(gid32) gid = int(gid32)
} }
errno = ToErrno(syscall.Fchown(f.fd, uid, gid)) if errno := f.fchown(uid, gid); errno != 0 {
if errno != 0 {
return errno return errno
} }
} }
...@@ -203,8 +215,7 @@ func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall. ...@@ -203,8 +215,7 @@ func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.
} }
if sz, ok := in.GetSize(); ok { if sz, ok := in.GetSize(); ok {
errno = ToErrno(syscall.Ftruncate(f.fd, int64(sz))) if errno := f.ftruncate(sz); errno != 0 {
if errno != 0 {
return errno return errno
} }
} }
......
...@@ -79,11 +79,7 @@ func TestForget(t *testing.T) { ...@@ -79,11 +79,7 @@ func TestForget(t *testing.T) {
EntryTimeout: &sec, EntryTimeout: &sec,
} }
options.Debug = testutil.VerboseTest() options.Debug = testutil.VerboseTest()
dir, err := ioutil.TempDir("", "TestForget") dir := t.TempDir()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
rawFS := NewNodeFS(root, options) rawFS := NewNodeFS(root, options)
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions) server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
......
...@@ -511,6 +511,41 @@ func (n *Inode) Children() map[string]*Inode { ...@@ -511,6 +511,41 @@ func (n *Inode) Children() map[string]*Inode {
return r return r
} }
type childEntry struct {
Name string
Inode *Inode
}
// childrenList returns the list of children of this directory Inode.
// The result is guaranteed to be stable as long as the directory did
// not change.
func (n *Inode) childrenList() []childEntry {
n.mu.Lock()
defer n.mu.Unlock()
r := make([]childEntry, 0, 2*len(n.children))
// The spec doesn't guarantee this, but as long as maps remain
// backed by hash tables, the simplest mechanism for
// randomization is picking a random start index. We undo this
// here by picking a deterministic start index again. If the
// Go runtime ever implements a memory moving GC, we might
// have to look at the keys instead.
minNode := ^uintptr(0)
minIdx := -1
for k, v := range n.children {
if p := uintptr(unsafe.Pointer(v)); p < minNode {
minIdx = len(r)
minNode = p
}
r = append(r, childEntry{Name: k, Inode: v})
}
if minIdx > 0 {
r = append(r[minIdx:], r[:minIdx]...)
}
return r
}
// Parents returns a parent of this Inode, or nil if this Inode is // Parents returns a parent of this Inode, or nil if this Inode is
// deleted or is the root // deleted or is the root
func (n *Inode) Parent() (string, *Inode) { func (n *Inode) Parent() (string, *Inode) {
......
...@@ -18,11 +18,11 @@ func TestInodeParents(t *testing.T) { ...@@ -18,11 +18,11 @@ func TestInodeParents(t *testing.T) {
// non-dupes should be stored // non-dupes should be stored
all := []parentData{ all := []parentData{
parentData{"foo", &ino1}, {"foo", &ino1},
parentData{"foo2", &ino1}, {"foo2", &ino1},
parentData{"foo3", &ino1}, {"foo3", &ino1},
parentData{"foo", &ino2}, {"foo", &ino2},
parentData{"foo", &ino3}, {"foo", &ino3},
} }
for i, v := range all { for i, v := range all {
p.add(v) p.add(v)
......
...@@ -55,15 +55,10 @@ func TestInterrupt(t *testing.T) { ...@@ -55,15 +55,10 @@ func TestInterrupt(t *testing.T) {
root := &interruptRoot{} root := &interruptRoot{}
oneSec := time.Second oneSec := time.Second
mntDir, _, clean := testMount(t, root, &Options{ mntDir, server := testMount(t, root, &Options{
EntryTimeout: &oneSec, EntryTimeout: &oneSec,
AttrTimeout: &oneSec, AttrTimeout: &oneSec,
}) })
defer func() {
if clean != nil {
clean()
}
}()
cmd := exec.Command("cat", mntDir+"/file") cmd := exec.Command("cat", mntDir+"/file")
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
...@@ -74,9 +69,7 @@ func TestInterrupt(t *testing.T) { ...@@ -74,9 +69,7 @@ func TestInterrupt(t *testing.T) {
if err := cmd.Process.Kill(); err != nil { if err := cmd.Process.Kill(); err != nil {
t.Errorf("Kill: %v", err) t.Errorf("Kill: %v", err)
} }
server.Unmount()
clean()
clean = nil
if !root.child.interrupted { if !root.child.interrupted {
t.Errorf("open request was not interrupted") t.Errorf("open request was not interrupted")
......
...@@ -70,25 +70,6 @@ type LoopbackNode struct { ...@@ -70,25 +70,6 @@ type LoopbackNode struct {
} }
var _ = (NodeStatfser)((*LoopbackNode)(nil)) var _ = (NodeStatfser)((*LoopbackNode)(nil))
var _ = (NodeStatfser)((*LoopbackNode)(nil))
var _ = (NodeGetattrer)((*LoopbackNode)(nil))
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
var _ = (NodeReadlinker)((*LoopbackNode)(nil))
var _ = (NodeOpener)((*LoopbackNode)(nil))
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
var _ = (NodeLookuper)((*LoopbackNode)(nil))
var _ = (NodeOpendirer)((*LoopbackNode)(nil))
var _ = (NodeReaddirer)((*LoopbackNode)(nil))
var _ = (NodeMkdirer)((*LoopbackNode)(nil))
var _ = (NodeMknoder)((*LoopbackNode)(nil))
var _ = (NodeLinker)((*LoopbackNode)(nil))
var _ = (NodeSymlinker)((*LoopbackNode)(nil))
var _ = (NodeUnlinker)((*LoopbackNode)(nil))
var _ = (NodeRmdirer)((*LoopbackNode)(nil))
var _ = (NodeRenamer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
s := syscall.Statfs_t{} s := syscall.Statfs_t{}
...@@ -107,6 +88,8 @@ func (n *LoopbackNode) path() string { ...@@ -107,6 +88,8 @@ func (n *LoopbackNode) path() string {
return filepath.Join(n.RootData.Path, path) return filepath.Join(n.RootData.Path, path)
} }
var _ = (NodeLookuper)((*LoopbackNode)(nil))
func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
...@@ -135,6 +118,8 @@ func (n *LoopbackNode) preserveOwner(ctx context.Context, path string) error { ...@@ -135,6 +118,8 @@ func (n *LoopbackNode) preserveOwner(ctx context.Context, path string) error {
return syscall.Lchown(path, int(caller.Uid), int(caller.Gid)) return syscall.Lchown(path, int(caller.Uid), int(caller.Gid))
} }
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, int(rdev))
...@@ -156,6 +141,8 @@ func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32 ...@@ -156,6 +141,8 @@ func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32
return ch, 0 return ch, 0
} }
var _ = (NodeMkdirer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := os.Mkdir(p, os.FileMode(mode)) err := os.Mkdir(p, os.FileMode(mode))
...@@ -177,18 +164,24 @@ func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out ...@@ -177,18 +164,24 @@ func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out
return ch, 0 return ch, 0
} }
var _ = (NodeRmdirer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno { func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := syscall.Rmdir(p) err := syscall.Rmdir(p)
return ToErrno(err) return ToErrno(err)
} }
var _ = (NodeUnlinker)((*LoopbackNode)(nil))
func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno { func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := syscall.Unlink(p) err := syscall.Unlink(p)
return ToErrno(err) return ToErrno(err)
} }
var _ = (NodeRenamer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno { func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno {
if flags&RENAME_EXCHANGE != 0 { if flags&RENAME_EXCHANGE != 0 {
return n.renameExchange(name, newParent, newName) return n.renameExchange(name, newParent, newName)
...@@ -225,6 +218,8 @@ func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mo ...@@ -225,6 +218,8 @@ func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mo
return ch, lf, 0, 0 return ch, lf, 0, 0
} }
var _ = (NodeSymlinker)((*LoopbackNode)(nil))
func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := syscall.Symlink(target, p) err := syscall.Symlink(target, p)
...@@ -244,6 +239,8 @@ func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fu ...@@ -244,6 +239,8 @@ func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fu
return ch, 0 return ch, 0
} }
var _ = (NodeLinker)((*LoopbackNode)(nil))
func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
...@@ -263,6 +260,8 @@ func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name stri ...@@ -263,6 +260,8 @@ func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name stri
return ch, 0 return ch, 0
} }
var _ = (NodeReadlinker)((*LoopbackNode)(nil))
func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
p := n.path() p := n.path()
...@@ -279,6 +278,8 @@ func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { ...@@ -279,6 +278,8 @@ func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
} }
} }
var _ = (NodeOpener)((*LoopbackNode)(nil))
func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
flags = flags &^ syscall.O_APPEND flags = flags &^ syscall.O_APPEND
p := n.path() p := n.path()
...@@ -290,6 +291,8 @@ func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, f ...@@ -290,6 +291,8 @@ func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, f
return lf, 0, 0 return lf, 0, 0
} }
var _ = (NodeOpendirer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno { func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno {
fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755) fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755)
if err != nil { if err != nil {
...@@ -299,10 +302,14 @@ func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno { ...@@ -299,10 +302,14 @@ func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno {
return OK return OK
} }
var _ = (NodeReaddirer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) { func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
return NewLoopbackDirStream(n.path()) return NewLoopbackDirStream(n.path())
} }
var _ = (NodeGetattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
if f != nil { if f != nil {
return f.(FileGetattrer).Getattr(ctx, out) return f.(FileGetattrer).Getattr(ctx, out)
......
//go:build darwin
// +build darwin // +build darwin
// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Copyright 2019 the Go-FUSE Authors. All rights reserved.
...@@ -16,18 +17,26 @@ import ( ...@@ -16,18 +17,26 @@ import (
"github.com/hanwen/go-fuse/v2/internal/utimens" "github.com/hanwen/go-fuse/v2/internal/utimens"
) )
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
} }
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.ENOSYS return syscall.ENOSYS
} }
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
return syscall.ENOSYS return syscall.ENOSYS
} }
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS return 0, syscall.ENOSYS
} }
...@@ -111,6 +120,8 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { ...@@ -111,6 +120,8 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
return ToErrno(err) return ToErrno(err)
} }
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) { len uint64, flags uint64) (uint32, syscall.Errno) {
......
//go:build linux
// +build linux // +build linux
// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Copyright 2019 the Go-FUSE Authors. All rights reserved.
...@@ -14,21 +15,29 @@ import ( ...@@ -14,21 +15,29 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Lgetxattr(n.path(), attr, dest) sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err) return uint32(sz), ToErrno(err)
} }
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := unix.Lsetxattr(n.path(), attr, data, int(flags)) err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err) return ToErrno(err)
} }
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := unix.Lremovexattr(n.path(), attr) err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err) return ToErrno(err)
} }
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Llistxattr(n.path(), dest) sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err) return uint32(sz), ToErrno(err)
...@@ -69,6 +78,8 @@ func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newN ...@@ -69,6 +78,8 @@ func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newN
return ToErrno(unix.Renameat2(fd1, name, fd2, newName, unix.RENAME_EXCHANGE)) return ToErrno(unix.Renameat2(fd1, name, fd2, newName, unix.RENAME_EXCHANGE))
} }
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) { len uint64, flags uint64) (uint32, syscall.Errno) {
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
func TestRenameExchange(t *testing.T) { func TestRenameExchange(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err) t.Fatalf("Mkdir: %v", err)
...@@ -95,7 +94,6 @@ func TestRenameExchange(t *testing.T) { ...@@ -95,7 +94,6 @@ func TestRenameExchange(t *testing.T) {
func TestRenameNoOverwrite(t *testing.T) { func TestRenameNoOverwrite(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err) t.Fatalf("Mkdir: %v", err)
...@@ -123,7 +121,6 @@ func TestRenameNoOverwrite(t *testing.T) { ...@@ -123,7 +121,6 @@ func TestRenameNoOverwrite(t *testing.T) {
func TestXAttr(t *testing.T) { func TestXAttr(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
tc.writeOrig("file", "", 0644) tc.writeOrig("file", "", 0644)
...@@ -188,7 +185,6 @@ func TestXAttr(t *testing.T) { ...@@ -188,7 +185,6 @@ func TestXAttr(t *testing.T) {
// so don't even bother. See `man 7 xattr` for more info. // so don't even bother. See `man 7 xattr` for more info.
func TestXAttrSymlink(t *testing.T) { func TestXAttrSymlink(t *testing.T) {
tc := newTestCase(t, nil) tc := newTestCase(t, nil)
defer tc.Clean()
path := tc.mntDir + "/symlink" path := tc.mntDir + "/symlink"
if err := syscall.Symlink("target/does/not/exist", path); err != nil { if err := syscall.Symlink("target/does/not/exist", path); err != nil {
...@@ -203,7 +199,6 @@ func TestXAttrSymlink(t *testing.T) { ...@@ -203,7 +199,6 @@ func TestXAttrSymlink(t *testing.T) {
func TestCopyFileRange(t *testing.T) { func TestCopyFileRange(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
if !tc.server.KernelSettings().SupportsVersion(7, 28) { if !tc.server.KernelSettings().SupportsVersion(7, 28) {
t.Skip("need v7.28 for CopyFileRange") t.Skip("need v7.28 for CopyFileRange")
...@@ -306,7 +301,7 @@ func waitMount(mnt string) error { ...@@ -306,7 +301,7 @@ func waitMount(mnt string) error {
func TestParallelDiropsHang(t *testing.T) { func TestParallelDiropsHang(t *testing.T) {
// We do NOT want to use newTestCase() here because we need to know the // We do NOT want to use newTestCase() here because we need to know the
// mnt path before the filesystem is mounted // mnt path before the filesystem is mounted
dir := testutil.TempDir() dir := t.TempDir()
orig := dir + "/orig" orig := dir + "/orig"
mnt := dir + "/mnt" mnt := dir + "/mnt"
if err := os.Mkdir(orig, 0755); err != nil { if err := os.Mkdir(orig, 0755); err != nil {
...@@ -315,7 +310,6 @@ func TestParallelDiropsHang(t *testing.T) { ...@@ -315,7 +310,6 @@ func TestParallelDiropsHang(t *testing.T) {
if err := os.Mkdir(mnt, 0755); err != nil { if err := os.Mkdir(mnt, 0755); err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(dir)
// Unblock the goroutines onces the mount shows up in /proc/self/mounts // Unblock the goroutines onces the mount shows up in /proc/self/mounts
wait := make(chan struct{}) wait := make(chan struct{})
...@@ -398,6 +392,16 @@ func TestParallelDiropsHang(t *testing.T) { ...@@ -398,6 +392,16 @@ func TestParallelDiropsHang(t *testing.T) {
} }
func TestRoMount(t *testing.T) { func TestRoMount(t *testing.T) {
tc := newTestCase(t, &testOptions{ro: true}) newTestCase(t, &testOptions{ro: true})
defer tc.Clean() }
func TestDirectMount(t *testing.T) {
opts := &testOptions{
directMount: true,
}
if os.Geteuid() == 0 {
t.Log("running as root, setting DirectMountStrict")
opts.directMountStrict = true
}
newTestCase(t, opts)
} }
// Copyright 2022 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"
"fmt"
"io/ioutil"
"strconv"
"strings"
"sync"
"syscall"
"testing"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fuse"
)
type maxWriteTestRoot struct {
Inode
sync.Mutex
// largest observed read size
largestRead int
// largest observed write size
largestWrite int
}
// https://github.com/torvalds/linux/blob/e2ae0d4a6b0ba461542f0fd0ba0b828658013e9f/include/linux/pagemap.h#L999
const VM_READAHEAD = 131072
var _ = (NodeOnAdder)((*maxWriteTestRoot)(nil))
func (n *maxWriteTestRoot) OnAdd(ctx context.Context) {
n.Inode.AddChild("file", n.Inode.NewInode(ctx, &maxWriteTestNode{maxWriteTestRoot: n}, StableAttr{}), false)
}
func (n *maxWriteTestRoot) resetStats() {
n.Lock()
n.largestWrite = 0
n.largestRead = 0
n.Unlock()
}
type maxWriteTestNode struct {
Inode
maxWriteTestRoot *maxWriteTestRoot
}
var _ = (NodeGetattrer)((*maxWriteTestNode)(nil))
func (n *maxWriteTestNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Size = 1024 * 1024 * 1024 // 1 GiB
return 0
}
var _ = (NodeOpener)((*maxWriteTestNode)(nil))
func (n *maxWriteTestNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
return &maxWriteTestFH{n.maxWriteTestRoot}, 0, OK
}
type maxWriteTestFH struct {
maxWriteTestRoot *maxWriteTestRoot
}
var _ = (FileReader)((*maxWriteTestFH)(nil))
func (fh *maxWriteTestFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) {
fh.maxWriteTestRoot.Lock()
if fh.maxWriteTestRoot.largestRead < len(data) {
fh.maxWriteTestRoot.largestRead = len(data)
}
fh.maxWriteTestRoot.Unlock()
return fuse.ReadResultData(data), 0
}
var _ = (FileWriter)((*maxWriteTestFH)(nil))
func (fh *maxWriteTestFH) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
fh.maxWriteTestRoot.Lock()
if fh.maxWriteTestRoot.largestWrite < len(data) {
fh.maxWriteTestRoot.largestWrite = len(data)
}
fh.maxWriteTestRoot.Unlock()
return uint32(len(data)), 0
}
// TestMaxWrite checks that combinations of the MaxWrite, MaxReadAhead, max_read
// options result in the expected observed read and write sizes from the kernel.
func TestMaxWrite(t *testing.T) {
testcases := []fuse.MountOptions{
{
MaxWrite: 4 * 1024, // 4 kiB (one page) = lower limit in all Linux versions
},
{
MaxWrite: 8 * 1024,
},
{
MaxWrite: 9999, // let's see what happens if this is unaligned
},
{
MaxWrite: 64 * 1024, // 64 kiB = go-fuse default
},
{
MaxWrite: 128 * 1024, // 128 kiB = upper limit in Linux v4.19 and older
},
{
MaxWrite: 1024 * 1024, // 1 MiB = upper limit in Linux v4.20+
},
// cycle through readahead values
{
MaxWrite: 128 * 1024,
MaxReadAhead: 4 * 1024,
},
{
MaxWrite: 128 * 1024,
MaxReadAhead: 8 * 1024,
},
{
MaxWrite: 128 * 1024,
MaxReadAhead: 16 * 1024,
},
{
MaxWrite: 128 * 1024,
MaxReadAhead: 32 * 1024,
},
{
MaxWrite: 128 * 1024,
MaxReadAhead: 64 * 1024,
},
{
MaxWrite: 128 * 1024,
MaxReadAhead: 128 * 1024,
},
{
// both at default
},
{
// default MaxWrite
MaxReadAhead: 4 * 1024,
},
}
for _, tc := range testcases {
name := fmt.Sprintf("MaxWr%d.MaxRa%d", tc.MaxWrite, tc.MaxReadAhead)
t.Run(name, func(t *testing.T) {
root := &maxWriteTestRoot{}
root.resetStats()
mntDir, srv := testMount(t, root, &Options{MountOptions: tc})
readAheadWant := tc.MaxReadAhead
if readAheadWant == 0 {
readAheadWant = VM_READAHEAD
}
readAheadHave := bdiReadahead(mntDir)
if readAheadHave != readAheadWant {
t.Errorf("Readahead mismatch: have=bdiReadahead=%d want=%d", readAheadHave, readAheadWant)
}
actualMaxWrite := tc.MaxWrite
if srv.KernelSettings().Flags&fuse.CAP_MAX_PAGES == 0 && actualMaxWrite > 128*1024 {
// Kernel 4.19 and lower don't have CAP_MAX_PAGES and limit to 128 kiB.
actualMaxWrite = 128 * 1024
} else if tc.MaxWrite == 0 {
actualMaxWrite = 128 * 1024
}
// Try to make 2 MiB requests, which is more than the kernel supports, so
// we will observe the imposed limits in the actual request sizes.
buf := make([]byte, 2*1024*1024)
// Direct I/O
fdDirect, err := syscall.Open(mntDir+"/file", syscall.O_RDWR|syscall.O_DIRECT, 0600)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fdDirect)
_, err = syscall.Pwrite(fdDirect, buf, 0)
if err != nil {
t.Errorf("write failed: %v", err)
}
root.Lock()
if root.largestWrite != actualMaxWrite {
t.Errorf("Direct I/O largestWrite: have=%d, want=%d", root.largestWrite, actualMaxWrite)
}
root.Unlock()
_, err = syscall.Pread(fdDirect, buf, 0)
if err != nil {
t.Errorf("read failed: %v", err)
}
root.Lock()
if root.largestRead != actualMaxWrite {
t.Errorf("Direct I/O largestRead: have=%d, want=%d", root.largestRead, actualMaxWrite)
}
root.Unlock()
root.resetStats()
// Buffered I/O
fdBuffered, err := syscall.Open(mntDir+"/file", syscall.O_RDWR, 0600)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fdBuffered)
// Buffered read
_, err = syscall.Pread(fdBuffered, buf, 0)
if err != nil {
t.Errorf("read failed: %v", err)
}
root.Lock()
// On Linux 4.19, I get exactly tc.MaxReadAhead, while on 6.0 I also get
// larger reads up to 128 kiB. We log the results but don't expect anything.
t.Logf("Buffered I/O largestRead: have=%d", root.largestRead)
root.Unlock()
// Buffered write
_, err = syscall.Pwrite(fdBuffered, buf, 0)
if err != nil {
t.Errorf("write failed: %v", err)
}
root.Lock()
if root.largestWrite != actualMaxWrite {
t.Errorf("Buffered I/O largestWrite: have=%d, want=%d", root.largestWrite, actualMaxWrite)
}
root.Unlock()
})
}
}
// bdiReadahead extracts the readahead size (in bytes) of the filesystem at mnt from
// /sys/class/bdi/%d:%d/read_ahead_kb .
func bdiReadahead(mnt string) int {
var st syscall.Stat_t
err := syscall.Stat(mnt, &st)
if err != nil {
panic(err)
}
path := fmt.Sprintf("/sys/class/bdi/%d:%d/read_ahead_kb", unix.Major(st.Dev), unix.Minor(st.Dev))
buf, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
trimmed := strings.TrimSpace(string(buf))
val, err := strconv.Atoi(trimmed)
if err != nil {
panic(err)
}
return val * 1024
}
...@@ -7,20 +7,25 @@ package fs ...@@ -7,20 +7,25 @@ package fs
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"os" "os"
"reflect"
"sync"
"syscall" "syscall"
"testing" "testing"
"time"
"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"
) )
func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server, func()) { func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server) {
t.Helper() t.Helper()
mntDir := testutil.TempDir() mntDir := t.TempDir()
if opts == nil { if opts == nil {
opts = &Options{ opts = &Options{
FirstAutomaticIno: 1, FirstAutomaticIno: 1,
...@@ -32,20 +37,18 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.S ...@@ -32,20 +37,18 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.S
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return mntDir, server, func() { t.Cleanup(func() {
if err := server.Unmount(); err != nil { if err := server.Unmount(); err != nil {
t.Fatalf("testMount: Unmount failed: %v", err) t.Fatalf("testMount: Unmount failed: %v", err)
} }
if err := syscall.Rmdir(mntDir); err != nil { })
t.Errorf("testMount: Remove failed: %v", err) return mntDir, server
}
}
} }
func TestDefaultOwner(t *testing.T) { func TestDefaultOwner(t *testing.T) {
want := "hello" want := "hello"
root := &Inode{} root := &Inode{}
mntDir, _, clean := testMount(t, root, &Options{ mntDir, _ := testMount(t, root, &Options{
FirstAutomaticIno: 1, FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode() n := root.EmbeddedInode()
...@@ -60,7 +63,6 @@ func TestDefaultOwner(t *testing.T) { ...@@ -60,7 +63,6 @@ func TestDefaultOwner(t *testing.T) {
UID: 42, UID: 42,
GID: 43, GID: 43,
}) })
defer clean()
var st syscall.Stat_t var st syscall.Stat_t
if err := syscall.Lstat(mntDir+"/file", &st); err != nil { if err := syscall.Lstat(mntDir+"/file", &st); err != nil {
...@@ -70,10 +72,51 @@ func TestDefaultOwner(t *testing.T) { ...@@ -70,10 +72,51 @@ func TestDefaultOwner(t *testing.T) {
} }
} }
func TestRootInode(t *testing.T) {
var rootIno uint64 = 42
root := &Inode{}
mntDir, _ := testMount(t, root, &Options{
RootStableAttr: &StableAttr{
Ino: rootIno,
Gen: 1,
},
})
var st syscall.Stat_t
if err := syscall.Lstat(mntDir, &st); err != nil {
t.Fatalf("Lstat: %v", err)
} else if st.Ino != rootIno {
t.Fatalf("Got Lstat inode %d, want %d", st.Ino, rootIno)
}
}
func TestLseekDefault(t *testing.T) {
data := []byte("hello")
root := &Inode{}
mntDir, _ := testMount(t, root, &Options{
FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
ch := n.NewPersistentInode(
ctx,
&MemRegularFile{
Data: data,
Attr: fuse.Attr{
Mode: 0464,
},
}, StableAttr{})
n.AddChild("file.bin", ch, false)
},
})
posixtest.LseekHoleSeeksToEOF(t, mntDir)
}
func TestDataFile(t *testing.T) { func TestDataFile(t *testing.T) {
want := "hello" want := "hello"
root := &Inode{} root := &Inode{}
mntDir, _, clean := testMount(t, root, &Options{ mntDir, _ := testMount(t, root, &Options{
FirstAutomaticIno: 1, FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode() n := root.EmbeddedInode()
...@@ -89,7 +132,6 @@ func TestDataFile(t *testing.T) { ...@@ -89,7 +132,6 @@ func TestDataFile(t *testing.T) {
n.AddChild("file", ch, false) n.AddChild("file", ch, false)
}, },
}) })
defer clean()
var st syscall.Stat_t var st syscall.Stat_t
if err := syscall.Lstat(mntDir+"/file", &st); err != nil { if err := syscall.Lstat(mntDir+"/file", &st); err != nil {
...@@ -141,7 +183,7 @@ func TestDataFileLargeRead(t *testing.T) { ...@@ -141,7 +183,7 @@ func TestDataFileLargeRead(t *testing.T) {
data := make([]byte, 256*1024) data := make([]byte, 256*1024)
rand.Read(data[:]) rand.Read(data[:])
mntDir, _, clean := testMount(t, root, &Options{ mntDir, _ := testMount(t, root, &Options{
FirstAutomaticIno: 1, FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode() n := root.EmbeddedInode()
...@@ -157,7 +199,6 @@ func TestDataFileLargeRead(t *testing.T) { ...@@ -157,7 +199,6 @@ func TestDataFileLargeRead(t *testing.T) {
n.AddChild("file", ch, false) n.AddChild("file", ch, false)
}, },
}) })
defer clean()
got, err := ioutil.ReadFile(mntDir + "/file") got, err := ioutil.ReadFile(mntDir + "/file")
if err != nil { if err != nil {
t.Fatalf("ReadFile: %v", err) t.Fatalf("ReadFile: %v", err)
...@@ -184,8 +225,7 @@ func (s *SymlinkerRoot) Symlink(ctx context.Context, target, name string, out *f ...@@ -184,8 +225,7 @@ func (s *SymlinkerRoot) Symlink(ctx context.Context, target, name string, out *f
func TestDataSymlink(t *testing.T) { func TestDataSymlink(t *testing.T) {
root := &SymlinkerRoot{} root := &SymlinkerRoot{}
mntDir, _, clean := testMount(t, root, nil) mntDir, _ := testMount(t, root, nil)
defer clean()
if err := syscall.Symlink("target", mntDir+"/link"); err != nil { if err := syscall.Symlink("target", mntDir+"/link"); err != nil {
t.Fatalf("Symlink: %v", err) t.Fatalf("Symlink: %v", err)
...@@ -197,3 +237,68 @@ func TestDataSymlink(t *testing.T) { ...@@ -197,3 +237,68 @@ func TestDataSymlink(t *testing.T) {
t.Errorf("Readlink: got %q want %q", got, want) t.Errorf("Readlink: got %q want %q", got, want)
} }
} }
func TestReaddirplusParallel(t *testing.T) {
root := &Inode{}
N := 100
oneSec := time.Second
names := map[string]int64{}
mntDir, _ := testMount(t, root, &Options{
FirstAutomaticIno: 1,
EntryTimeout: &oneSec,
AttrTimeout: &oneSec,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
for i := 0; i < N; i++ {
ch := n.NewPersistentInode(
ctx,
&MemRegularFile{
Data: bytes.Repeat([]byte{'x'}, i),
},
StableAttr{})
name := fmt.Sprintf("file%04d", i)
names[name] = int64(i)
n.AddChild(name, ch, false)
}
},
})
read := func() (map[string]int64, error) {
es, err := os.ReadDir(mntDir)
if err != nil {
return nil, err
}
r := map[string]int64{}
for _, e := range es {
inf, err := e.Info()
if err != nil {
return nil, err
}
r[e.Name()] = inf.Size()
}
return r, nil
}
var wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
res, err := read()
if err != nil {
t.Errorf("readdir: %v", err)
}
if got, want := len(res), len(names); got != want {
t.Errorf("got %d want %d", got, want)
return
}
if !reflect.DeepEqual(res, names) {
t.Errorf("maps have different content")
}
}()
}
wg.Wait()
}
...@@ -55,8 +55,7 @@ func (fn *randomTypeTest) Readdir(ctx context.Context) (DirStream, syscall.Errno ...@@ -55,8 +55,7 @@ func (fn *randomTypeTest) Readdir(ctx context.Context) (DirStream, syscall.Errno
func TestReaddirTypeFixup(t *testing.T) { func TestReaddirTypeFixup(t *testing.T) {
root := &randomTypeTest{} root := &randomTypeTest{}
mntDir, _, clean := testMount(t, root, nil) mntDir, _ := testMount(t, root, nil)
defer clean()
f, err := os.Open(mntDir) f, err := os.Open(mntDir)
if err != nil { if err != nil {
...@@ -78,7 +77,6 @@ func TestReaddirTypeFixup(t *testing.T) { ...@@ -78,7 +77,6 @@ func TestReaddirTypeFixup(t *testing.T) {
if err != 0 { if err != 0 {
t.Errorf("Next: %d", err) t.Errorf("Next: %d", err)
} }
t.Logf("%q: mode=0x%x", e.Name, e.Mode)
gotIsDir := (e.Mode & syscall.S_IFDIR) != 0 gotIsDir := (e.Mode & syscall.S_IFDIR) != 0
wantIsdir := (crc32.ChecksumIEEE([]byte(e.Name)) % 2) == 1 wantIsdir := (crc32.ChecksumIEEE([]byte(e.Name)) % 2) == 1
if gotIsDir != wantIsdir { if gotIsDir != wantIsdir {
......
...@@ -16,8 +16,7 @@ import ( ...@@ -16,8 +16,7 @@ import (
func TestReadonlyCreate(t *testing.T) { func TestReadonlyCreate(t *testing.T) {
root := &Inode{} root := &Inode{}
mntDir, _, clean := testMount(t, root, nil) mntDir, _ := testMount(t, root, nil)
defer clean()
_, err := syscall.Creat(mntDir+"/test", 0644) _, err := syscall.Creat(mntDir+"/test", 0644)
if want := syscall.EROFS; want != err { if want := syscall.EROFS; want != err {
...@@ -28,7 +27,7 @@ func TestReadonlyCreate(t *testing.T) { ...@@ -28,7 +27,7 @@ func TestReadonlyCreate(t *testing.T) {
func TestDefaultPermissions(t *testing.T) { func TestDefaultPermissions(t *testing.T) {
root := &Inode{} root := &Inode{}
mntDir, _, clean := testMount(t, root, &Options{ mntDir, _ := testMount(t, root, &Options{
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
dir := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFDIR}) dir := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFDIR})
file := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFREG}) file := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFREG})
...@@ -37,7 +36,6 @@ func TestDefaultPermissions(t *testing.T) { ...@@ -37,7 +36,6 @@ func TestDefaultPermissions(t *testing.T) {
root.AddChild("file", file, false) root.AddChild("file", file, false)
}, },
}) })
defer clean()
for k, v := range map[string]uint32{ for k, v := range map[string]uint32{
"dir": fuse.S_IFDIR | 0755, "dir": fuse.S_IFDIR | 0755,
......
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
func TestRmChildParallel(t *testing.T) { func TestRmChildParallel(t *testing.T) {
want := "hello" want := "hello"
root := &Inode{} root := &Inode{}
_, _, clean := testMount(t, root, &Options{ testMount(t, root, &Options{
FirstAutomaticIno: 1, FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode() n := root.EmbeddedInode()
...@@ -53,5 +53,4 @@ func TestRmChildParallel(t *testing.T) { ...@@ -53,5 +53,4 @@ func TestRmChildParallel(t *testing.T) {
wg.Wait() wg.Wait()
}, },
}) })
defer clean()
} }
...@@ -46,21 +46,21 @@ func (tc *testCase) writeOrig(path, content string, mode os.FileMode) { ...@@ -46,21 +46,21 @@ func (tc *testCase) writeOrig(path, content string, mode os.FileMode) {
} }
} }
func (tc *testCase) Clean() { func (tc *testCase) clean() {
if err := tc.server.Unmount(); err != nil { if err := tc.server.Unmount(); err != nil {
tc.Fatal(err) tc.Fatal(err)
} }
if err := os.RemoveAll(tc.dir); err != nil {
tc.Fatal(err)
}
} }
type testOptions struct { type testOptions struct {
entryCache bool entryCache bool
attrCache bool enableLocks bool
suppressDebug bool attrCache bool
testDir string suppressDebug bool
ro bool testDir string
ro bool
directMount bool // sets MountOptions.DirectMount
directMountStrict bool // sets MountOptions.DirectMountStrict
} }
// newTestCase creates the directories `orig` and `mnt` inside a temporary // newTestCase creates the directories `orig` and `mnt` inside a temporary
...@@ -70,7 +70,7 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase { ...@@ -70,7 +70,7 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
opts = &testOptions{} opts = &testOptions{}
} }
if opts.testDir == "" { if opts.testDir == "" {
opts.testDir = testutil.TempDir() opts.testDir = t.TempDir()
} }
tc := &testCase{ tc := &testCase{
dir: opts.testDir, dir: opts.testDir,
...@@ -107,7 +107,11 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase { ...@@ -107,7 +107,11 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
Logger: log.New(os.Stderr, "", 0), Logger: log.New(os.Stderr, "", 0),
}) })
mOpts := &fuse.MountOptions{} mOpts := &fuse.MountOptions{
DirectMount: opts.directMount,
DirectMountStrict: opts.directMountStrict,
EnableLocks: opts.enableLocks,
}
if !opts.suppressDebug { if !opts.suppressDebug {
mOpts.Debug = testutil.VerboseTest() mOpts.Debug = testutil.VerboseTest()
} }
...@@ -123,12 +127,13 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase { ...@@ -123,12 +127,13 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
if err := tc.server.WaitMount(); err != nil { if err := tc.server.WaitMount(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(tc.clean)
return tc return tc
} }
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
tc.writeOrig("file", "hello", 0644) tc.writeOrig("file", "hello", 0644)
...@@ -162,15 +167,10 @@ func TestFileFdLeak(t *testing.T) { ...@@ -162,15 +167,10 @@ func TestFileFdLeak(t *testing.T) {
attrCache: true, attrCache: true,
entryCache: true, entryCache: true,
}) })
defer func() {
if tc != nil {
tc.Clean()
}
}()
posixtest.FdLeak(t, tc.mntDir) posixtest.FdLeak(t, tc.mntDir)
tc.Clean() tc.clean()
bridge := tc.rawFS.(*rawBridge) bridge := tc.rawFS.(*rawBridge)
tc = nil tc = nil
...@@ -181,7 +181,6 @@ func TestFileFdLeak(t *testing.T) { ...@@ -181,7 +181,6 @@ func TestFileFdLeak(t *testing.T) {
func TestNotifyEntry(t *testing.T) { func TestNotifyEntry(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
orig := tc.origDir + "/file" orig := tc.origDir + "/file"
fn := tc.mntDir + "/file" fn := tc.mntDir + "/file"
...@@ -214,7 +213,6 @@ func TestNotifyEntry(t *testing.T) { ...@@ -214,7 +213,6 @@ func TestNotifyEntry(t *testing.T) {
func TestReadDirStress(t *testing.T) { func TestReadDirStress(t *testing.T) {
tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true})
defer tc.Clean()
// Create 110 entries // Create 110 entries
for i := 0; i < 110; i++ { for i := 0; i < 110; i++ {
...@@ -234,12 +232,11 @@ func TestReadDirStress(t *testing.T) { ...@@ -234,12 +232,11 @@ func TestReadDirStress(t *testing.T) {
return return
} }
_, err = f.Readdirnames(-1) _, err = f.Readdirnames(-1)
f.Close()
if err != nil { if err != nil {
t.Errorf("goroutine %d iteration %d: %v", gr, i, err) t.Errorf("goroutine %d iteration %d: %v", gr, i, err)
f.Close()
return return
} }
f.Close()
} }
} }
...@@ -249,13 +246,13 @@ func TestReadDirStress(t *testing.T) { ...@@ -249,13 +246,13 @@ func TestReadDirStress(t *testing.T) {
go stress(i) go stress(i)
} }
wg.Wait() wg.Wait()
} }
// This test is racy. If an external process consumes space while this // This test is racy. If an external process consumes space while this
// runs, we may see spurious differences between the two statfs() calls. // runs, we may see spurious differences between the two statfs() calls.
func TestStatFs(t *testing.T) { func TestStatFs(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
empty := syscall.Statfs_t{} empty := syscall.Statfs_t{}
orig := empty orig := empty
...@@ -283,7 +280,6 @@ func TestGetAttrParallel(t *testing.T) { ...@@ -283,7 +280,6 @@ func TestGetAttrParallel(t *testing.T) {
// (f)stat in parallel don't lead to fstat on closed files. // (f)stat in parallel don't lead to fstat on closed files.
// We can only test that if we switch off caching // We can only test that if we switch off caching
tc := newTestCase(t, &testOptions{suppressDebug: true}) tc := newTestCase(t, &testOptions{suppressDebug: true})
defer tc.Clean()
N := 100 N := 100
...@@ -324,7 +320,6 @@ func TestGetAttrParallel(t *testing.T) { ...@@ -324,7 +320,6 @@ func TestGetAttrParallel(t *testing.T) {
func TestMknod(t *testing.T) { func TestMknod(t *testing.T) {
tc := newTestCase(t, &testOptions{}) tc := newTestCase(t, &testOptions{})
defer tc.Clean()
modes := map[string]uint32{ modes := map[string]uint32{
"regular": syscall.S_IFREG, "regular": syscall.S_IFREG,
...@@ -358,8 +353,7 @@ func TestMknod(t *testing.T) { ...@@ -358,8 +353,7 @@ func TestMknod(t *testing.T) {
} }
func TestMknodNotSupported(t *testing.T) { func TestMknodNotSupported(t *testing.T) {
mountPoint := testutil.TempDir() mountPoint := t.TempDir()
defer os.Remove(mountPoint)
server, err := Mount(mountPoint, &Inode{}, nil) server, err := Mount(mountPoint, &Inode{}, nil)
if err != nil { if err != nil {
...@@ -385,8 +379,10 @@ func TestPosix(t *testing.T) { ...@@ -385,8 +379,10 @@ func TestPosix(t *testing.T) {
t.Run(nm, func(t *testing.T) { t.Run(nm, func(t *testing.T) {
tc := newTestCase(t, &testOptions{ tc := newTestCase(t, &testOptions{
suppressDebug: noisy[nm], suppressDebug: noisy[nm],
attrCache: true, entryCache: true}) attrCache: true,
defer tc.Clean() entryCache: true,
enableLocks: true,
})
fn(t, tc.mntDir) fn(t, tc.mntDir)
}) })
...@@ -414,7 +410,6 @@ func TestOpenDirectIO(t *testing.T) { ...@@ -414,7 +410,6 @@ func TestOpenDirectIO(t *testing.T) {
} }
tc := newTestCase(t, &opts) tc := newTestCase(t, &opts)
defer tc.Clean()
posixtest.DirectIO(t, tc.mntDir) posixtest.DirectIO(t, tc.mntDir)
} }
...@@ -424,13 +419,12 @@ func TestOpenDirectIO(t *testing.T) { ...@@ -424,13 +419,12 @@ func TestOpenDirectIO(t *testing.T) {
// //
// Note: Run as // Note: Run as
// //
// TMPDIR=/var/tmp go test -run TestFsstress // TMPDIR=/var/tmp go test -run TestFsstress
// //
// to make sure the backing filesystem is ext4. /tmp is tmpfs on modern Linux // to make sure the backing filesystem is ext4. /tmp is tmpfs on modern Linux
// distributions, and tmpfs does not reuse inode numbers, hiding the problem. // distributions, and tmpfs does not reuse inode numbers, hiding the problem.
func TestFsstress(t *testing.T) { func TestFsstress(t *testing.T) {
tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true})
defer tc.Clean()
{ {
old := runtime.GOMAXPROCS(100) old := runtime.GOMAXPROCS(100)
...@@ -591,12 +585,19 @@ func TestFsstress(t *testing.T) { ...@@ -591,12 +585,19 @@ func TestFsstress(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
wg.Add(1)
go func() {
cmd.Wait()
wg.Done()
}()
defer cmd.Process.Kill() defer cmd.Process.Kill()
// Run the test for 1 second. If it deadlocks, it usually does within 20ms. // Run the test for 1 second. If it deadlocks, it usually does within 20ms.
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
cancel() cancel()
cmd.Process.Kill()
// waitTimeout waits for the waitgroup for the specified max timeout. // waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out. // Returns true if waiting timed out.
...@@ -643,7 +644,6 @@ func TestFsstress(t *testing.T) { ...@@ -643,7 +644,6 @@ func TestFsstress(t *testing.T) {
func TestStaleHardlinks(t *testing.T) { 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})
defer tc.Clean()
// "link0" is original file // "link0" is original file
link0 := tc.mntDir + "/link0" link0 := tc.mntDir + "/link0"
......
...@@ -17,16 +17,8 @@ import ( ...@@ -17,16 +17,8 @@ import (
) )
func TestWindowsEmulations(t *testing.T) { func TestWindowsEmulations(t *testing.T) {
mntDir, err := ioutil.TempDir("", "ZipFS") mntDir := t.TempDir()
if err != nil { origDir := t.TempDir()
t.Fatal(err)
}
defer os.RemoveAll(mntDir)
origDir, err := ioutil.TempDir("", "ZipFS")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origDir)
rootData := &fs.LoopbackRoot{ rootData := &fs.LoopbackRoot{
NewNode: newWindowsNode, NewNode: newWindowsNode,
......
...@@ -9,8 +9,11 @@ import ( ...@@ -9,8 +9,11 @@ import (
"bytes" "bytes"
"context" "context"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort"
"strings"
"syscall" "syscall"
"testing" "testing"
...@@ -19,6 +22,7 @@ import ( ...@@ -19,6 +22,7 @@ import (
var testData = map[string]string{ var testData = map[string]string{
"file.txt": "content", "file.txt": "content",
"dir/": "",
"dir/subfile1": "content2", "dir/subfile1": "content2",
"dir/subdir/subfile": "content3", "dir/subdir/subfile": "content3",
} }
...@@ -27,9 +31,17 @@ func createZip(data map[string]string) []byte { ...@@ -27,9 +31,17 @@ func createZip(data map[string]string) []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
zw := zip.NewWriter(buf) zw := zip.NewWriter(buf)
for k, v := range data { var keys []string
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fw, _ := zw.Create(k) fw, _ := zw.Create(k)
fw.Write([]byte(v)) d := []byte(testData[k])
if len(d) > 0 {
fw.Write(d)
}
} }
zw.Close() zw.Close()
...@@ -59,10 +71,7 @@ func TestZipFS(t *testing.T) { ...@@ -59,10 +71,7 @@ func TestZipFS(t *testing.T) {
} }
root := &zipRoot{zr: r} root := &zipRoot{zr: r}
mntDir, err := ioutil.TempDir("", "ZipFS") mntDir := t.TempDir()
if err != nil {
t.Fatal(err)
}
server, err := fs.Mount(mntDir, root, nil) server, err := fs.Mount(mntDir, root, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -70,6 +79,15 @@ func TestZipFS(t *testing.T) { ...@@ -70,6 +79,15 @@ func TestZipFS(t *testing.T) {
defer server.Unmount() defer server.Unmount()
for k, v := range testData { for k, v := range testData {
if strings.HasSuffix(k, "/") {
fi, err := os.Stat(filepath.Join(mntDir, k))
if err != nil {
t.Errorf("stat %s: %v", k, err)
} else if !fi.IsDir() {
t.Errorf("want isdir, got %v", fi)
}
continue
}
c, err := ioutil.ReadFile(filepath.Join(mntDir, k)) c, err := ioutil.ReadFile(filepath.Join(mntDir, k))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -108,10 +126,7 @@ func TestZipFSOnAdd(t *testing.T) { ...@@ -108,10 +126,7 @@ func TestZipFSOnAdd(t *testing.T) {
zr := &zipRoot{zr: r} zr := &zipRoot{zr: r}
root := &fs.Inode{} root := &fs.Inode{}
mnt, err := ioutil.TempDir("", "ZipFS") mnt := t.TempDir()
if err != nil {
t.Fatal(err)
}
server, err := fs.Mount(mnt, root, &fs.Options{ server, err := fs.Mount(mnt, root, &fs.Options{
OnAdd: func(ctx context.Context) { OnAdd: func(ctx context.Context) {
root.AddChild("sub", root.AddChild("sub",
......
...@@ -107,6 +107,11 @@ func (zr *zipRoot) OnAdd(ctx context.Context) { ...@@ -107,6 +107,11 @@ func (zr *zipRoot) OnAdd(ctx context.Context) {
p = ch p = ch
} }
if f.FileInfo().IsDir() {
continue
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{}) ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{})
p.AddChild(base, ch, true) p.AddChild(base, ch, true)
} }
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
// see https://github.com/hanwen/go-fuse/issues/261 for an example of that // see https://github.com/hanwen/go-fuse/issues/261 for an example of that
// problem. // problem.
// //
// Higher level interfaces // # Higher level interfaces
// //
// As said above this packages provides way to implement filesystems in terms of // As said above this packages provides way to implement filesystems in terms of
// raw FUSE protocol. // raw FUSE protocol.
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
// Package github.com/hanwen/go-fuse/v2/fs provides way to implement // Package github.com/hanwen/go-fuse/v2/fs provides way to implement
// filesystems in terms of paths and/or inodes. // filesystems in terms of paths and/or inodes.
// //
// Mount styles // # Mount styles
// //
// The NewServer() handles mounting the filesystem, which // The NewServer() handles mounting the filesystem, which
// involves opening `/dev/fuse` and calling the // involves opening `/dev/fuse` and calling the
...@@ -153,12 +153,42 @@ type MountOptions struct { ...@@ -153,12 +153,42 @@ type MountOptions struct {
// async I/O. Concurrency for synchronous I/O is not limited. // async I/O. Concurrency for synchronous I/O is not limited.
MaxBackground int MaxBackground int
// Write size to use. If 0, use default. This number is // MaxWrite is the max size for read and write requests. If 0, use
// capped at the kernel maximum. // go-fuse default (currently 64 kiB).
// This number is internally capped at MAX_KERNEL_WRITE (higher values don't make
// sense).
//
// Non-direct-io reads are mostly served via kernel readahead, which is
// additionally subject to the MaxReadAhead limit.
//
// Implementation notes:
//
// There's four values the Linux kernel looks at when deciding the request size:
// * MaxWrite, passed via InitOut.MaxWrite. Limits the WRITE size.
// * max_read, passed via a string mount option. Limits the READ size.
// go-fuse sets max_read equal to MaxWrite.
// You can see the current max_read value in /proc/self/mounts .
// * MaxPages, passed via InitOut.MaxPages. In Linux 4.20 and later, the value
// can go up to 1 MiB and go-fuse calculates the MaxPages value acc.
// to MaxWrite, rounding up.
// On older kernels, the value is fixed at 128 kiB and the
// passed value is ignored. No request can be larger than MaxPages, so
// READ and WRITE are effectively capped at MaxPages.
// * MaxReadAhead, passed via InitOut.MaxReadAhead.
MaxWrite int MaxWrite int
// Max read ahead to use. If 0, use default. This number is // MaxReadAhead is the max read ahead size to use. It controls how much data the
// capped at the kernel maximum. // kernel reads in advance to satisfy future read requests from applications.
// How much exactly is subject to clever heuristics in the kernel
// (see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/readahead.c?h=v6.2-rc5#n375
// if you are brave) and hence also depends on the kernel version.
//
// If 0, use kernel default. This number is capped at the kernel maximum
// (128 kiB on Linux) and cannot be larger than MaxWrite.
//
// MaxReadAhead only affects buffered reads (=non-direct-io), but even then, the
// kernel can and does send larger reads to satisfy read reqests from applications
// (up to MaxWrite or VM_READAHEAD_PAGES=128 kiB, whichever is less).
MaxReadAhead int MaxReadAhead int
// If IgnoreSecurityLabels is set, all security related xattr // If IgnoreSecurityLabels is set, all security related xattr
...@@ -193,6 +223,11 @@ type MountOptions struct { ...@@ -193,6 +223,11 @@ type MountOptions struct {
// you must implement the GetLk/SetLk/SetLkw methods. // you must implement the GetLk/SetLk/SetLkw methods.
EnableLocks bool EnableLocks bool
// If set, the kernel caches all Readlink return values. The
// filesystem must use content notification to force the
// kernel to issue a new Readlink call.
EnableSymlinkCaching bool
// If set, ask kernel not to do automatic data cache invalidation. // If set, ask kernel not to do automatic data cache invalidation.
// The filesystem is fully responsible for invalidating data cache. // The filesystem is fully responsible for invalidating data cache.
ExplicitDataCacheControl bool ExplicitDataCacheControl bool
...@@ -217,10 +252,20 @@ type MountOptions struct { ...@@ -217,10 +252,20 @@ type MountOptions struct {
// If set, fuse will first attempt to use syscall.Mount instead of // If set, fuse will first attempt to use syscall.Mount instead of
// fusermount to mount the filesystem. This will not update /etc/mtab // fusermount to mount the filesystem. This will not update /etc/mtab
// but might be needed if fusermount is not available. // but might be needed if fusermount is not available.
// Also, Server.Unmount will attempt syscall.Unmount before calling
// fusermount.
DirectMount bool DirectMount bool
// Options passed to syscall.Mount, the default value used by fusermount // DirectMountStrict is like DirectMount but no fallback to fusermount is
// is syscall.MS_NOSUID|syscall.MS_NODEV // performed. If both DirectMount and DirectMountStrict are set,
// DirectMountStrict wins.
DirectMountStrict bool
// DirectMountFlags are the mountflags passed to syscall.Mount. If zero, the
// default value used by fusermount are used: syscall.MS_NOSUID|syscall.MS_NODEV.
//
// If you actually *want* zero flags, pass syscall.MS_MGC_VAL, which is ignored
// by the kernel. See `man 2 mount` for details about MS_MGC_VAL.
DirectMountFlags uintptr DirectMountFlags uintptr
// EnableAcls enables kernel ACL support. // EnableAcls enables kernel ACL support.
......
...@@ -41,15 +41,26 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd ...@@ -41,15 +41,26 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
source = opts.Name source = opts.Name
} }
var flags uintptr var flags uintptr = syscall.MS_NOSUID | syscall.MS_NODEV
flags |= syscall.MS_NOSUID | syscall.MS_NODEV if opts.DirectMountFlags != 0 {
flags = opts.DirectMountFlags
}
var st syscall.Stat_t
err = syscall.Stat(mountPoint, &st)
if err != nil {
return
}
// some values we need to pass to mount, but override possible since opts.Options comes after // some values we need to pass to mount - we do as fusermount does.
// override possible since opts.Options comes after.
var r = []string{ var r = []string{
fmt.Sprintf("fd=%d", fd), fmt.Sprintf("fd=%d", fd),
"rootmode=40000", fmt.Sprintf("rootmode=%o", st.Mode&syscall.S_IFMT),
"user_id=0", fmt.Sprintf("user_id=%d", os.Geteuid()),
"group_id=0", fmt.Sprintf("group_id=%d", os.Getegid()),
// match what we do with fusermount
fmt.Sprintf("max_read=%d", opts.MaxWrite),
} }
r = append(r, opts.Options...) r = append(r, opts.Options...)
...@@ -57,7 +68,11 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd ...@@ -57,7 +68,11 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
r = append(r, "allow_other") r = append(r, "allow_other")
} }
err = syscall.Mount(opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ",")) if opts.Debug {
log.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)",
source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
}
err = syscall.Mount(source, mountPoint, "fuse."+opts.Name, flags, strings.Join(r, ","))
if err != nil { if err != nil {
syscall.Close(fd) syscall.Close(fd)
return return
...@@ -125,13 +140,16 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { ...@@ -125,13 +140,16 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) {
// Create a FUSE FS on the specified mount point. The returned // Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute. // mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
if opts.DirectMount { if opts.DirectMount || opts.DirectMountStrict {
fd, err := mountDirect(mountPoint, opts, ready) fd, err := mountDirect(mountPoint, opts, ready)
if err == nil { if err == nil {
return fd, nil return fd, nil
} else if opts.Debug { } else if opts.Debug {
log.Printf("mount: failed to do direct mount: %s", err) log.Printf("mount: failed to do direct mount: %s", err)
} }
if opts.DirectMountStrict {
return -1, err
}
} }
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this // Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
...@@ -157,12 +175,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -157,12 +175,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
} }
func unmount(mountPoint string, opts *MountOptions) (err error) { func unmount(mountPoint string, opts *MountOptions) (err error) {
if opts.DirectMount { if opts.DirectMount || opts.DirectMountStrict {
// Attempt to directly unmount, if fails fallback to fusermount method // Attempt to directly unmount, if fails fallback to fusermount method
err := syscall.Unmount(mountPoint, 0) err := syscall.Unmount(mountPoint, 0)
if err == nil { if err == nil {
return nil return nil
} }
if opts.DirectMountStrict {
return err
}
} }
bin, err := fusermountBinary() bin, err := fusermountBinary()
......
...@@ -3,8 +3,11 @@ package fuse ...@@ -3,8 +3,11 @@ package fuse
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"syscall" "syscall"
"testing" "testing"
"github.com/moby/sys/mountinfo"
) )
// TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a // TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a
...@@ -98,3 +101,120 @@ func TestMountMaxWrite(t *testing.T) { ...@@ -98,3 +101,120 @@ func TestMountMaxWrite(t *testing.T) {
}) })
} }
} }
// mountCheckOptions mounts a defaultRawFileSystem and extracts the resulting effective
// mount options from /proc/self/mounts.
// The mount options are a comma-separated string like this:
// rw,nosuid,nodev,relatime,user_id=1026,group_id=1026
func mountCheckOptions(t *testing.T, opts MountOptions) (info mountinfo.Info) {
mnt, err := ioutil.TempDir("", t.Name())
if err != nil {
t.Fatal(err)
}
fs := NewDefaultRawFileSystem()
srv, err := NewServer(fs, mnt, &opts)
if err != nil {
t.Fatal(err)
}
// Check mount options
mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(mnt))
if err != nil {
t.Error(err)
}
if len(mounts) != 1 {
t.Errorf("Could not find mountpoint %q in /proc/self/mountinfo", mnt)
}
orig := *mounts[0]
if testing.Verbose() {
t.Logf("full mountinfo: %#v", orig)
}
// We are only interested in some fields, as the others are arbitrary id numbers
// or contain random strings like "/tmp/TestDirectMount1126361240".
//
// What are all those fields: Look for "/proc/[pid]/mountinfo" in
// https://man7.org/linux/man-pages/man5/proc.5.html .
info = mountinfo.Info{
Options: orig.Options,
Source: orig.Source,
FSType: orig.FSType,
VFSOptions: orig.VFSOptions,
Optional: orig.Optional,
}
// server needs to run for Unmount to work
go srv.Serve()
err = srv.Unmount()
if err != nil {
t.Error(err)
}
return info
}
// TestDirectMount checks that DirectMount and DirectMountStrict work and show the
// same effective mount options in /proc/self/mounts
func TestDirectMount(t *testing.T) {
optsTable := []MountOptions{
{Debug: true},
{Debug: true, AllowOther: true},
{Debug: true, MaxWrite: 9999},
{Debug: true, FsName: "aaa"},
{Debug: true, Name: "bbb"},
{Debug: true, FsName: "ccc", Name: "ddd"},
{Debug: true, FsName: "a,b"},
{Debug: true, FsName: `a\b`},
{Debug: true, FsName: `a\,b`},
}
for _, opts := range optsTable {
// Without DirectMount - i.e. using fusermount
o1 := mountCheckOptions(t, opts)
// With DirectMount
opts.DirectMount = true
o2 := mountCheckOptions(t, opts)
if o2 != o1 {
t.Errorf(`DirectMount effective mount options mismatch:
DirectMount: %#v
fusermount: %#v`, o2, o1)
// When this already fails then DirectMountStrict will fail the same way.
// Skip it for less noise in the logs.
continue
}
if os.Geteuid() == 0 {
// With DirectMountStrict
opts.DirectMountStrict = true
o3 := mountCheckOptions(t, opts)
if o3 != o1 {
t.Errorf(`DirectMountStrict effective mount options mismatch:
DirectMountStrict: %#v
fusermount: %#v`, o3, o1)
}
}
}
}
// TestEscapedMountOption tests that fusermount doesn't exit when when using commas or backslashs in options.
// It also tests that commas or backslashs in options are correctly propagated to /proc/mounts.
func TestEscapedMountOption(t *testing.T) {
fsname := `fsname,with\,many,comm\as,and\backsl\\ashs`
opts := &MountOptions{
FsName: fsname,
}
mnt := t.TempDir()
fs := NewDefaultRawFileSystem()
srv, err := NewServer(fs, mnt, opts)
if err != nil {
t.Error(err)
}
go srv.Serve()
defer srv.Unmount()
mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(mnt))
if err != nil {
t.Fatal(err)
}
if len(mounts) != 1 {
t.Fatalf("Could not find mountpoint %q in /proc/self/mountinfo", mnt)
}
m := *mounts[0]
if m.Source != fsname {
t.Errorf("mountinfo(%q): got %q want %q", mnt, m.Source, fsname)
}
}
...@@ -70,6 +70,12 @@ const ( ...@@ -70,6 +70,12 @@ const (
_OP_NOTIFY_DELETE = uint32(104) // protocol version 18 _OP_NOTIFY_DELETE = uint32(104) // protocol version 18
_OPCODE_COUNT = uint32(105) _OPCODE_COUNT = uint32(105)
// Constants from Linux kernel fs/fuse/fuse_i.h
// Default MaxPages value in all kernel versions
_FUSE_DEFAULT_MAX_PAGES_PER_REQ = 32
// Upper MaxPages limit in Linux v4.20+ (v4.19 and older: 32)
_FUSE_MAX_MAX_PAGES = 256
) )
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -90,12 +96,14 @@ func doInit(server *Server, req *request) { ...@@ -90,12 +96,14 @@ func doInit(server *Server, req *request) {
server.reqMu.Lock() server.reqMu.Lock()
server.kernelSettings = *input server.kernelSettings = *input
server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS | server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS) CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS | CAP_MAX_PAGES)
if server.opts.EnableLocks { if server.opts.EnableLocks {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
} }
if server.opts.EnableSymlinkCaching {
server.kernelSettings.Flags |= CAP_CACHE_SYMLINKS
}
if server.opts.EnableAcl { if server.opts.EnableAcl {
server.kernelSettings.Flags |= CAP_POSIX_ACL server.kernelSettings.Flags |= CAP_POSIX_ACL
} }
...@@ -123,6 +131,11 @@ func doInit(server *Server, req *request) { ...@@ -123,6 +131,11 @@ func doInit(server *Server, req *request) {
if input.Minor >= 13 { if input.Minor >= 13 {
server.setSplice() server.setSplice()
} }
// maxPages is the maximum request size we want the kernel to use, in units of
// memory pages (usually 4kiB). Linux v4.19 and older ignore this and always use
// 128kiB.
maxPages := (server.opts.MaxWrite-1)/syscall.Getpagesize() + 1 // Round up
server.reqMu.Unlock() server.reqMu.Unlock()
out := (*InitOut)(req.outData()) out := (*InitOut)(req.outData())
...@@ -134,6 +147,7 @@ func doInit(server *Server, req *request) { ...@@ -134,6 +147,7 @@ func doInit(server *Server, req *request) {
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),
} }
if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead { if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead {
...@@ -536,6 +550,7 @@ func getHandler(o uint32) *operationHandler { ...@@ -536,6 +550,7 @@ func getHandler(o uint32) *operationHandler {
return operationHandlers[o] return operationHandlers[o]
} }
// maximum size of all input headers
var maxInputSize uintptr var maxInputSize uintptr
func init() { func init() {
...@@ -771,6 +786,7 @@ func init() { ...@@ -771,6 +786,7 @@ func init() {
_OP_READ: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, _OP_READ: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) },
_OP_WRITE: func(ptr unsafe.Pointer) interface{} { return (*WriteIn)(ptr) }, _OP_WRITE: func(ptr unsafe.Pointer) interface{} { return (*WriteIn)(ptr) },
_OP_READDIR: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, _OP_READDIR: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) },
_OP_FSYNCDIR: func(ptr unsafe.Pointer) interface{} { return (*FsyncIn)(ptr) },
_OP_ACCESS: func(ptr unsafe.Pointer) interface{} { return (*AccessIn)(ptr) }, _OP_ACCESS: func(ptr unsafe.Pointer) interface{} { return (*AccessIn)(ptr) },
_OP_FORGET: func(ptr unsafe.Pointer) interface{} { return (*ForgetIn)(ptr) }, _OP_FORGET: func(ptr unsafe.Pointer) interface{} { return (*ForgetIn)(ptr) },
_OP_BATCH_FORGET: func(ptr unsafe.Pointer) interface{} { return (*_BatchForgetIn)(ptr) }, _OP_BATCH_FORGET: func(ptr unsafe.Pointer) interface{} { return (*_BatchForgetIn)(ptr) },
......
...@@ -6,17 +6,12 @@ package pathfs ...@@ -6,17 +6,12 @@ package pathfs
import ( import (
"io/ioutil" "io/ioutil"
"os"
"testing" "testing"
"github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestCopyFile(t *testing.T) { func TestCopyFile(t *testing.T) {
d1 := testutil.TempDir() d1 := t.TempDir()
defer os.RemoveAll(d1) d2 := t.TempDir()
d2 := testutil.TempDir()
defer os.RemoveAll(d2)
fs1 := NewLoopbackFileSystem(d1) fs1 := NewLoopbackFileSystem(d1)
fs2 := NewLoopbackFileSystem(d2) fs2 := NewLoopbackFileSystem(d2)
......
...@@ -35,7 +35,7 @@ func (fs *ownerFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse ...@@ -35,7 +35,7 @@ func (fs *ownerFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse
} }
func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup func()) { func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup func()) {
wd := testutil.TempDir() wd := t.TempDir()
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
fs := &ownerFs{NewDefaultFileSystem()} fs := &ownerFs{NewDefaultFileSystem()}
...@@ -50,7 +50,6 @@ func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup ...@@ -50,7 +50,6 @@ func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup
} }
return wd, func() { return wd, func() {
state.Unmount() state.Unmount()
os.RemoveAll(wd)
} }
} }
......
...@@ -2,20 +2,23 @@ ...@@ -2,20 +2,23 @@
// 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
// +build linux // +build linux
package pathfs package pathfs
import ( import (
"os" "os"
"path/filepath"
"reflect" "reflect"
"syscall" "syscall"
"testing" "testing"
) )
func TestSysUtimensat(t *testing.T) { func TestSysUtimensat(t *testing.T) {
symlink := "/tmp/TestSysUtimensat" dir := t.TempDir()
os.Remove(symlink)
symlink := filepath.Join(dir, "symlink")
err := os.Symlink("/nonexisting/file", symlink) err := os.Symlink("/nonexisting/file", symlink)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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
// +build linux // +build linux
package pathfs package pathfs
...@@ -15,7 +16,6 @@ import ( ...@@ -15,7 +16,6 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
) )
var xattrGolden = map[string][]byte{ var xattrGolden = map[string][]byte{
...@@ -102,7 +102,7 @@ func (fs *XAttrTestFs) RemoveXAttr(name string, attr string, context *fuse.Conte ...@@ -102,7 +102,7 @@ func (fs *XAttrTestFs) RemoveXAttr(name string, attr string, context *fuse.Conte
func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint string, cleanup func()) { func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint string, cleanup func()) {
xfs := NewXAttrFs(nm, m) xfs := NewXAttrFs(nm, m)
mountPoint = testutil.TempDir() mountPoint = t.TempDir()
nfs := NewPathNodeFs(xfs, nil) nfs := NewPathNodeFs(xfs, nil)
state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(),
...@@ -114,7 +114,6 @@ func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint str ...@@ -114,7 +114,6 @@ func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint str
go state.Serve() go state.Serve()
return mountPoint, func() { return mountPoint, func() {
state.Unmount() state.Unmount()
os.RemoveAll(mountPoint)
} }
} }
......
...@@ -77,6 +77,9 @@ var ( ...@@ -77,6 +77,9 @@ var (
W_OK: "w", W_OK: "w",
R_OK: "r", R_OK: "r",
} }
getAttrFlagNames = map[int64]string{
FUSE_GETATTR_FH: "FH",
}
) )
func flagString(names map[int64]string, fl int64, def string) string { func flagString(names map[int64]string, fl int64, def string) string {
...@@ -209,7 +212,7 @@ func (o *AttrOut) string() string { ...@@ -209,7 +212,7 @@ func (o *AttrOut) string() string {
// ft converts (seconds , nanoseconds) -> float(seconds) // ft converts (seconds , nanoseconds) -> float(seconds)
func ft(tsec uint64, tnsec uint32) float64 { func ft(tsec uint64, tnsec uint32) float64 {
return float64(tsec) + float64(tnsec)*1E-9 return float64(tsec) + float64(tnsec)*1e-9
} }
// Returned by LOOKUP // Returned by LOOKUP
......
...@@ -37,7 +37,7 @@ func (in *CreateIn) string() string { ...@@ -37,7 +37,7 @@ func (in *CreateIn) string() string {
} }
func (in *GetAttrIn) string() string { func (in *GetAttrIn) string() string {
return fmt.Sprintf("{Fh %d}", in.Fh_) return fmt.Sprintf("{Fh %d %s}", in.Fh_, flagString(getAttrFlagNames, int64(in.Flags_), ""))
} }
func (in *MknodIn) string() string { func (in *MknodIn) string() string {
......
...@@ -21,13 +21,16 @@ import ( ...@@ -21,13 +21,16 @@ import (
) )
const ( const (
// The kernel caps writes at 128k. // Linux v4.20+ caps requests at 1 MiB. Older kernels at 128 kiB.
MAX_KERNEL_WRITE = 128 * 1024 MAX_KERNEL_WRITE = 1024 * 1024
// Linux kernel constant from include/uapi/linux/fuse.h // Linux kernel constant from include/uapi/linux/fuse.h
// Reads from /dev/fuse that are smaller fail with EINVAL. // Reads from /dev/fuse that are smaller fail with EINVAL.
_FUSE_MIN_READ_BUFFER = 8192 _FUSE_MIN_READ_BUFFER = 8192
// defaultMaxWrite is the default value for MountOptions.MaxWrite
defaultMaxWrite = 128 * 1024 // 128 kiB
minMaxReaders = 2 minMaxReaders = 2
maxMaxReaders = 16 maxMaxReaders = 16
) )
...@@ -113,14 +116,14 @@ func (ms *Server) RecordLatencies(l LatencyMap) { ...@@ -113,14 +116,14 @@ func (ms *Server) RecordLatencies(l LatencyMap) {
// Unmount calls fusermount -u on the mount. This has the effect of // Unmount calls fusermount -u on the mount. This has the effect of
// shutting down the filesystem. After the Server is unmounted, it // shutting down the filesystem. After the Server is unmounted, it
// should be discarded. // should be discarded. This function is idempotent.
// //
// Does not work when we were mounted with the magic /dev/fd/N mountpoint syntax, // Does not work when we were mounted with the magic /dev/fd/N mountpoint syntax,
// as we do not know the real mountpoint. Unmount using // as we do not know the real mountpoint. Unmount using
// //
// fusermount -u /path/to/real/mountpoint // fusermount -u /path/to/real/mountpoint
// //
/// in this case. // in this case.
func (ms *Server) Unmount() (err error) { func (ms *Server) Unmount() (err error) {
if ms.mountPoint == "" { if ms.mountPoint == "" {
return nil return nil
...@@ -167,11 +170,12 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -167,11 +170,12 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
o.MaxWrite = 0 o.MaxWrite = 0
} }
if o.MaxWrite == 0 { if o.MaxWrite == 0 {
o.MaxWrite = 1 << 16 o.MaxWrite = defaultMaxWrite
} }
if o.MaxWrite > MAX_KERNEL_WRITE { if o.MaxWrite > MAX_KERNEL_WRITE {
o.MaxWrite = MAX_KERNEL_WRITE o.MaxWrite = MAX_KERNEL_WRITE
} }
if o.Name == "" { if o.Name == "" {
name := fs.String() name := fs.String()
l := len(name) l := len(name)
...@@ -181,12 +185,6 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -181,12 +185,6 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
o.Name = strings.Replace(name[:l], ",", ";", -1) o.Name = strings.Replace(name[:l], ",", ";", -1)
} }
for _, s := range o.optionsStrings() {
if strings.Contains(s, ",") {
return nil, fmt.Errorf("found ',' in option string %q", s)
}
}
maxReaders := runtime.GOMAXPROCS(0) maxReaders := runtime.GOMAXPROCS(0)
if maxReaders < minMaxReaders { if maxReaders < minMaxReaders {
maxReaders = minMaxReaders maxReaders = minMaxReaders
...@@ -247,6 +245,10 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -247,6 +245,10 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
return ms, nil return ms, nil
} }
func escape(optionValue string) string {
return strings.Replace(strings.Replace(optionValue, `\`, `\\`, -1), `,`, `\,`, -1)
}
func (o *MountOptions) optionsStrings() []string { func (o *MountOptions) optionsStrings() []string {
var r []string var r []string
r = append(r, o.Options...) r = append(r, o.Options...)
...@@ -254,13 +256,13 @@ func (o *MountOptions) optionsStrings() []string { ...@@ -254,13 +256,13 @@ func (o *MountOptions) optionsStrings() []string {
if o.AllowOther { if o.AllowOther {
r = append(r, "allow_other") r = append(r, "allow_other")
} }
if o.FsName != "" { if o.FsName != "" {
r = append(r, "fsname="+o.FsName) r = append(r, "fsname="+o.FsName)
} }
if o.Name != "" { if o.Name != "" {
r = append(r, "subtype="+o.Name) r = append(r, "subtype="+o.Name)
} }
r = append(r, fmt.Sprintf("max_read=%d", o.MaxWrite))
// OSXFUSE applies a 60-second timeout for file operations. This // OSXFUSE applies a 60-second timeout for file operations. This
// is inconsistent with how FUSE works on Linux, where operations // is inconsistent with how FUSE works on Linux, where operations
...@@ -269,7 +271,15 @@ func (o *MountOptions) optionsStrings() []string { ...@@ -269,7 +271,15 @@ func (o *MountOptions) optionsStrings() []string {
r = append(r, "daemon_timeout=0") r = append(r, "daemon_timeout=0")
} }
return r // Commas and backslashs in an option need to be escaped, because
// options are separated by a comma and backslashs are used to
// escape other characters.
var rEscaped []string
for _, s := range r {
rEscaped = append(rEscaped, escape(s))
}
return rEscaped
} }
// DebugData returns internal status information for debugging // DebugData returns internal status information for debugging
......
...@@ -19,12 +19,12 @@ func (s *Server) setSplice() { ...@@ -19,12 +19,12 @@ func (s *Server) setSplice() {
// //
// This is a four-step process: // This is a four-step process:
// //
// 1) Splice data form fdData.Fd into the "pair1" pipe buffer --> pair1: [payload] // 1. Splice data form fdData.Fd into the "pair1" pipe buffer --> pair1: [payload]
// Now we know the actual payload length and can // Now we know the actual payload length and can
// construct the reply header // construct the reply header
// 2) Write header into the "pair2" pipe buffer --> pair2: [header] // 2. Write header into the "pair2" pipe buffer --> pair2: [header]
// 4) Splice data from "pair1" into "pair2" --> pair2: [header][payload] // 4. Splice data from "pair1" into "pair2" --> pair2: [header][payload]
// 3) Splice the data from "pair2" into /dev/fuse // 3. Splice the data from "pair2" into /dev/fuse
// //
// This dance is neccessary because header and payload cannot be split across // This dance is neccessary because header and payload cannot be split across
// two splices and we cannot seek in a pipe buffer. // two splices and we cannot seek in a pipe buffer.
......
...@@ -37,7 +37,7 @@ func (fs *cacheFs) Open(name string, flags uint32, context *fuse.Context) (fuseF ...@@ -37,7 +37,7 @@ func (fs *cacheFs) Open(name string, flags uint32, context *fuse.Context) (fuseF
} }
func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) { func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
dir := testutil.TempDir() dir := t.TempDir()
os.Mkdir(dir+"/mnt", 0755) os.Mkdir(dir+"/mnt", 0755)
os.Mkdir(dir+"/orig", 0755) os.Mkdir(dir+"/orig", 0755)
...@@ -65,10 +65,7 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) { ...@@ -65,10 +65,7 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
t.Fatal("WaitMount", err) t.Fatal("WaitMount", err)
} }
return dir, pfs, func() { return dir, pfs, func() {
err := state.Unmount() state.Unmount()
if err == nil {
os.RemoveAll(dir)
}
} }
} }
...@@ -102,11 +99,15 @@ func TestFopenKeepCache(t *testing.T) { ...@@ -102,11 +99,15 @@ func TestFopenKeepCache(t *testing.T) {
return st return st
} }
// XXX Linux FUSE client automatically invalidates cache of a file if it sees size change. // Without CAP_EXPLICIT_INVAL_DATA Linux FUSE client automatically
// As workaround we keep len(before) == len(after) to avoid that codepath. // invalidates cache of a file if it sees size change. As workaround we
// See https://github.com/hanwen/go-fuse/pull/273 for details. // keep len(before) == len(after) to avoid that codepath.
// //
// TODO use len(before) != len(after) if kernel supports precise control over data cache. // With CAP_EXPLICIT_INVAL_DATA - even when data cache stays the same,
// but st_size changes, Linux reads file content as dcache[:st_size],
// i.e. either truncated (st_size↓) or extended (st_size↑). Here
// len(before) == len(after) also helps to avoid dealing with those
// peculiarities.
before := "before" before := "before"
after := "afterX" after := "afterX"
if len(before) != len(after) { if len(before) != len(after) {
...@@ -133,7 +134,8 @@ func TestFopenKeepCache(t *testing.T) { ...@@ -133,7 +134,8 @@ func TestFopenKeepCache(t *testing.T) {
// this forces kernel client to relookup/regetattr the file and reread the attributes. // this forces kernel client to relookup/regetattr the file and reread the attributes.
// //
// this way we make sure the kernel knows updated size/mtime before we // this way we make sure the kernel knows updated size/mtime before we
// try to read the file next time. // try to read the file next time, which should invalidate data cache
// if CAP_EXPLICIT_INVAL_DATA was not negotiated.
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
_ = xstat(wd + "/mnt/file.txt") _ = xstat(wd + "/mnt/file.txt")
...@@ -146,9 +148,9 @@ func TestFopenKeepCache(t *testing.T) { ...@@ -146,9 +148,9 @@ func TestFopenKeepCache(t *testing.T) {
t.Skipf("protocol v%d has no notify support.", minor) t.Skipf("protocol v%d has no notify support.", minor)
} }
code := pathfs.EntryNotify("", "file.txt") code := pathfs.FileNotify("file.txt", 0, 0) // invalidate direntry and whole data cache
if !code.Ok() { if !code.Ok() {
t.Errorf("EntryNotify: %v", code) t.Errorf("FileNotify: %v", code)
} }
c = xreadFile(wd + "/mnt/file.txt") c = xreadFile(wd + "/mnt/file.txt")
...@@ -186,8 +188,7 @@ func TestNonseekable(t *testing.T) { ...@@ -186,8 +188,7 @@ func TestNonseekable(t *testing.T) {
fs := &nonseekFs{FileSystem: pathfs.NewDefaultFileSystem()} fs := &nonseekFs{FileSystem: pathfs.NewDefaultFileSystem()}
fs.Length = 200 * 1024 fs.Length = 200 * 1024
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
nfs := pathfs.NewPathNodeFs(fs, nil) nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions() opts := nodefs.NewOptions()
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
...@@ -216,8 +217,7 @@ func TestNonseekable(t *testing.T) { ...@@ -216,8 +217,7 @@ func TestNonseekable(t *testing.T) {
} }
func TestGetAttrRace(t *testing.T) { func TestGetAttrRace(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
os.Mkdir(dir+"/mnt", 0755) os.Mkdir(dir+"/mnt", 0755)
os.Mkdir(dir+"/orig", 0755) os.Mkdir(dir+"/orig", 0755)
......
...@@ -52,13 +52,7 @@ func (d *DataNode) Read(_ nodefs.File, dest []byte, off int64, _ *fuse.Context) ...@@ -52,13 +52,7 @@ func (d *DataNode) Read(_ nodefs.File, dest []byte, off int64, _ *fuse.Context)
// TestCacheControl verifies that FUSE server process can store/retrieve kernel data cache. // TestCacheControl verifies that FUSE server process can store/retrieve kernel data cache.
func TestCacheControl(t *testing.T) { func TestCacheControl(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer func() {
err := os.Remove(dir)
if err != nil {
t.Fatal(err)
}
}()
// setup a filesystem with 1 file // setup a filesystem with 1 file
root := nodefs.NewDefaultNode() root := nodefs.NewDefaultNode()
......
...@@ -16,8 +16,7 @@ import ( ...@@ -16,8 +16,7 @@ import (
) )
func TestDefaultNodeGetAttr(t *testing.T) { func TestDefaultNodeGetAttr(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
opts := &nodefs.Options{ opts := &nodefs.Options{
// Note: defaultNode.GetAttr() calling file.GetAttr() is only useful if // Note: defaultNode.GetAttr() calling file.GetAttr() is only useful if
......
...@@ -6,7 +6,6 @@ package test ...@@ -6,7 +6,6 @@ package test
import ( import (
"io/ioutil" "io/ioutil"
"os"
"testing" "testing"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
...@@ -42,7 +41,7 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) { ...@@ -42,7 +41,7 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) {
} }
var err error var err error
dir := testutil.TempDir() dir := t.TempDir()
pathfs := pathfs.NewPathNodeFs(fs, nil) pathfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions() opts := nodefs.NewOptions()
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
...@@ -57,7 +56,6 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) { ...@@ -57,7 +56,6 @@ func defaultReadTest(t *testing.T) (root string, cleanup func()) {
} }
return dir, func() { return dir, func() {
state.Unmount() state.Unmount()
os.Remove(dir)
} }
} }
......
...@@ -34,8 +34,7 @@ func (f *flipNode) GetAttr(out *fuse.Attr, file nodefs.File, c *fuse.Context) fu ...@@ -34,8 +34,7 @@ func (f *flipNode) GetAttr(out *fuse.Attr, file nodefs.File, c *fuse.Context) fu
} }
func TestDeleteNotify(t *testing.T) { func TestDeleteNotify(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
root := nodefs.NewMemNodeFSRoot(dir + "/backing") root := nodefs.NewMemNodeFSRoot(dir + "/backing")
conn := nodefs.NewFileSystemConnector(root, conn := nodefs.NewFileSystemConnector(root,
&nodefs.Options{PortableInodes: true}) &nodefs.Options{PortableInodes: true})
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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
// +build linux // +build linux
package test package test
...@@ -122,8 +123,7 @@ func TestFlockInvoked(t *testing.T) { ...@@ -122,8 +123,7 @@ func TestFlockInvoked(t *testing.T) {
t.Skip("flock command not found.") t.Skip("flock command not found.")
} }
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
opts := &nodefs.Options{ opts := &nodefs.Options{
Owner: fuse.CurrentOwner(), Owner: fuse.CurrentOwner(),
...@@ -187,17 +187,8 @@ func TestNoLockSupport(t *testing.T) { ...@@ -187,17 +187,8 @@ func TestNoLockSupport(t *testing.T) {
t.Skip("flock command not found.") t.Skip("flock command not found.")
} }
tmp, err := ioutil.TempDir("", "TestNoLockSupport") tmp := t.TempDir()
if err != nil { mnt := t.TempDir()
t.Fatal(err)
}
mnt, err := ioutil.TempDir("", "TestNoLockSupport")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
defer os.RemoveAll(mnt)
opts := &nodefs.Options{ opts := &nodefs.Options{
Owner: fuse.CurrentOwner(), Owner: fuse.CurrentOwner(),
......
...@@ -143,7 +143,7 @@ func NewFile() *MutableDataFile { ...@@ -143,7 +143,7 @@ func NewFile() *MutableDataFile {
} }
func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func()) { func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func()) {
dir = testutil.TempDir() dir = t.TempDir()
nfs := pathfs.NewPathNodeFs(fs, nil) nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions() opts := nodefs.NewOptions()
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
...@@ -161,8 +161,6 @@ func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func( ...@@ -161,8 +161,6 @@ func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func(
clean = func() { clean = func() {
if err := state.Unmount(); err != nil { if err := state.Unmount(); err != nil {
t.Errorf("cleanup: Unmount: %v", err) t.Errorf("cleanup: Unmount: %v", err)
} else {
os.RemoveAll(dir)
} }
} }
......
...@@ -17,7 +17,6 @@ import ( ...@@ -17,7 +17,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
) )
var enableOverlayfsTest bool var enableOverlayfsTest bool
...@@ -147,11 +146,9 @@ func TestOverlayfs(t *testing.T) { ...@@ -147,11 +146,9 @@ func TestOverlayfs(t *testing.T) {
tc.Mkdir(tc.origSubdir, 0777) tc.Mkdir(tc.origSubdir, 0777)
tc.WriteFile(filepath.Join(tc.origSubdir, testfile), content, 0700) tc.WriteFile(filepath.Join(tc.origSubdir, testfile), content, 0700)
tmpMergedDir := testutil.TempDir() tmpMergedDir := t.TempDir()
defer os.RemoveAll(tmpMergedDir) tmpWorkDir := t.TempDir()
tmpWorkDir := testutil.TempDir() tmpUpperDir := t.TempDir()
defer os.RemoveAll(tmpWorkDir)
tmpUpperDir := testutil.TempDir()
defer os.RemoveAll(tmpUpperDir) defer os.RemoveAll(tmpUpperDir)
if err := unix.Mount("overlay", tmpMergedDir, "overlay", 0, if err := unix.Mount("overlay", tmpMergedDir, "overlay", 0,
fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", tc.mnt, tmpUpperDir, tmpWorkDir)); err != nil { fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", tc.mnt, tmpUpperDir, tmpWorkDir)); err != nil {
......
...@@ -77,7 +77,7 @@ func NewTestCase(t *testing.T) *testCase { ...@@ -77,7 +77,7 @@ func NewTestCase(t *testing.T) *testCase {
const subdir string = "subdir" const subdir string = "subdir"
var err error var err error
tc.tmpDir = testutil.TempDir() tc.tmpDir = t.TempDir()
tc.orig = tc.tmpDir + "/orig" tc.orig = tc.tmpDir + "/orig"
tc.mnt = tc.tmpDir + "/mnt" tc.mnt = tc.tmpDir + "/mnt"
...@@ -125,7 +125,6 @@ func (tc *testCase) Cleanup() { ...@@ -125,7 +125,6 @@ func (tc *testCase) Cleanup() {
if err != nil { if err != nil {
tc.tester.Fatalf("Unmount failed: %v", err) tc.tester.Fatalf("Unmount failed: %v", err)
} }
os.RemoveAll(tc.tmpDir)
} }
func (tc *testCase) rootNode() *nodefs.Inode { func (tc *testCase) rootNode() *nodefs.Inode {
...@@ -774,8 +773,7 @@ func TestNonVerboseFStatFs(t *testing.T) { ...@@ -774,8 +773,7 @@ func TestNonVerboseFStatFs(t *testing.T) {
} }
func TestOriginalIsSymlink(t *testing.T) { func TestOriginalIsSymlink(t *testing.T) {
tmpDir := testutil.TempDir() tmpDir := t.TempDir()
defer os.RemoveAll(tmpDir)
orig := tmpDir + "/orig" orig := tmpDir + "/orig"
err := os.Mkdir(orig, 0755) err := os.Mkdir(orig, 0755)
if err != nil { if err != nil {
......
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestMountOnExisting(t *testing.T) { func TestMountOnExisting(t *testing.T) {
...@@ -171,8 +170,7 @@ func TestDeletedUnmount(t *testing.T) { ...@@ -171,8 +170,7 @@ func TestDeletedUnmount(t *testing.T) {
} }
func TestDefaultNodeMount(t *testing.T) { func TestDefaultNodeMount(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
root := nodefs.NewDefaultNode() root := nodefs.NewDefaultNode()
s, conn, err := nodefs.MountRoot(dir, root, nil) s, conn, err := nodefs.MountRoot(dir, root, nil)
if err != nil { if err != nil {
...@@ -198,8 +196,7 @@ func TestDefaultNodeMount(t *testing.T) { ...@@ -198,8 +196,7 @@ func TestDefaultNodeMount(t *testing.T) {
} }
func TestLiveness(t *testing.T) { func TestLiveness(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
root := nodefs.NewDefaultNode() root := nodefs.NewDefaultNode()
s, _, err := nodefs.MountRoot(dir, root, nil) s, _, err := nodefs.MountRoot(dir, root, nil)
if err != nil { if err != nil {
......
...@@ -28,13 +28,7 @@ func (d *truncatableFile) Truncate(file nodefs.File, size uint64, context *fuse. ...@@ -28,13 +28,7 @@ func (d *truncatableFile) Truncate(file nodefs.File, size uint64, context *fuse.
// TestNilFileTruncation verifies that the FUSE server process does not // TestNilFileTruncation verifies that the FUSE server process does not
// crash when file truncation is performed on nil file handles. // crash when file truncation is performed on nil file handles.
func TestNilFileTruncation(t *testing.T) { func TestNilFileTruncation(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer func() {
err := os.Remove(dir)
if err != nil {
t.Fatal(err)
}
}()
root := nodefs.NewDefaultNode() root := nodefs.NewDefaultNode()
opts := nodefs.NewOptions() opts := nodefs.NewOptions()
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"testing" "testing"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
...@@ -45,7 +44,6 @@ func (r *tRoot) Lookup(out *fuse.Attr, name string, fctx *fuse.Context) (*nodefs ...@@ -45,7 +44,6 @@ func (r *tRoot) Lookup(out *fuse.Attr, name string, fctx *fuse.Context) (*nodefs
return node.Inode(), st return node.Inode(), st
} }
// verifyFileRead verifies that file @path has content == dataOK. // verifyFileRead verifies that file @path has content == dataOK.
func verifyFileRead(path string, dataOK string) error { func verifyFileRead(path string, dataOK string) error {
v, err := ioutil.ReadFile(path) v, err := ioutil.ReadFile(path)
...@@ -59,13 +57,7 @@ func verifyFileRead(path string, dataOK string) error { ...@@ -59,13 +57,7 @@ func verifyFileRead(path string, dataOK string) error {
} }
func TestNodeParallelLookup(t *testing.T) { func TestNodeParallelLookup(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer func() {
err := os.Remove(dir)
if err != nil {
t.Fatal(err)
}
}()
root := &tRoot{ root := &tRoot{
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
...@@ -99,7 +91,7 @@ func TestNodeParallelLookup(t *testing.T) { ...@@ -99,7 +91,7 @@ func TestNodeParallelLookup(t *testing.T) {
}() }()
// the test will deadlock if the client cannot issue several lookups simultaneously // the test will deadlock if the client cannot issue several lookups simultaneously
if srv.KernelSettings().Flags & fuse.CAP_PARALLEL_DIROPS == 0 { if srv.KernelSettings().Flags&fuse.CAP_PARALLEL_DIROPS == 0 {
t.Skip("Kernel serializes dir lookups") t.Skip("Kernel serializes dir lookups")
} }
...@@ -110,10 +102,10 @@ func TestNodeParallelLookup(t *testing.T) { ...@@ -110,10 +102,10 @@ func TestNodeParallelLookup(t *testing.T) {
defer cancel() defer cancel()
wg, ctx := errgroup.WithContext(ctx0) wg, ctx := errgroup.WithContext(ctx0)
wg.Go(func() error { wg.Go(func() error {
return verifyFileRead(dir + "/hello", "abc") return verifyFileRead(dir+"/hello", "abc")
}) })
wg.Go(func() error { wg.Go(func() error {
return verifyFileRead(dir + "/world", "def") return verifyFileRead(dir+"/world", "def")
}) })
// wait till both threads queue into Lookup // wait till both threads queue into Lookup
......
...@@ -61,7 +61,7 @@ func (n *rootNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (* ...@@ -61,7 +61,7 @@ func (n *rootNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*
} }
func TestUpdateNode(t *testing.T) { func TestUpdateNode(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
root := &rootNode{ root := &rootNode{
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
backing: map[string]string{"a": "aaa"}, backing: map[string]string{"a": "aaa"},
......
...@@ -6,7 +6,6 @@ package test ...@@ -6,7 +6,6 @@ package test
import ( import (
"io/ioutil" "io/ioutil"
"os"
"sync/atomic" "sync/atomic"
"testing" "testing"
...@@ -51,13 +50,7 @@ func (d *NoFileNode) Open(flags uint32, context *fuse.Context) (nodefs.File, fus ...@@ -51,13 +50,7 @@ func (d *NoFileNode) Open(flags uint32, context *fuse.Context) (nodefs.File, fus
} }
func TestNoFile(t *testing.T) { func TestNoFile(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer func() {
err := os.Remove(dir)
if err != nil {
t.Fatal(err)
}
}()
// setup a filesystem with 2 files: // setup a filesystem with 2 files:
// //
......
...@@ -78,7 +78,7 @@ type NotifyTest struct { ...@@ -78,7 +78,7 @@ type NotifyTest struct {
func NewNotifyTest(t *testing.T) *NotifyTest { func NewNotifyTest(t *testing.T) *NotifyTest {
me := &NotifyTest{} me := &NotifyTest{}
me.fs = newNotifyFs() me.fs = newNotifyFs()
me.dir = testutil.TempDir() me.dir = t.TempDir()
entryTTL := 100 * time.Millisecond entryTTL := 100 * time.Millisecond
opts := &nodefs.Options{ opts := &nodefs.Options{
EntryTimeout: entryTTL, EntryTimeout: entryTTL,
......
...@@ -34,7 +34,7 @@ func (fs *umaskFS) Mkdir(name string, mode uint32, context *fuse.Context) (code ...@@ -34,7 +34,7 @@ func (fs *umaskFS) Mkdir(name string, mode uint32, context *fuse.Context) (code
} }
func TestUmask(t *testing.T) { func TestUmask(t *testing.T) {
tmpDir := testutil.TempDir() tmpDir := t.TempDir()
orig := tmpDir + "/orig" orig := tmpDir + "/orig"
mnt := tmpDir + "/mnt" mnt := tmpDir + "/mnt"
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
// 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
// +build linux // +build linux
package test package test
import ( import (
"os"
"path/filepath" "path/filepath"
"syscall" "syscall"
"testing" "testing"
...@@ -39,8 +39,7 @@ func (n *xattrChildNode) GetXAttr(attr string, context *fuse.Context) ([]byte, f ...@@ -39,8 +39,7 @@ func (n *xattrChildNode) GetXAttr(attr string, context *fuse.Context) ([]byte, f
} }
func TestDefaultXAttr(t *testing.T) { func TestDefaultXAttr(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
root := &xattrNode{ root := &xattrNode{
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
...@@ -69,8 +68,7 @@ func TestDefaultXAttr(t *testing.T) { ...@@ -69,8 +68,7 @@ func TestDefaultXAttr(t *testing.T) {
} }
func TestEmptyXAttr(t *testing.T) { func TestEmptyXAttr(t *testing.T) {
dir := testutil.TempDir() dir := t.TempDir()
defer os.RemoveAll(dir)
root := &xattrNode{ root := &xattrNode{
Node: nodefs.NewDefaultNode(), Node: nodefs.NewDefaultNode(),
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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
// +build linux // +build linux
package test package test
......
...@@ -509,7 +509,7 @@ const ( ...@@ -509,7 +509,7 @@ const (
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_CODE_MAX = -6 // NOTIFY_CODE_MAX = -6
) )
type FlushIn struct { type FlushIn struct {
......
...@@ -2,8 +2,9 @@ module github.com/hanwen/go-fuse/v2 ...@@ -2,8 +2,9 @@ module github.com/hanwen/go-fuse/v2
require ( require (
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
github.com/moby/sys/mountinfo v0.6.2
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
) )
go 1.13 go 1.13
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
...@@ -16,8 +16,9 @@ import ( ...@@ -16,8 +16,9 @@ import (
// Called by TestLoopbackFileUtimens and TestLoopbackFileSystemUtimens. // Called by TestLoopbackFileUtimens and TestLoopbackFileSystemUtimens.
// //
// Parameters: // Parameters:
// path ........ path to the backing file //
// utimensFn ... Utimens() function that acts on the backing file // path ........ path to the backing file
// utimensFn ... Utimens() function that acts on the backing file
func TestLoopbackUtimens(t *testing.T, path string, utimensFn func(atime *time.Time, mtime *time.Time) fuse.Status) { func TestLoopbackUtimens(t *testing.T, path string, utimensFn func(atime *time.Time, mtime *time.Time) fuse.Status) {
// Arbitrary date: 05/02/2018 @ 7:57pm (UTC) // Arbitrary date: 05/02/2018 @ 7:57pm (UTC)
t0sec := int64(1525291058) t0sec := int64(1525291058)
......
// 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 testutil
import (
"io/ioutil"
"log"
"runtime"
"strings"
)
// TempDir creates a temporary directory that includes the name of the
// testcase. Panics if there was an I/O problem creating the directory.
func TempDir() string {
frames := make([]uintptr, 10) // at least 1 entry needed
n := runtime.Callers(1, frames)
lastName := ""
for _, pc := range frames[:n] {
f := runtime.FuncForPC(pc)
name := f.Name()
i := strings.LastIndex(name, ".")
if i >= 0 {
name = name[i+1:]
}
if strings.HasPrefix(name, "Test") {
lastName = name
}
}
dir, err := ioutil.TempDir("", lastName)
if err != nil {
log.Panicf("TempDir(%s): %v", lastName, err)
}
return dir
}
...@@ -335,7 +335,7 @@ var _ = (fs.NodeReaddirer)((*unionFSNode)(nil)) ...@@ -335,7 +335,7 @@ var _ = (fs.NodeReaddirer)((*unionFSNode)(nil))
func (n *unionFSNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { func (n *unionFSNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
root := n.root() root := n.root()
markers := map[string]struct{}{delDirHash: struct{}{}} markers := map[string]struct{}{delDirHash: {}}
// ignore error: assume no markers // ignore error: assume no markers
root.allMarkers(markers) root.allMarkers(markers)
...@@ -346,18 +346,29 @@ func (n *unionFSNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) ...@@ -346,18 +346,29 @@ func (n *unionFSNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)
// deepest root first. // deepest root first.
readRoot(root.roots[len(root.roots)-i-1], dir, names) readRoot(root.roots[len(root.roots)-i-1], dir, names)
} }
result := make([]fuse.DirEntry, 0, len(names)) result := make([]fuse.DirEntry, 0, 2*len(names))
maxIdx := -1
maxName := ""
for nm, mode := range names { for nm, mode := range names {
marker := filePathHash(filepath.Join(dir, nm)) marker := filePathHash(filepath.Join(dir, nm))
if _, ok := markers[marker]; ok { if _, ok := markers[marker]; ok {
continue continue
} }
if nm > maxName {
maxName = nm
maxIdx = len(result)
}
result = append(result, fuse.DirEntry{ result = append(result, fuse.DirEntry{
Name: nm, Name: nm,
Mode: mode, Mode: mode,
}) })
} }
if len(result) > 0 {
result = append(result[maxIdx:], result[:maxIdx]...)
}
return fs.NewListDirStream(result), 0 return fs.NewListDirStream(result), 0
} }
......
...@@ -33,12 +33,11 @@ func (tc *testCase) Clean() { ...@@ -33,12 +33,11 @@ func (tc *testCase) Clean() {
tc.server.Unmount() tc.server.Unmount()
tc.server = nil tc.server = nil
} }
os.RemoveAll(tc.dir)
} }
func newTestCase(t *testing.T, populate bool) *testCase { func newTestCase(t *testing.T, populate bool) *testCase {
t.Helper() t.Helper()
dir := testutil.TempDir() dir := t.TempDir()
dirs := []string{"ro", "rw", "mnt"} dirs := []string{"ro", "rw", "mnt"}
if populate { if populate {
dirs = append(dirs, "ro/dir") dirs = append(dirs, "ro/dir")
......
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"testing" "testing"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
) )
// All holds a map of all test functions // All holds a map of all test functions
...@@ -32,6 +33,7 @@ var All = map[string]func(*testing.T, string){ ...@@ -32,6 +33,7 @@ var All = map[string]func(*testing.T, string){
"ParallelFileOpen": ParallelFileOpen, "ParallelFileOpen": ParallelFileOpen,
"Link": Link, "Link": Link,
"LinkUnlinkRename": LinkUnlinkRename, "LinkUnlinkRename": LinkUnlinkRename,
"LseekHoleSeeksToEOF": LseekHoleSeeksToEOF,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist, "RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist, "RenameOverwriteDestExist": RenameOverwriteDestExist,
"RenameOpenDir": RenameOpenDir, "RenameOpenDir": RenameOpenDir,
...@@ -41,6 +43,8 @@ var All = map[string]func(*testing.T, string){ ...@@ -41,6 +43,8 @@ var All = map[string]func(*testing.T, string){
"OpenAt": OpenAt, "OpenAt": OpenAt,
"Fallocate": Fallocate, "Fallocate": Fallocate,
"DirSeek": DirSeek, "DirSeek": DirSeek,
"FcntlFlockSetLk": FcntlFlockSetLk,
"FcntlFlockLocksFile": FcntlFlockLocksFile,
} }
func DirectIO(t *testing.T, mnt string) { func DirectIO(t *testing.T, mnt string) {
...@@ -631,3 +635,88 @@ func Fallocate(t *testing.T, mnt string) { ...@@ -631,3 +635,88 @@ func Fallocate(t *testing.T, mnt string) {
fi.Size()) fi.Size())
} }
} }
func FcntlFlockSetLk(t *testing.T, mnt string) {
for i, cmd := range []int{syscall.F_SETLK, syscall.F_SETLKW} {
filename := mnt + fmt.Sprintf("/file%d", i)
f1, err := os.Create(filename)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f1.Close()
wlk := syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(f1.Fd(), cmd, &wlk); err != nil {
t.Fatalf("FcntlFlock failed: %v", err)
}
f2, err := os.OpenFile(filename, os.O_RDWR, 0766)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f2.Close()
lk := syscall.Flock_t{}
if err := syscall.FcntlFlock(f2.Fd(), unix.F_OFD_GETLK, &lk); err != nil {
t.Errorf("FcntlFlock failed: %v", err)
}
if lk.Type != syscall.F_WRLCK {
t.Errorf("got lk.Type=%v, want %v", lk.Type, syscall.F_WRLCK)
}
}
}
func FcntlFlockLocksFile(t *testing.T, mnt string) {
filename := mnt + "/test"
f1, err := os.Create(filename)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f1.Close()
wlk := syscall.Flock_t{
Type: syscall.F_WRLCK,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(f1.Fd(), syscall.F_SETLK, &wlk); err != nil {
t.Fatalf("FcntlFlock failed: %v", err)
}
f2, err := os.OpenFile(filename, os.O_RDWR, 0766)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f2.Close()
rlk := syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(f2.Fd(), syscall.F_SETLK, &rlk); err != syscall.EAGAIN {
t.Errorf("FcntlFlock returned %v, expected EAGAIN", err)
}
}
func LseekHoleSeeksToEOF(t *testing.T, mnt string) {
fn := filepath.Join(mnt, "file.bin")
content := bytes.Repeat([]byte("abcxyz\n"), 1024)
if err := ioutil.WriteFile(fn, content, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
fd, err := syscall.Open(fn, syscall.O_RDONLY, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer syscall.Close(fd)
off, err := unix.Seek(fd, int64(len(content)/2), unix.SEEK_HOLE)
if err != nil {
t.Fatalf("Seek: %v", err)
} else if off != int64(len(content)) {
t.Errorf("got offset %d, want %d", off, len(content))
}
}
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
package splice package splice
import ()
func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) { func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) {
panic("not implemented") panic("not implemented")
return 0, nil return 0, nil
......
...@@ -19,7 +19,7 @@ const testTtl = 100 * time.Millisecond ...@@ -19,7 +19,7 @@ const testTtl = 100 * time.Millisecond
func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup func()) { func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup func()) {
root := &MultiZipFs{} root := &MultiZipFs{}
mountPoint = testutil.TempDir() mountPoint = t.TempDir()
dt := testTtl dt := testTtl
opts := &fs.Options{ opts := &fs.Options{
...@@ -34,7 +34,6 @@ func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup fun ...@@ -34,7 +34,6 @@ func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup fun
} }
return mountPoint, server, func() { return mountPoint, server, func() {
server.Unmount() server.Unmount()
os.RemoveAll(mountPoint)
} }
} }
......
...@@ -67,8 +67,7 @@ func TestTar(t *testing.T) { ...@@ -67,8 +67,7 @@ func TestTar(t *testing.T) {
root := &tarRoot{rc: &addClose{buf}} root := &tarRoot{rc: &addClose{buf}}
mnt := testutil.TempDir() mnt := t.TempDir()
defer os.Remove(mnt)
opts := &fs.Options{} opts := &fs.Options{}
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
s, err := fs.Mount(mnt, root, opts) s, err := fs.Mount(mnt, root, opts)
...@@ -95,12 +94,12 @@ func TestTar(t *testing.T) { ...@@ -95,12 +94,12 @@ func TestTar(t *testing.T) {
} }
} else if strings.HasSuffix(k, "/") { } else if strings.HasSuffix(k, "/") {
if got, want := st.Mode, uint32(syscall.S_IFDIR|0464); got != want { if got, want := uint32(st.Mode), uint32(syscall.S_IFDIR|0464); got != want {
t.Errorf("dir %q: got mode %o, want %o", k, got, want) t.Errorf("dir %q: got mode %o, want %o", k, got, want)
} }
} else { } else {
if got, want := st.Mode, uint32(syscall.S_IFREG|0464); got != want { if got, want := uint32(st.Mode), uint32(syscall.S_IFREG|0464); got != want {
t.Errorf("entry %q, got mode %o, want %o", k, got, want) t.Errorf("entry %q, got mode %o, want %o", k, got, want)
} }
......
...@@ -33,14 +33,13 @@ func setupZipfs(t *testing.T) (mountPoint string, cleanup func()) { ...@@ -33,14 +33,13 @@ func setupZipfs(t *testing.T) (mountPoint string, cleanup func()) {
t.Fatalf("NewArchiveFileSystem failed: %v", err) t.Fatalf("NewArchiveFileSystem failed: %v", err)
} }
mountPoint = testutil.TempDir() mountPoint = t.TempDir()
opts := &fs.Options{} opts := &fs.Options{}
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
server, err := fs.Mount(mountPoint, root, opts) server, err := fs.Mount(mountPoint, root, opts)
return mountPoint, func() { return mountPoint, func() {
server.Unmount() server.Unmount()
os.RemoveAll(mountPoint)
} }
} }
......
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