mirror of
https://github.com/hanzoai/go-fuse.git
synced 2026-10-30 14:46:18 +00:00
237 lines
5.5 KiB
Go
237 lines
5.5 KiB
Go
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build linux
|
|
// +build linux
|
|
|
|
package test
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/go-fuse/v2/fuse"
|
|
"github.com/hanzoai/go-fuse/v2/fuse/nodefs"
|
|
"github.com/hanzoai/go-fuse/v2/internal/testutil"
|
|
)
|
|
|
|
func TestFlockExclusive(t *testing.T) {
|
|
cmd, err := exec.LookPath("flock")
|
|
if err != nil {
|
|
t.Skip("flock command not found.")
|
|
}
|
|
tc := NewTestCase(t)
|
|
defer tc.Cleanup()
|
|
|
|
contents := []byte{1, 2, 3}
|
|
tc.WriteFile(tc.origFile, []byte(contents), 0700)
|
|
|
|
for {
|
|
f, err := os.Open("/dev/null")
|
|
if err != nil {
|
|
t.Fatalf("Open(/dev/null): %v", err)
|
|
}
|
|
defer f.Close()
|
|
if f.Fd() > 3 {
|
|
break
|
|
}
|
|
}
|
|
|
|
f, err := os.OpenFile(tc.mountFile, os.O_WRONLY, 0)
|
|
if err != nil {
|
|
t.Fatalf("OpenFile(%q): %v", tc.mountFile, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
|
t.Errorf("Flock returned: %v", err)
|
|
return
|
|
}
|
|
|
|
_, err = runExternalFlock(cmd, tc.mountFile)
|
|
if err == nil {
|
|
t.Errorf("Expected flock to fail, but it did not")
|
|
}
|
|
}
|
|
|
|
func runExternalFlock(flockPath, fname string) ([]byte, error) {
|
|
f, err := os.OpenFile(fname, os.O_WRONLY, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
// in order to test the lock property we must use cmd.ExtraFiles (instead of passing the actual file)
|
|
// if we were to pass the file then this flock command would fail to place the lock (returning a
|
|
// 'file busy' error) as it is already opened and locked at this point (see above)
|
|
cmd := exec.Command(flockPath, "--exclusive", "--nonblock", "3")
|
|
cmd.Env = append(cmd.Env, "LC_ALL=C") // in case the user's shell language is different
|
|
cmd.ExtraFiles = []*os.File{f}
|
|
return cmd.CombinedOutput()
|
|
}
|
|
|
|
type lockingNode struct {
|
|
nodefs.Node
|
|
|
|
mu sync.Mutex
|
|
getLkInvoked bool
|
|
setLkInvoked bool
|
|
setLkwInvoked bool
|
|
}
|
|
|
|
func (n *lockingNode) GetLkInvoked() bool {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
return n.getLkInvoked
|
|
}
|
|
|
|
func (n *lockingNode) SetLkInvoked() bool {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
return n.setLkInvoked
|
|
}
|
|
|
|
func (n *lockingNode) SetLkwInvoked() bool {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
return n.setLkwInvoked
|
|
}
|
|
|
|
func (n *lockingNode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
|
|
return nodefs.NewDataFile([]byte("hello world")), fuse.OK
|
|
}
|
|
|
|
func (n *lockingNode) GetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
n.getLkInvoked = true
|
|
return fuse.OK
|
|
}
|
|
|
|
func (n *lockingNode) SetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
n.setLkInvoked = true
|
|
return fuse.OK
|
|
}
|
|
|
|
func (n *lockingNode) SetLkw(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
n.setLkwInvoked = true
|
|
return fuse.OK
|
|
}
|
|
|
|
func TestFlockInvoked(t *testing.T) {
|
|
flock, err := exec.LookPath("flock")
|
|
if err != nil {
|
|
t.Skip("flock command not found.")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
opts := &nodefs.Options{
|
|
Owner: fuse.CurrentOwner(),
|
|
Debug: testutil.VerboseTest(),
|
|
}
|
|
|
|
root := nodefs.NewDefaultNode()
|
|
conn := nodefs.NewFileSystemConnector(root, opts)
|
|
mountOpts := fuse.MountOptions{
|
|
EnableLocks: true,
|
|
}
|
|
s, err := fuse.NewServer(conn.RawFS(), dir, &mountOpts)
|
|
if err != nil {
|
|
t.Fatal("NewServer", err)
|
|
}
|
|
|
|
go s.Serve()
|
|
if err := s.WaitMount(); err != nil {
|
|
t.Fatal("WaitMount", err)
|
|
}
|
|
defer s.Unmount()
|
|
|
|
node := &lockingNode{
|
|
Node: nodefs.NewDefaultNode(),
|
|
}
|
|
root.Inode().NewChild("foo", false, node)
|
|
|
|
realPath := filepath.Join(dir, "foo")
|
|
|
|
if node.SetLkInvoked() {
|
|
t.Fatalf("SetLk is invoked")
|
|
}
|
|
|
|
cmd := exec.Command(flock, "--nonblock", realPath, "echo", "locked")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("flock %v: %v", err, string(out))
|
|
}
|
|
if !node.SetLkInvoked() {
|
|
t.Fatalf("SetLk is not invoked")
|
|
}
|
|
|
|
if node.SetLkwInvoked() {
|
|
t.Fatalf("SetLkw is invoked")
|
|
}
|
|
cmd = exec.Command(flock, realPath, "echo", "locked")
|
|
out, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("flock %v: %v", err, string(out))
|
|
}
|
|
if !node.SetLkwInvoked() {
|
|
t.Fatalf("SetLkw is not invoked")
|
|
}
|
|
}
|
|
|
|
// Test that file system that don't implement locking are still
|
|
// handled in the VFS layer.
|
|
func TestNoLockSupport(t *testing.T) {
|
|
flock, err := exec.LookPath("flock")
|
|
if err != nil {
|
|
t.Skip("flock command not found.")
|
|
}
|
|
|
|
tmp := t.TempDir()
|
|
mnt := t.TempDir()
|
|
|
|
opts := &nodefs.Options{
|
|
Owner: fuse.CurrentOwner(),
|
|
Debug: testutil.VerboseTest(),
|
|
}
|
|
root := nodefs.NewMemNodeFSRoot(tmp)
|
|
|
|
lock := fuse.FileLock{}
|
|
outLock := fuse.FileLock{}
|
|
ctx := fuse.Context{}
|
|
if status := root.GetLk(nil, uint64(1), &lock, uint32(0x0), &outLock, &ctx); status != fuse.ENOSYS {
|
|
t.Fatalf("MemNodeFs should not implement locking")
|
|
}
|
|
|
|
s, _, err := nodefs.MountRoot(mnt, root, opts)
|
|
if err != nil {
|
|
t.Fatalf("MountRoot: %v", err)
|
|
}
|
|
go s.Serve()
|
|
if err := s.WaitMount(); err != nil {
|
|
t.Fatalf("WaitMount: %v", err)
|
|
}
|
|
defer s.Unmount()
|
|
|
|
fn := mnt + "/file.txt"
|
|
if err := os.WriteFile(fn, []byte("content"), 0644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
cmd := exec.Command(flock, "--nonblock", fn, "echo", "locked")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("flock %v: %v", err, string(out))
|
|
}
|
|
}
|