From 77f61b7ccc7f9c3c9072ea45d3fa2f59811945e1 Mon Sep 17 00:00:00 2001 From: Yufeng Cheng Date: Tue, 16 Jan 2018 15:24:44 +0800 Subject: [PATCH 1/3] implement file locking: getlk/setlk/setlkw/flock --- fuse/api.go | 5 +- fuse/defaultraw.go | 10 ++- fuse/lockingfs.go | 14 +++- fuse/nodefs/api.go | 10 ++- fuse/nodefs/defaultfile.go | 13 +++- fuse/nodefs/defaultnode.go | 12 ++++ fuse/nodefs/files.go | 54 ++++++++++++++-- fuse/nodefs/fsops.go | 24 +++++-- fuse/nodefs/lockingfile.go | 16 ++++- fuse/opcode.go | 25 +++++++- fuse/pathfs/pathfs.go | 22 +++++++ fuse/test/file_lock_test.go | 125 ++++++++++++++++++++++++++++++++++++ fuse/test/flock_test.go | 55 ---------------- fuse/types.go | 36 ++++++++--- fuse/upgrade.go | 24 ++++++- 15 files changed, 358 insertions(+), 87 deletions(-) create mode 100644 fuse/test/file_lock_test.go delete mode 100644 fuse/test/flock_test.go diff --git a/fuse/api.go b/fuse/api.go index 89edfe946..5c92e6919 100644 --- a/fuse/api.go +++ b/fuse/api.go @@ -134,7 +134,10 @@ type RawFileSystem interface { Open(input *OpenIn, out *OpenOut) (status Status) Read(input *ReadIn, buf []byte) (ReadResult, Status) - Flock(input *FlockIn, flags int) (code Status) + // File locking + GetLk(input *LkIn, out *LkOut) (code Status) + SetLk(input *LkIn) (code Status) + SetLkw(input *LkIn) (code Status) Release(input *ReleaseIn) Write(input *WriteIn, data []byte) (written uint32, code Status) diff --git a/fuse/defaultraw.go b/fuse/defaultraw.go index 28e2ff826..cbfd4cb1c 100644 --- a/fuse/defaultraw.go +++ b/fuse/defaultraw.go @@ -117,7 +117,15 @@ func (fs *defaultRawFileSystem) Read(input *ReadIn, buf []byte) (ReadResult, Sta return nil, ENOSYS } -func (fs *defaultRawFileSystem) Flock(input *FlockIn, flags int) Status { +func (fs *defaultRawFileSystem) GetLk(in *LkIn, out *LkOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) SetLk(in *LkIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) SetLkw(in *LkIn) (code Status) { return ENOSYS } diff --git a/fuse/lockingfs.go b/fuse/lockingfs.go index b110d61d2..139a47f4a 100644 --- a/fuse/lockingfs.go +++ b/fuse/lockingfs.go @@ -159,9 +159,19 @@ func (fs *lockingRawFileSystem) Read(input *ReadIn, buf []byte) (ReadResult, Sta return fs.RawFS.Read(input, buf) } -func (fs *lockingRawFileSystem) Flock(input *FlockIn, flags int) Status { +func (fs *lockingRawFileSystem) GetLk(in *LkIn, out *LkOut) (code Status) { defer fs.locked()() - return fs.RawFS.Flock(input, flags) + return fs.RawFS.GetLk(in, out) +} + +func (fs *lockingRawFileSystem) SetLk(in *LkIn) (code Status) { + defer fs.locked()() + return fs.RawFS.SetLk(in) +} + +func (fs *lockingRawFileSystem) SetLkw(in *LkIn) (code Status) { + defer fs.locked()() + return fs.RawFS.SetLkw(in) } func (fs *lockingRawFileSystem) Write(input *WriteIn, data []byte) (written uint32, code Status) { diff --git a/fuse/nodefs/api.go b/fuse/nodefs/api.go index c20bc7c2f..3c4bd2550 100644 --- a/fuse/nodefs/api.go +++ b/fuse/nodefs/api.go @@ -103,6 +103,11 @@ type Node interface { SetXAttr(attr string, data []byte, flags int, context *fuse.Context) fuse.Status ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status) + // File locking + GetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) + SetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) + SetLkw(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) + // Attributes GetAttr(out *fuse.Attr, file File, context *fuse.Context) (code fuse.Status) Chmod(file File, perms uint32, context *fuse.Context) (code fuse.Status) @@ -131,7 +136,10 @@ type File interface { Read(dest []byte, off int64) (fuse.ReadResult, fuse.Status) Write(data []byte, off int64) (written uint32, code fuse.Status) - Flock(flags int) fuse.Status + // File locking + GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) + SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) + SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) // Flush is called for close() call on a file descriptor. In // case of duplicated descriptor, it may be called more than diff --git a/fuse/nodefs/defaultfile.go b/fuse/nodefs/defaultfile.go index 9a28601b3..7b6f8aabb 100644 --- a/fuse/nodefs/defaultfile.go +++ b/fuse/nodefs/defaultfile.go @@ -37,7 +37,18 @@ func (f *defaultFile) Write(data []byte, off int64) (uint32, fuse.Status) { return 0, fuse.ENOSYS } -func (f *defaultFile) Flock(flags int) fuse.Status { return fuse.ENOSYS } +func (f *defaultFile) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { + return fuse.ENOSYS +} + +func (f *defaultFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + return fuse.ENOSYS +} + +func (f *defaultFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + return fuse.ENOSYS +} + func (f *defaultFile) Flush() fuse.Status { return fuse.OK } diff --git a/fuse/nodefs/defaultnode.go b/fuse/nodefs/defaultnode.go index 0e477a1b8..23bef1dd2 100644 --- a/fuse/nodefs/defaultnode.go +++ b/fuse/nodefs/defaultnode.go @@ -137,6 +137,18 @@ func (n *defaultNode) GetAttr(out *fuse.Attr, file File, context *fuse.Context) return fuse.OK } +func (n *defaultNode) GetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { + return fuse.ENOSYS +} + +func (n *defaultNode) SetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { + return fuse.ENOSYS +} + +func (n *defaultNode) SetLkw(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { + return fuse.ENOSYS +} + func (n *defaultNode) Chmod(file File, perms uint32, context *fuse.Context) (code fuse.Status) { return fuse.ENOSYS } diff --git a/fuse/nodefs/files.go b/fuse/nodefs/files.go index 0e5755e8a..bf3a2b30b 100644 --- a/fuse/nodefs/files.go +++ b/fuse/nodefs/files.go @@ -167,12 +167,56 @@ func (f *loopbackFile) Fsync(flags int) (code fuse.Status) { return r } -func (f *loopbackFile) Flock(flags int) fuse.Status { - f.lock.Lock() - r := fuse.ToStatus(syscall.Flock(int(f.File.Fd()), flags)) - f.lock.Unlock() +const ( + F_OFD_GETLK = 36 + F_OFD_SETLK = 37 + F_OFD_SETLKW = 38 +) - return r +func (f *loopbackFile) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + code = fuse.ToStatus(syscall.FcntlFlock(f.File.Fd(), F_OFD_GETLK, &flk)) + out.FromFlockT(&flk) + return +} + +func (f *loopbackFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + return f.setLock(owner, lk, flags, false) +} + +func (f *loopbackFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + return f.setLock(owner, lk, flags, true) +} + +func (f *loopbackFile) setLock(owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (code fuse.Status) { + if (flags & fuse.FUSE_LK_FLOCK) != 0 { + var op int + switch lk.Typ { + case syscall.F_RDLCK: + op = syscall.LOCK_SH + case syscall.F_WRLCK: + op = syscall.LOCK_EX + case syscall.F_UNLCK: + op = syscall.LOCK_UN + default: + return fuse.EINVAL + } + if !blocking { + op |= syscall.LOCK_NB + } + return fuse.ToStatus(syscall.Flock(int(f.File.Fd()), op)) + } else { + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + var op int + if blocking { + op = F_OFD_SETLKW + } else { + op = F_OFD_SETLK + } + return fuse.ToStatus(syscall.FcntlFlock(f.File.Fd(), op, &flk)) + } } func (f *loopbackFile) Truncate(size uint64) fuse.Status { diff --git a/fuse/nodefs/fsops.go b/fuse/nodefs/fsops.go index 04f77b969..df89ac110 100644 --- a/fuse/nodefs/fsops.go +++ b/fuse/nodefs/fsops.go @@ -452,15 +452,25 @@ func (c *rawBridge) Read(input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse. return node.Node().Read(f, buf, int64(input.Offset), &input.Context) } -func (c *rawBridge) Flock(input *fuse.FlockIn, flags int) fuse.Status { - node := c.toInode(input.NodeId) - opened := node.mount.getOpenedFile(input.Fh) +func (c *rawBridge) GetLk(input *fuse.LkIn, out *fuse.LkOut) (code fuse.Status) { + n := c.toInode(input.NodeId) + opened := n.mount.getOpenedFile(input.Fh) - if opened != nil { - return opened.WithFlags.File.Flock(flags) - } + return n.fsInode.GetLk(opened, input.Owner, &input.Lk, input.LkFlags, &out.Lk, &input.Context) +} + +func (c *rawBridge) SetLk(input *fuse.LkIn) (code fuse.Status) { + n := c.toInode(input.NodeId) + opened := n.mount.getOpenedFile(input.Fh) + + return n.fsInode.SetLk(opened, input.Owner, &input.Lk, input.LkFlags, &input.Context) +} + +func (c *rawBridge) SetLkw(input *fuse.LkIn) (code fuse.Status) { + n := c.toInode(input.NodeId) + opened := n.mount.getOpenedFile(input.Fh) - return fuse.EBADF + return n.fsInode.SetLkw(opened, input.Owner, &input.Lk, input.LkFlags, &input.Context) } func (c *rawBridge) StatFs(header *fuse.InHeader, out *fuse.StatfsOut) fuse.Status { diff --git a/fuse/nodefs/lockingfile.go b/fuse/nodefs/lockingfile.go index e3463f1e8..1adcfb195 100644 --- a/fuse/nodefs/lockingfile.go +++ b/fuse/nodefs/lockingfile.go @@ -54,10 +54,22 @@ func (f *lockingFile) Flush() fuse.Status { return f.file.Flush() } -func (f *lockingFile) Flock(flags int) fuse.Status { +func (f *lockingFile) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { f.mu.Lock() defer f.mu.Unlock() - return f.file.Flock(flags) + return f.file.GetLk(owner, lk, flags, out) +} + +func (f *lockingFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + f.mu.Lock() + defer f.mu.Unlock() + return f.file.SetLk(owner, lk, flags) +} + +func (f *lockingFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { + f.mu.Lock() + defer f.mu.Unlock() + return f.file.SetLkw(owner, lk, flags) } func (f *lockingFile) Release() { diff --git a/fuse/opcode.go b/fuse/opcode.go index de9ef3d3b..ecc78ba07 100644 --- a/fuse/opcode.go +++ b/fuse/opcode.go @@ -83,7 +83,7 @@ func doInit(server *Server, req *request) { server.reqMu.Lock() server.kernelSettings = *input server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS | - CAP_AUTO_INVAL_DATA | CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT) + CAP_AUTO_INVAL_DATA | CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS) if input.Minor >= 13 { server.setSplice() @@ -390,6 +390,18 @@ func doFallocate(server *Server, req *request) { req.status = server.fileSystem.Fallocate((*FallocateIn)(req.inData)) } +func doGetLk(server *Server, req *request) { + req.status = server.fileSystem.GetLk((*LkIn)(req.inData), (*LkOut)(req.outData())) +} + +func doSetLk(server *Server, req *request) { + req.status = server.fileSystem.SetLk((*LkIn)(req.inData)) +} + +func doSetLkw(server *Server, req *request) { + req.status = server.fileSystem.SetLkw((*LkIn)(req.inData)) +} + //////////////////////////////////////////////////////////////// type operationFunc func(*Server, *request) @@ -457,6 +469,9 @@ func init() { _OP_READDIR: unsafe.Sizeof(ReadIn{}), _OP_RELEASEDIR: unsafe.Sizeof(ReleaseIn{}), _OP_FSYNCDIR: unsafe.Sizeof(FsyncIn{}), + _OP_GETLK: unsafe.Sizeof(LkIn{}), + _OP_SETLK: unsafe.Sizeof(LkIn{}), + _OP_SETLKW: unsafe.Sizeof(LkIn{}), _OP_ACCESS: unsafe.Sizeof(AccessIn{}), _OP_CREATE: unsafe.Sizeof(CreateIn{}), _OP_INTERRUPT: unsafe.Sizeof(InterruptIn{}), @@ -484,6 +499,7 @@ func init() { _OP_LISTXATTR: unsafe.Sizeof(GetXAttrOut{}), _OP_INIT: unsafe.Sizeof(InitOut{}), _OP_OPENDIR: unsafe.Sizeof(OpenOut{}), + _OP_GETLK: unsafe.Sizeof(LkOut{}), _OP_CREATE: unsafe.Sizeof(CreateOut{}), _OP_BMAP: unsafe.Sizeof(_BmapOut{}), _OP_IOCTL: unsafe.Sizeof(_IoctlOut{}), @@ -572,6 +588,9 @@ func init() { _OP_FSYNCDIR: doFsyncDir, _OP_SETXATTR: doSetXAttr, _OP_REMOVEXATTR: doRemoveXAttr, + _OP_GETLK: doGetLk, + _OP_SETLK: doSetLk, + _OP_SETLKW: doSetLkw, _OP_ACCESS: doAccess, _OP_SYMLINK: doSymlink, _OP_RENAME: doRename, @@ -600,6 +619,7 @@ func init() { _OP_NOTIFY_DELETE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalDeleteOut)(ptr) }, _OP_STATFS: func(ptr unsafe.Pointer) interface{} { return (*StatfsOut)(ptr) }, _OP_SYMLINK: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) }, + _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkOut)(ptr) }, } { operationHandlers[op].DecodeOut = f } @@ -629,6 +649,9 @@ func init() { _OP_FALLOCATE: func(ptr unsafe.Pointer) interface{} { return (*FallocateIn)(ptr) }, _OP_READDIRPLUS: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, _OP_RENAME: func(ptr unsafe.Pointer) interface{} { return (*RenameIn)(ptr) }, + _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, + _OP_SETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, + _OP_SETLKW: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, } { operationHandlers[op].DecodeIn = f } diff --git a/fuse/pathfs/pathfs.go b/fuse/pathfs/pathfs.go index 9df24fbe8..3b637ffa0 100644 --- a/fuse/pathfs/pathfs.go +++ b/fuse/pathfs/pathfs.go @@ -752,3 +752,25 @@ func (n *pathInode) Write(file nodefs.File, data []byte, off int64, context *fus } return 0, fuse.ENOSYS } + +func (n *pathInode) GetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { + if file != nil { + return file.GetLk(owner, lk, flags, out) + } + return fuse.ENOSYS +} + +func (n *pathInode) SetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { + if file != nil { + return file.SetLk(owner, lk, flags) + } + return fuse.ENOSYS +} + +func (n *pathInode) SetLkw(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { + if file != nil { + return file.SetLkw(owner, lk, flags) + } + return fuse.ENOSYS +} + diff --git a/fuse/test/file_lock_test.go b/fuse/test/file_lock_test.go new file mode 100644 index 000000000..bcdd02404 --- /dev/null +++ b/fuse/test/file_lock_test.go @@ -0,0 +1,125 @@ +// 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. + +// +build linux + +package test + +import ( + "bytes" + "os" + "os/exec" + "syscall" + "testing" + "path/filepath" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + "github.com/hanwen/go-fuse/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) + + 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 + } + + if out, err := runExternalFlock(cmd, tc.mountFile); !bytes.Contains(out, []byte("failed to get lock")) { + t.Errorf("runExternalFlock(%q): %s (%v)", tc.mountFile, out, err) + } +} + +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() + cmd := exec.Command(flockPath, "--verbose", "--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 + flockInvoked bool +} + +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.flockInvoked = 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.flockInvoked = 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.flockInvoked = true + return fuse.OK +} + +func TestFlockInvoked(t *testing.T) { + flock, err := exec.LookPath("flock") + if err != nil { + t.Skip("flock command not found.") + } + + dir := testutil.TempDir() + defer os.RemoveAll(dir) + + opts := &nodefs.Options{ + Owner: fuse.CurrentOwner(), + Debug: testutil.VerboseTest(), + } + + root := nodefs.NewDefaultNode() + s, _, err := nodefs.MountRoot(dir, root, opts) + if err != nil { + t.Fatalf("MountRoot: %v", err) + } + go s.Serve() + if err := s.WaitMount(); err != nil { + t.Fatal("WaitMount", err) + } + defer s.Unmount() + + node := &lockingNode{ + Node: nodefs.NewDefaultNode(), + flockInvoked: false, + } + root.Inode().NewChild("foo", false, node) + + realPath := filepath.Join(dir, "foo") + 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.flockInvoked { + t.Fatalf("flock is not invoked") + } +} diff --git a/fuse/test/flock_test.go b/fuse/test/flock_test.go deleted file mode 100644 index 09b9bb21f..000000000 --- a/fuse/test/flock_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// 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. - -// +build linux - -package test - -import ( - "bytes" - "os" - "os/exec" - "syscall" - "testing" -) - -// See https://github.com/hanwen/go-fuse/issues/170 -func disabledTestFlock(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) - - 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 - } - - if out, err := runExternalFlock(cmd, tc.mountFile); !bytes.Contains(out, []byte("failed to get lock")) { - t.Errorf("runExternalFlock(%q): %s (%v)", tc.mountFile, out, err) - } -} - -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() - cmd := exec.Command(flockPath, "--verbose", "--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() -} diff --git a/fuse/types.go b/fuse/types.go index 7239ff6a5..65ae6ddfb 100644 --- a/fuse/types.go +++ b/fuse/types.go @@ -6,6 +6,7 @@ package fuse import ( "syscall" + "io" ) const ( @@ -306,24 +307,24 @@ type GetXAttrOut struct { Padding uint32 } -type _FileLock struct { +type FileLock struct { Start uint64 End uint64 Typ uint32 Pid uint32 } -type _LkIn struct { +type LkIn struct { InHeader Fh uint64 Owner uint64 - Lk _FileLock + Lk FileLock LkFlags uint32 Padding uint32 } -type _LkOut struct { - Lk _FileLock +type LkOut struct { + Lk FileLock } // For AccessIn.Mask. @@ -467,7 +468,26 @@ type FallocateIn struct { Padding uint32 } -type FlockIn struct { - InHeader - Fh uint64 +func (lk *FileLock) ToFlockT(flockT *syscall.Flock_t) { + flockT.Start = int64(lk.Start) + if lk.End == (1<<63)-1 { + flockT.Len = 0 + } else { + flockT.Len = int64(lk.End - lk.Start + 1) + } + flockT.Whence = int16(io.SeekStart) + flockT.Type = int16(lk.Typ) +} + +func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) { + lk.Typ = uint32(flockT.Type) + if flockT.Type != syscall.F_UNLCK { + lk.Start = uint64(flockT.Start) + if flockT.Len == 0 { + lk.End = (1 << 63) - 1 + } else { + lk.End = uint64(flockT.Start + flockT.Len - 1) + } + } + lk.Pid = uint32(flockT.Pid) } diff --git a/fuse/upgrade.go b/fuse/upgrade.go index cc81167b7..693c8f7a1 100644 --- a/fuse/upgrade.go +++ b/fuse/upgrade.go @@ -244,11 +244,29 @@ func (fs *wrappingFS) Read(input *ReadIn, buf []byte) (ReadResult, Status) { return nil, ENOSYS } -func (fs *wrappingFS) Flock(input *FlockIn, flags int) Status { +func (fs *wrappingFS) GetLk(in *LkIn, out *LkOut) (code Status) { if s, ok := fs.fs.(interface { - Flock(input *FlockIn, flags int) Status + GetLk(in *LkIn, out *LkOut) (code Status) }); ok { - return s.Flock(input, flags) + return s.GetLk(in, out) + } + return ENOSYS +} + +func (fs *wrappingFS) SetLk(in *LkIn) (code Status) { + if s, ok := fs.fs.(interface { + SetLk(in *LkIn) (code Status) + }); ok { + return s.SetLk(in) + } + return ENOSYS +} + +func (fs *wrappingFS) SetLkw(in *LkIn) (code Status) { + if s, ok := fs.fs.(interface { + SetLkw(in *LkIn) (code Status) + }); ok { + return s.SetLkw(in) } return ENOSYS } From d810ec8a27e8cf5be35872d34a2c1602a2c9115d Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 3 Jul 2018 16:16:23 -0700 Subject: [PATCH 2/3] update based on feedback from hanwen [#158502492](https://www.pivotaltracker.com/story/show/#158502492) --- fuse/nodefs/api.go | 6 +++++ fuse/test/file_lock_test.go | 54 ++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/fuse/nodefs/api.go b/fuse/nodefs/api.go index 3c4bd2550..32fa8c7ff 100644 --- a/fuse/nodefs/api.go +++ b/fuse/nodefs/api.go @@ -104,8 +104,14 @@ type Node interface { ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status) // File locking + // + // GetLk returns existing lock information for file. GetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) + + // Sets or clears the lock described by lk on file. SetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) + + // Sets or clears the lock described by lk. This call blocks until the operation can be completed. SetLkw(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) // Attributes diff --git a/fuse/test/file_lock_test.go b/fuse/test/file_lock_test.go index bcdd02404..0dc73d8e0 100644 --- a/fuse/test/file_lock_test.go +++ b/fuse/test/file_lock_test.go @@ -7,7 +7,6 @@ package test import ( - "bytes" "os" "os/exec" "syscall" @@ -41,8 +40,9 @@ func TestFlockExclusive(t *testing.T) { return } - if out, err := runExternalFlock(cmd, tc.mountFile); !bytes.Contains(out, []byte("failed to get lock")) { - t.Errorf("runExternalFlock(%q): %s (%v)", tc.mountFile, out, err) + _, err = runExternalFlock(cmd, tc.mountFile) + if err == nil { + t.Errorf("Expected flock to fail, but it did not") } } @@ -52,7 +52,11 @@ func runExternalFlock(flockPath, fname string) ([]byte, error) { return nil, err } defer f.Close() - cmd := exec.Command(flockPath, "--verbose", "--exclusive", "--nonblock", "3") + + // 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() @@ -60,7 +64,9 @@ func runExternalFlock(flockPath, fname string) ([]byte, error) { type lockingNode struct { nodefs.Node - flockInvoked bool + GetLkInvoked bool + SetLkInvoked bool + SetLkwInvoked bool } func (n *lockingNode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) { @@ -68,17 +74,17 @@ func (n *lockingNode) Open(flags uint32, context *fuse.Context) (file nodefs.Fil } func (n *lockingNode) GetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { - n.flockInvoked = true + 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.flockInvoked = true + 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.flockInvoked = true + n.SetLkwInvoked = true return fuse.OK } @@ -108,18 +114,34 @@ func TestFlockInvoked(t *testing.T) { defer s.Unmount() node := &lockingNode{ - Node: nodefs.NewDefaultNode(), - flockInvoked: false, + Node: nodefs.NewDefaultNode(), } root.Inode().NewChild("foo", false, node) realPath := filepath.Join(dir, "foo") - 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 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.flockInvoked { - t.Fatalf("flock is not invoked") + if !node.SetLkwInvoked { + t.Fatalf("SetLkw is not invoked") } } From 72aba6be0db6987636d2602e146837fb913e5991 Mon Sep 17 00:00:00 2001 From: Maria Shaldibina Date: Thu, 19 Jul 2018 14:40:55 -0700 Subject: [PATCH 3/3] Update AUTHORS and reformat --- AUTHORS | 3 +++ fuse/test/file_lock_test.go | 9 ++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index ef1474ef9..9a0b0950e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,3 +14,6 @@ Paul Jolly Shayan Pooya Valient Gough Yongwoo Park +Kaoet Ibe +Paul Warren +Maria Shaldibina diff --git a/fuse/test/file_lock_test.go b/fuse/test/file_lock_test.go index 0dc73d8e0..ad8e5b1c3 100644 --- a/fuse/test/file_lock_test.go +++ b/fuse/test/file_lock_test.go @@ -9,9 +9,9 @@ package test import ( "os" "os/exec" + "path/filepath" "syscall" "testing" - "path/filepath" "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" @@ -64,8 +64,8 @@ func runExternalFlock(flockPath, fname string) ([]byte, error) { type lockingNode struct { nodefs.Node - GetLkInvoked bool - SetLkInvoked bool + GetLkInvoked bool + SetLkInvoked bool SetLkwInvoked bool } @@ -114,7 +114,7 @@ func TestFlockInvoked(t *testing.T) { defer s.Unmount() node := &lockingNode{ - Node: nodefs.NewDefaultNode(), + Node: nodefs.NewDefaultNode(), } root.Inode().NewChild("foo", false, node) @@ -132,7 +132,6 @@ func TestFlockInvoked(t *testing.T) { t.Fatalf("SetLk is not invoked") } - if node.SetLkwInvoked { t.Fatalf("SetLkw is invoked") }