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

nodefs: add Rename.

Inode.MvChild

Fix opaqueID
parent 29229b42
......@@ -91,6 +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
Open(ctx context.Context, flags uint32) (fh File, fuseFlags uint32, code fuse.Status)
......
......@@ -152,6 +152,10 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, out *f
b.registerInode(child)
}
out.NodeId = child.nodeID
// NOSUBMIT - or should let FS expose Attr.Ino? This makes
// testing semantics hard though, because os.Lstat doesn't
// reflect the FUSE FS
out.Attr.Ino = child.nodeID
out.Generation = b.nodes[out.NodeId].generation
b.mu.Unlock()
unlockNodes(parent, child)
......@@ -204,6 +208,7 @@ func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOu
}
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)
......@@ -254,6 +259,8 @@ func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) (code fuse
if b.options.AttrTimeout != nil {
out.SetTimeout(*b.options.AttrTimeout)
}
out.Ino = input.NodeId
return code
}
......@@ -327,7 +334,16 @@ func (b *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse
}
func (b *rawBridge) Rename(input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
return fuse.ENOSYS
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() {
// NOSUBMIT - is it better to have the user code do
// this? Maybe the user code wants a transaction over
// more nodes?
p1.MvChild(oldName, p2, newName)
}
return code
}
func (b *rawBridge) Link(input *fuse.LinkIn, filename string, out *fuse.EntryOut) (code fuse.Status) {
......
......@@ -5,6 +5,7 @@
package nodefs
import (
"fmt"
"log"
"sort"
"strings"
......@@ -66,6 +67,16 @@ type Inode struct {
parents map[parentData]struct{}
}
// debugString is used for debugging. Racy.
func (n *Inode) debugString() string {
var ss []string
for nm, ch := range n.children {
ss = append(ss, fmt.Sprintf("%q=%d(%d)", nm, ch.nodeID, ch.opaqueID))
}
return fmt.Sprintf("%d: %s", n.nodeID, strings.Join(ss, ","))
}
// newInode creates creates new inode pointing to node.
//
// node -> inode association is NOT set.
......@@ -276,6 +287,7 @@ func (n *Inode) newInode(node Node, mode uint32, opaqueID uint64, persistent boo
ch := &Inode{
mode: mode ^ 07777,
node: node,
opaqueID: opaqueID,
bridge: n.bridge,
persistent: persistent,
parents: make(map[parentData]struct{}),
......@@ -299,7 +311,7 @@ func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (forgotten bool,
n.mu.Lock()
if nlookup > 0 && dropPersistence {
panic("only one allowed")
log.Panic("only one allowed")
} else if nlookup > 0 {
n.lookupCount -= nlookup
n.changeCounter++
......@@ -414,3 +426,44 @@ retry:
return true, true
}
// TODO - RENAME_NOREPLACE, RENAME_EXCHANGE
func (n *Inode) MvChild(old string, newParent *Inode, newName string) {
retry:
for {
lockNode2(n, newParent)
counter1 := n.changeCounter
counter2 := newParent.changeCounter
oldChild := n.children[old]
destChild := newParent.children[newName]
unlockNode2(n, newParent)
lockNodes(n, newParent, oldChild, destChild)
if counter2 != newParent.changeCounter || counter1 != n.changeCounter {
unlockNodes(n, newParent, oldChild, destChild)
continue retry
}
if destChild != nil {
delete(newParent.children, newName)
delete(destChild.parents, parentData{newName, newParent})
destChild.changeCounter++
newParent.changeCounter++
}
if oldChild != nil {
newParent.children[newName] = oldChild
newParent.changeCounter++
delete(n.children, old)
delete(oldChild.parents, parentData{old, n})
oldChild.parents[parentData{newName, newParent}] = struct{}{}
oldChild.changeCounter++
}
unlockNodes(n, newParent, oldChild, destChild)
return
}
}
......@@ -115,6 +115,20 @@ 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 {
p1 := filepath.Join(n.path(), name)
var newParentLoopback *loopbackNode
if r, ok := newParent.(*loopbackRoot); ok {
newParentLoopback = &r.loopbackNode
} else {
newParentLoopback = newParent.(*loopbackNode)
}
p2 := filepath.Join(newParentLoopback.path(), newName)
err := os.Rename(p1, p2)
return fuse.ToStatus(err)
}
func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32) (inode *Inode, fh File, fuseFlags uint32, code fuse.Status) {
p := filepath.Join(n.path(), name)
......
......@@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"runtime"
"syscall"
"testing"
"time"
......@@ -59,9 +60,12 @@ func newTestCase(t *testing.T) *testCase {
}
loopback := NewLoopback(tc.origDir)
_ = time.Second
oneSec := time.Second
tc.rawFS = NewNodeFS(loopback, &Options{
Debug: testutil.VerboseTest(),
Debug: testutil.VerboseTest(),
// NOSUBMIT - should run all tests without cache too
EntryTimeout: &oneSec,
AttrTimeout: &oneSec,
})
......@@ -244,3 +248,36 @@ func TestMkdir(t *testing.T) {
t.Fatalf("Remove: %v", err)
}
}
func TestRename(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)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(tc.mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Rename(tc.mntDir+"/file", tc.mntDir+"/dir/renamed"); err != nil {
t.Errorf("Rename: %v", err)
}
if fi, err := os.Lstat(tc.mntDir + "/file"); err == nil {
t.Fatalf("Lstat old: %v", fi)
}
if err := syscall.Lstat(tc.mntDir+"/dir/renamed", &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if got := st.Ino; got != beforeIno {
t.Errorf("got ino %d, want %d", got, beforeIno)
}
}
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