Commit 7e0334c3 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into y/nodefs-cancel

* master:
  fuse: suppress ENOENT warnings for INTERRUPT responses
  loopback: enable logger for tests
  fuse: add `EnableAcl` flag to MountOptions
  fuse: fix wrong CAP_POSIX_ACL value
  fs: track most recent parent
  fs: tests: add TestStaleHardlinks (fails at the moment)
  fuse: fix definition of CAP_POSIX_ACL, CAP_HANDLE_KILLPRIV
  example/winfs: add a sleep before the busy check
  example/winfs: new example
  fs: return ENOTSUP if NodeMknoder isn't implemented
parents f0cbba3e eefea5e0
// Copyright 2020 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.
// This is main program driver for a loopback filesystem that emulates
// windows semantics (no delete/rename on opened files.)
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"path"
"sync"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Release decreases the open count. The kernel doesn't wait with
// returning from close(), so if the caller is too quick to
// unlink/rename after calling close(), this may still trigger EBUSY.
// Kludge around this by sleeping for a bit before we check business.
var delay = flag.Duration("delay", 10*time.Microsecond,
"wait this long before checking business")
// WindowsNode is a loopback FS node keeping track of open counts.
type WindowsNode struct {
// WindowsNode inherits most functionality from LoopbackNode.
fs.LoopbackNode
mu sync.Mutex
openCount int
}
func (n *WindowsNode) increment() {
n.mu.Lock()
defer n.mu.Unlock()
n.openCount++
}
func (n *WindowsNode) decrement() {
n.mu.Lock()
defer n.mu.Unlock()
n.openCount--
}
var _ = (fs.NodeOpener)((*WindowsNode)(nil))
func (n *WindowsNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
fh, flags, errno := n.LoopbackNode.Open(ctx, flags)
if errno == 0 {
n.increment()
}
return fh, flags, errno
}
var _ = (fs.NodeCreater)((*WindowsNode)(nil))
func (n *WindowsNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (*fs.Inode, fs.FileHandle, uint32, syscall.Errno) {
inode, fh, flags, errno := n.LoopbackNode.Create(ctx, name, flags, mode, out)
if errno == 0 {
wn := inode.Operations().(*WindowsNode)
wn.increment()
}
return inode, fh, flags, errno
}
var _ = (fs.NodeReleaser)((*WindowsNode)(nil))
func (n *WindowsNode) Release(ctx context.Context, f fs.FileHandle) syscall.Errno {
n.decrement()
if fr, ok := f.(fs.FileReleaser); ok {
return fr.Release(ctx)
}
return 0
}
func isBusy(parent *fs.Inode, name string) bool {
time.Sleep(*delay)
if ch := parent.GetChild(name); ch != nil {
if wn, ok := ch.Operations().(*WindowsNode); ok {
wn.mu.Lock()
defer wn.mu.Unlock()
if wn.openCount > 0 {
return true
}
}
}
return false
}
var _ = (fs.NodeUnlinker)((*WindowsNode)(nil))
func (n *WindowsNode) Unlink(ctx context.Context, name string) syscall.Errno {
if isBusy(n.EmbeddedInode(), name) {
return syscall.EBUSY
}
return n.LoopbackNode.Unlink(ctx, name)
}
var _ = (fs.NodeRenamer)((*WindowsNode)(nil))
func (n *WindowsNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
if isBusy(n.EmbeddedInode(), name) || isBusy(newParent.EmbeddedInode(), newName) {
return syscall.EBUSY
}
return n.LoopbackNode.Rename(ctx, name, newParent, newName, flags)
}
func newWindowsNode(rootData *fs.LoopbackRoot, _ *fs.Inode, _ string, _ *syscall.Stat_t) fs.InodeEmbedder {
n := &WindowsNode{
LoopbackNode: fs.LoopbackNode{
RootData: rootData,
},
}
return n
}
func main() {
log.SetFlags(log.Lmicroseconds)
debug := flag.Bool("debug", false, "print debugging messages.")
flag.Parse()
if flag.NArg() < 2 {
fmt.Printf("usage: %s MOUNTPOINT ORIGINAL\n", path.Base(os.Args[0]))
fmt.Printf("\noptions:\n")
flag.PrintDefaults()
os.Exit(2)
}
orig := flag.Arg(1)
rootData := &fs.LoopbackRoot{
NewNode: newWindowsNode,
Path: orig,
}
sec := time.Second
opts := &fs.Options{
AttrTimeout: &sec,
EntryTimeout: &sec,
}
opts.Debug = *debug
opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig)
opts.MountOptions.Name = "winfs"
opts.NullPermissions = true
server, err := fs.Mount(flag.Arg(0), newWindowsNode(rootData, nil, "", nil), opts)
if err != nil {
log.Fatalf("Mount fail: %v\n", err)
}
fmt.Println("Mounted!")
server.Wait()
}
......@@ -418,6 +418,8 @@ func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name stri
var errno syscall.Errno
if mops, ok := parent.ops.(NodeMknoder); ok {
child, errno = mops.Mknod(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, input.Rdev, out)
} else {
return fuse.ENOTSUP
}
if errno != 0 {
......
......@@ -18,11 +18,6 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
)
type parentData struct {
name string
parent *Inode
}
// StableAttr holds immutable attributes of a object in the filesystem.
type StableAttr struct {
// Each Inode has a type, which does not change over the
......@@ -105,7 +100,7 @@ type Inode struct {
// Parents of this Inode. Can be more than one due to hard links.
// When you change this, you MUST increment changeCounter.
parents map[parentData]struct{}
parents inodeParents
}
func (n *Inode) IsDir() bool {
......@@ -126,7 +121,6 @@ func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge,
n.bridge = bridge
n.persistent = persistent
n.nodeId = nodeId
n.parents = make(map[parentData]struct{})
if attr.Mode == fuse.S_IFDIR {
n.children = make(map[string]*Inode)
}
......@@ -265,7 +259,7 @@ func unlockNodes(ns ...*Inode) {
func (n *Inode) Forgotten() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.lookupCount == 0 && len(n.parents) == 0 && !n.persistent
return n.lookupCount == 0 && n.parents.count() == 0 && !n.persistent
}
// Operations returns the object implementing the file system
......@@ -283,26 +277,16 @@ func (n *Inode) Path(root *Inode) string {
var segments []string
p := n
for p != nil && p != root {
var pd parentData
// We don't try to take all locks at the same time, because
// the caller won't use the "path" string under lock anyway.
found := false
p.mu.Lock()
// Select an arbitrary parent
for pd = range p.parents {
found = true
break
}
// Get last known parent
pd := p.parents.get()
p.mu.Unlock()
if found == false {
if pd == nil {
p = nil
break
}
if pd.parent == nil {
break
}
segments = append(segments, pd.name)
p = pd.parent
}
......@@ -340,11 +324,9 @@ func (iparent *Inode) setEntry(name string, ichild *Inode) {
// Directories cannot have more than one parent. Clear the map.
// This special-case is neccessary because ichild may still have a
// parent that was forgotten (i.e. removed from bridge.inoMap).
for i := range ichild.parents {
delete(ichild.parents, i)
}
ichild.parents.clear()
}
ichild.parents[newParent] = struct{}{}
ichild.parents.add(newParent)
iparent.children[name] = ichild
ichild.changeCounter++
iparent.changeCounter++
......@@ -421,7 +403,7 @@ retry:
parents = parents[:0]
nChange := n.changeCounter
live = n.lookupCount > 0 || len(n.children) > 0 || n.persistent
for p := range n.parents {
for _, p := range n.parents.all() {
parents = append(parents, p)
lockme = append(lockme, p.parent)
}
......@@ -447,7 +429,7 @@ retry:
delete(p.parent.children, p.name)
p.parent.changeCounter++
}
n.parents = map[parentData]struct{}{}
n.parents.clear()
n.changeCounter++
if n.lookupCount != 0 {
......@@ -488,7 +470,7 @@ retry:
parentCounter := n.changeCounter
if !ok {
n.children[name] = ch
ch.parents[parentData{name, n}] = struct{}{}
ch.parents.add(parentData{name, n})
n.changeCounter++
ch.changeCounter++
unlockNode2(n, ch)
......@@ -506,9 +488,9 @@ retry:
continue retry
}
delete(prev.parents, parentData{name, n})
prev.parents.delete(parentData{name, n})
n.children[name] = ch
ch.parents[parentData{name, n}] = struct{}{}
ch.parents.add(parentData{name, n})
n.changeCounter++
ch.changeCounter++
prev.changeCounter++
......@@ -534,10 +516,11 @@ func (n *Inode) Children() map[string]*Inode {
func (n *Inode) Parent() (string, *Inode) {
n.mu.Lock()
defer n.mu.Unlock()
for k := range n.parents {
return k.name, k.parent
p := n.parents.get()
if p == nil {
return "", nil
}
return "", nil
return p.name, p.parent
}
// RmAllChildren recursively drops a tree, forgetting all persistent
......@@ -589,7 +572,7 @@ retry:
for _, nm := range names {
ch := n.children[nm]
delete(n.children, nm)
delete(ch.parents, parentData{nm, n})
ch.parents.delete(parentData{nm, n})
ch.changeCounter++
}
......@@ -640,7 +623,7 @@ retry:
if oldChild != nil {
delete(n.children, old)
delete(oldChild.parents, parentData{old, n})
oldChild.parents.delete(parentData{old, n})
n.changeCounter++
oldChild.changeCounter++
}
......@@ -649,7 +632,7 @@ retry:
// This can cause the child to be slated for
// removal; see below
delete(newParent.children, newName)
delete(destChild.parents, parentData{newName, newParent})
destChild.parents.delete(parentData{newName, newParent})
destChild.changeCounter++
newParent.changeCounter++
}
......@@ -658,7 +641,7 @@ retry:
newParent.children[newName] = oldChild
newParent.changeCounter++
oldChild.parents[parentData{newName, newParent}] = struct{}{}
oldChild.parents.add(parentData{newName, newParent})
oldChild.changeCounter++
}
......@@ -698,14 +681,14 @@ retry:
// Detach
if oldChild != nil {
delete(oldParent.children, oldName)
delete(oldChild.parents, parentData{oldName, oldParent})
oldChild.parents.delete(parentData{oldName, oldParent})
oldParent.changeCounter++
oldChild.changeCounter++
}
if destChild != nil {
delete(newParent.children, newName)
delete(destChild.parents, parentData{newName, newParent})
destChild.parents.delete(parentData{newName, newParent})
destChild.changeCounter++
newParent.changeCounter++
}
......@@ -715,7 +698,7 @@ retry:
newParent.children[newName] = oldChild
newParent.changeCounter++
oldChild.parents[parentData{newName, newParent}] = struct{}{}
oldChild.parents.add(parentData{newName, newParent})
oldChild.changeCounter++
}
......@@ -723,7 +706,7 @@ retry:
oldParent.children[oldName] = oldChild
oldParent.changeCounter++
destChild.parents[parentData{oldName, oldParent}] = struct{}{}
destChild.parents.add(parentData{oldName, oldParent})
destChild.changeCounter++
}
unlockNodes(oldParent, newParent, oldChild, destChild)
......
// Copyright 2021 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
// inodeParents stores zero or more parents of an Inode,
// remembering which one is the most recent.
//
// No internal locking: the caller is responsible for preventing
// concurrent access.
type inodeParents struct {
// newest is the most-recently add()'ed parent.
// nil when we don't have any parents.
newest *parentData
// other are parents in addition to the newest.
// nil or empty when we have <= 1 parents.
other map[parentData]struct{}
}
// add adds a parent to the store.
func (p *inodeParents) add(n parentData) {
// one and only parent
if p.newest == nil {
p.newest = &n
}
// already known as `newest`
if *p.newest == n {
return
}
// old `newest` gets displaced into `other`
if p.other == nil {
p.other = make(map[parentData]struct{})
}
p.other[*p.newest] = struct{}{}
// new parent becomes `newest` (possibly moving up from `other`)
delete(p.other, n)
p.newest = &n
}
// get returns the most recent parent
// or nil if there is no parent at all.
func (p *inodeParents) get() *parentData {
return p.newest
}
// all returns all known parents
// or nil if there is no parent at all.
func (p *inodeParents) all() []parentData {
count := p.count()
if count == 0 {
return nil
}
out := make([]parentData, 0, count)
out = append(out, *p.newest)
for i := range p.other {
out = append(out, i)
}
return out
}
func (p *inodeParents) delete(n parentData) {
// We have zero parents, so we can't delete any.
if p.newest == nil {
return
}
// If it's not the `newest` it must be in `other` (or nowhere).
if *p.newest != n {
delete(p.other, n)
return
}
// We want to delete `newest`, but there is no other to replace it.
if len(p.other) == 0 {
p.newest = nil
return
}
// Move random entry from `other` over `newest`.
var i parentData
for i = range p.other {
p.newest = &i
break
}
delete(p.other, i)
}
func (p *inodeParents) clear() {
p.newest = nil
p.other = nil
}
func (p *inodeParents) count() int {
if p.newest == nil {
return 0
}
return 1 + len(p.other)
}
type parentData struct {
name string
parent *Inode
}
package fs
import (
"testing"
)
func TestInodeParents(t *testing.T) {
var p inodeParents
var ino1, ino2, ino3 Inode
// empty store should be empty without panicing
if count := p.count(); count != 0 {
t.Error(count)
}
if p.all() != nil {
t.Error("empty store should return nil but did not")
}
// non-dupes should be stored
all := []parentData{
parentData{"foo", &ino1},
parentData{"foo2", &ino1},
parentData{"foo3", &ino1},
parentData{"foo", &ino2},
parentData{"foo", &ino3},
}
for i, v := range all {
p.add(v)
if count := p.count(); count != i+1 {
t.Errorf("want=%d have=%d", i+1, count)
}
last := p.get()
if *last != v {
t.Error("get did not give us last-known parent")
}
}
// adding dupes should not cause the count to increase, but
// must cause get() to return the most recently added dupe.
for _, v := range all {
p.add(v)
if count := p.count(); count != len(all) {
t.Errorf("want=%d have=%d", len(all), count)
}
last := p.get()
if *last != v {
t.Error("get did not give us last-known parent")
}
}
all2 := p.all()
if len(all) != len(all2) {
t.Errorf("want=%d have=%d", len(all), len(all2))
}
}
......@@ -104,6 +104,7 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
tc.rawFS = NewNodeFS(tc.loopback, &Options{
EntryTimeout: entryDT,
AttrTimeout: attrDT,
Logger: log.New(os.Stderr, "", 0),
})
mOpts := &fuse.MountOptions{}
......@@ -356,6 +357,24 @@ func TestMknod(t *testing.T) {
}
}
func TestMknodNotSupported(t *testing.T) {
mountPoint := testutil.TempDir()
defer os.Remove(mountPoint)
server, err := Mount(mountPoint, &Inode{}, nil)
if err != nil {
t.Fatalf("cannot mount: %v", err)
}
defer server.Unmount()
name := filepath.Join(mountPoint, "foo")
if got, want := syscall.Mknod(name, syscall.S_IFREG|0755, (8<<8)|0), syscall.ENOTSUP; got != want {
t.Fatalf("mknod: got %v, want %v", got, want)
}
}
func TestPosix(t *testing.T) {
noisy := map[string]bool{
"ParallelFileOpen": true,
......@@ -612,6 +631,51 @@ func TestFsstress(t *testing.T) {
}
}
// TestStaleHardlinks creates a lot of hard links and deletes them again
// behind the back of the loopback fs. Then opens the original file.
//
// Fails at the moment. Core of the problem:
//
// 18:41:50.796468 rx 136: LOOKUP n1 ["link0"] 6b
// 18:41:50.796489 tx 136: OK, {n2 g1 tE=0s tA=0s {M0100600 SZ=0 L=1 1026:1026 B0*4096 i0:269663 A 1616348510.793212 M 1616348510.793212 C 1616348510.795212}}
// 18:41:50.796535 rx 138: OPEN n2 {O_RDONLY,0x8000}
// 18:41:50.796557 tx 138: 2=no such file or directory, {Fh 0 }
func TestStaleHardlinks(t *testing.T) {
// Disable all caches we can disable
tc := newTestCase(t, &testOptions{attrCache: false, entryCache: false})
defer tc.Clean()
// "link0" is original file
link0 := tc.mntDir + "/link0"
if fd, err := syscall.Creat(link0, 0600); err != nil {
t.Fatal(err)
} else {
syscall.Close(fd)
}
// Create hardlinks via mntDir
for i := 1; i < 20; i++ {
linki := fmt.Sprintf(tc.mntDir+"/link%d", i)
if err := syscall.Link(link0, linki); err != nil {
t.Fatal(err)
}
}
// Delete hardlinks via origDir (behind loopback fs's back)
for i := 1; i < 20; i++ {
linki := fmt.Sprintf(tc.origDir+"/link%d", i)
if err := syscall.Unlink(linki); err != nil {
t.Fatal(err)
}
}
// Try to open link0 via mntDir
fd, err := syscall.Open(link0, syscall.O_RDONLY, 0)
if err != nil {
t.Error(err)
} else {
syscall.Close(fd)
}
}
func init() {
syscall.Umask(0)
}
......@@ -173,6 +173,13 @@ type MountOptions struct {
// Options passed to syscall.Mount, the default value used by fusermount
// is syscall.MS_NOSUID|syscall.MS_NODEV
DirectMountFlags uintptr
// EnableAcls enables kernel ACL support.
//
// See the comments to FUSE_CAP_POSIX_ACL
// in https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// for details.
EnableAcl bool
}
// RawFileSystem is an interface close to the FUSE wire protocol.
......
......@@ -95,6 +95,10 @@ func doInit(server *Server, req *request) {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
}
if server.opts.EnableAcl {
server.kernelSettings.Flags |= CAP_POSIX_ACL
}
dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA
if server.opts.ExplicitDataCacheControl {
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
......
......@@ -32,6 +32,9 @@ func doPollHackLookup(ms *Server, req *request) {
out := (*AttrOut)(req.outData())
out.Attr = attr
req.status = OK
case _OP_GETXATTR:
// Kernel will try to read acl xattrs. Pretend we don't have any.
req.status = ENODATA
case _OP_POLL:
req.status = ENOSYS
......
......@@ -485,8 +485,15 @@ func (ms *Server) handleRequest(req *request) Status {
errNo := ms.write(req)
if errNo != 0 {
log.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode))
// Unless debugging is enabled, ignore ENOENT for INTERRUPT responses
// which indicates that the referred request is no longer known by the
// kernel. This is a normal if the referred request already has
// completed.
if ms.opts.Debug || !(req.inHeader.Opcode == _OP_INTERRUPT && errNo == ENOENT) {
log.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode))
}
}
ms.returnRequest(req)
return Status(errNo)
......
......@@ -265,6 +265,13 @@ type OpenOut struct {
}
// To be set in InitIn/InitOut.Flags.
//
// Keep in sync with either of
// * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fuse.h
// * https://github.com/libfuse/libfuse/blob/master/include/fuse_kernel.h
// but NOT with
// * https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// This file has CAP_HANDLE_KILLPRIV and CAP_POSIX_ACL reversed!
const (
CAP_ASYNC_READ = (1 << 0)
CAP_POSIX_LOCKS = (1 << 1)
......
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