Commit e5194406 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: Rename2 support, kinda.

parent 89a1a0c7
......@@ -91,7 +91,7 @@ type Node interface {
Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, fuse.Status)
Rmdir(ctx context.Context, name string) fuse.Status
Unlink(ctx context.Context, name string) fuse.Status
Rename(ctx context.Context, name string, newParent Node, newName string) fuse.Status
Rename(ctx context.Context, name string, newParent Node, newName string, flags uint32) fuse.Status
Open(ctx context.Context, flags uint32) (fh File, fuseFlags uint32, code fuse.Status)
......
......@@ -11,6 +11,7 @@ import (
"time"
"github.com/hanwen/go-fuse/fuse"
"golang.org/x/sys/unix"
)
type mapEntry struct {
......@@ -142,7 +143,7 @@ func (b *rawBridge) Lookup(header *fuse.InHeader, name string, out *fuse.EntryOu
return code
}
b.addNewChild(parent, name, child, out)
b.addNewChild(parent, name, child, nil, out)
b.setEntryOutTimeout(out)
return fuse.OK
}
......@@ -178,7 +179,7 @@ func (b *rawBridge) Mkdir(input *fuse.MkdirIn, name string, out *fuse.EntryOut)
log.Panicf("Mkdir: mode must be S_IFDIR (%o), got %o", fuse.S_IFDIR, out.Attr.Mode)
}
b.addNewChild(parent, name, child, out)
b.addNewChild(parent, name, child, nil, out)
b.setEntryOutTimeout(out)
return fuse.OK
}
......@@ -191,18 +192,26 @@ func (b *rawBridge) Mknod(input *fuse.MknodIn, name string, out *fuse.EntryOut)
return code
}
b.addNewChild(parent, name, child, out)
b.addNewChild(parent, name, child, nil, out)
b.setEntryOutTimeout(out)
return fuse.OK
}
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, out *fuse.EntryOut) {
// addNewChild inserts the child into the tree. Returns file handle if file != nil.
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file File, out *fuse.EntryOut) uint64 {
lockNodes(parent, child)
parent.setEntry(name, child)
b.mu.Lock()
child.lookupCount++
if child.nodeID == 0 {
b.registerInode(child)
}
var fh uint64
if file != nil {
fh = b.registerFile(file)
}
out.NodeId = child.nodeID
// NOSUBMIT - or should let FS expose Attr.Ino? This makes
// testing semantics hard though, because os.Lstat doesn't
......@@ -211,6 +220,7 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, out *f
out.Generation = b.nodes[out.NodeId].generation
b.mu.Unlock()
unlockNodes(parent, child)
return fh
}
func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
......@@ -252,19 +262,7 @@ func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOu
return code
}
lockNode2(parent, child)
parent.setEntry(name, child)
b.mu.Lock()
if child.nodeID == 0 {
b.registerInode(child)
}
out.Fh = b.registerFile(f)
out.NodeId = child.nodeID
out.Ino = child.nodeID
out.Generation = b.nodes[child.nodeID].generation
b.mu.Unlock()
unlockNode2(parent, child)
out.Fh = b.addNewChild(parent, name, child, f, &out.EntryOut)
b.setEntryOutTimeout(&out.EntryOut)
out.OpenFlags = flags
......@@ -278,10 +276,7 @@ func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOu
}
func (b *rawBridge) Forget(nodeid, nlookup uint64) {
b.mu.Lock()
n := b.nodes[nodeid].inode
b.mu.Unlock()
n, _ := b.inode(nodeid, 0)
n.removeRef(nlookup, false)
}
......@@ -308,14 +303,18 @@ func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) (code fuse
out.Nlink = 1
}
if b.options.AttrTimeout != nil {
out.SetTimeout(*b.options.AttrTimeout)
}
b.setAttrTimeout(out)
out.Ino = input.NodeId
return code
}
func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
if b.options.AttrTimeout != nil {
out.SetTimeout(*b.options.AttrTimeout)
}
}
func (b *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse.Status) {
ctx := context.TODO()
......@@ -379,19 +378,26 @@ func (b *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse
// Must call GetAttr(); the filesystem may override some of
// the changes we effect here.
attr := &out.Attr
code = n.node.GetAttr(ctx, f, attr)
// TODO - attr timout?
// should take AttrOut
code = n.node.GetAttr(ctx, f, attr)
b.setAttrTimeout(out)
return code
}
func (b *rawBridge) Rename(input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
func (b *rawBridge) Rename(input *fuse.RenameIn, oldName string, newName string) fuse.Status {
p1, _ := b.inode(input.NodeId, 0)
p2, _ := b.inode(input.Newdir, 0)
if code := p1.node.Rename(context.TODO(), oldName, p2.node, newName); code.Ok() {
code := p1.node.Rename(context.TODO(), oldName, p2.node, newName, input.Flags)
if code.Ok() {
if input.Flags&unix.RENAME_EXCHANGE != 0 {
// XXX - test coverage.
p1.ExchangeChild(oldName, p2, newName)
} else {
p1.MvChild(oldName, p2, newName, true)
}
}
return code
}
......@@ -541,5 +547,6 @@ func (b *rawBridge) FsyncDir(input *fuse.FsyncIn) (code fuse.Status) {
func (b *rawBridge) StatFs(input *fuse.InHeader, out *fuse.StatfsOut) (code fuse.Status) {
return
}
func (b *rawBridge) Init(*fuse.Server) {
}
......@@ -279,6 +279,7 @@ func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (forgotten bool,
if nlookup > 0 && dropPersistence {
log.Panic("only one allowed")
} else if nlookup > 0 {
n.lookupCount -= nlookup
n.changeCounter++
} else if dropPersistence && n.persistent {
......@@ -396,9 +397,8 @@ retry:
return true, true
}
// TODO - RENAME_NOREPLACE, RENAME_EXCHANGE MvChild executes a
// rename. If overwrite is set, a child at the destination will be
// overwritten.
// MvChild executes a rename. If overwrite is set, a child at the
// destination will be overwritten.
func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool {
retry:
for {
......@@ -453,3 +453,64 @@ retry:
return true
}
}
func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string) {
oldParent := n
retry:
for {
lockNode2(oldParent, newParent)
counter1 := oldParent.changeCounter
counter2 := newParent.changeCounter
oldChild := oldParent.children[oldName]
destChild := newParent.children[newName]
unlockNode2(oldParent, newParent)
if destChild == nil && oldChild == nil {
return
}
if destChild == oldChild {
return
}
lockNodes(oldParent, newParent, oldChild, destChild)
if counter2 != newParent.changeCounter || counter1 != oldParent.changeCounter {
unlockNodes(oldParent, newParent, oldChild, destChild)
continue retry
}
// Detach
if oldChild != nil {
delete(oldParent.children, oldName)
delete(oldChild.parents, parentData{oldName, oldParent})
oldParent.changeCounter++
oldChild.changeCounter++
}
if destChild != nil {
delete(newParent.children, newName)
delete(destChild.parents, parentData{newName, newParent})
destChild.changeCounter++
newParent.changeCounter++
}
// Attach
if oldChild != nil {
newParent.children[newName] = oldChild
newParent.changeCounter++
oldChild.parents[parentData{newName, newParent}] = struct{}{}
oldChild.changeCounter++
}
if destChild != nil {
oldParent.children[oldName] = oldChild
oldParent.changeCounter++
destChild.parents[parentData{oldName, oldParent}] = struct{}{}
destChild.changeCounter++
}
unlockNodes(oldParent, newParent, oldChild, destChild)
return
}
}
......@@ -123,7 +123,12 @@ func (n *loopbackNode) Unlink(ctx context.Context, name string) fuse.Status {
return fuse.ToStatus(err)
}
func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Node, newName string) fuse.Status {
func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Node, newName string, flags uint32) fuse.Status {
if flags != 0 {
return fuse.ENOSYS
}
p1 := filepath.Join(n.path(), name)
var newParentLoopback *loopbackNode
if r, ok := newParent.(*loopbackRoot); ok {
......
......@@ -14,6 +14,8 @@ import (
"testing"
"time"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
......@@ -249,7 +251,7 @@ func TestMkdir(t *testing.T) {
}
}
func TestRename(t *testing.T) {
func testRenameOverwrite(t *testing.T, destExists bool) {
tc := newTestCase(t)
defer tc.Clean()
......@@ -260,6 +262,12 @@ func TestRename(t *testing.T) {
t.Fatalf("WriteFile: %v", err)
}
if destExists {
if err := ioutil.WriteFile(tc.origDir+"/dir/renamed", []byte("xx"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
}
st := syscall.Stat_t{}
if err := syscall.Lstat(tc.mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat before: %v", err)
......@@ -281,3 +289,49 @@ func TestRename(t *testing.T) {
t.Errorf("got ino %d, want %d", got, beforeIno)
}
}
func TestRenameDestExist(t *testing.T) {
testRenameOverwrite(t, true)
}
func TestRenameDestNoExist(t *testing.T) {
testRenameOverwrite(t, false)
}
func TestRenameNoOverwrite(t *testing.T) {
tc := newTestCase(t)
defer tc.Clean()
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := ioutil.WriteFile(tc.origDir+"/file", []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := ioutil.WriteFile(tc.origDir+"/dir/file", []byte("x"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 1: %v", err)
}
defer syscall.Close(f1)
f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatalf("open 2: %v", err)
}
defer syscall.Close(f2)
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_NOREPLACE); err == nil {
t.Errorf("rename NOREPLACE succeeded")
} else if err != syscall.EEXIST {
t.Errorf("got %v (%T) want EEXIST", err, err)
}
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err == nil {
t.Errorf("rename EXCHANGE succeeded")
} else if err != syscall.EINVAL {
t.Errorf("got %v (%T) want %v (%T)", err, err, syscall.EINVAL, syscall.EINVAL)
}
}
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