Commit 45eb49de authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: support Link

parent 34f928bf
......@@ -9,7 +9,8 @@ Decisions
=========
* Nodes contain references to their children. This is useful
because most filesystems will need to construct tree-like structures.
because most filesystems will need to construct tree-like
structures.
* Nodes can be "persistent", meaning their lifetime is not under
control of the kernel. This is useful for constructing FS trees
......@@ -25,6 +26,11 @@ Decisions
during their lifetime. It also prevents the common error of
forgetting to return the filetype in Lookup/GetAttr.
* Support for hard links. libfuse doesn't support this in the
high-level API. Extra care for race conditions is needed when
looking up the same file different paths.
To decide
=========
......
......@@ -82,7 +82,7 @@ type Operations interface {
//
// See InodeOf for public API to retrieve an inode from Node.
inode() *Inode
setInode(*Inode)
setInode(*Inode) bool
// File locking
GetLk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status)
......@@ -105,6 +105,7 @@ type Operations interface {
Unlink(ctx context.Context, name string) fuse.Status
Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) fuse.Status
Create(ctx context.Context, name string, flags uint32, mode uint32) (node *Inode, fh FileHandle, fuseFlags uint32, code fuse.Status)
Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (node *Inode, code fuse.Status)
Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, code fuse.Status)
Readlink(ctx context.Context) (string, fuse.Status)
Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, code fuse.Status)
......
......@@ -80,7 +80,7 @@ func (b *rawBridge) newInode(node Operations, mode uint32, id FileID, persistent
b.nodes[id.Ino] = inode
node.setInode(inode)
return inode
return node.inode()
}
func NewNodeFS(root Operations, opts *Options) fuse.RawFileSystem {
......@@ -370,12 +370,21 @@ func (b *rawBridge) Rename(input *fuse.RenameIn, oldName string, newName string)
return code
}
func (b *rawBridge) Link(input *fuse.LinkIn, filename string, out *fuse.EntryOut) (code fuse.Status) {
return fuse.ENOSYS
func (b *rawBridge) Link(input *fuse.LinkIn, name string, out *fuse.EntryOut) (code fuse.Status) {
parent, _ := b.inode(input.NodeId, 0)
target, _ := b.inode(input.Oldnodeid, 0)
child, code := parent.node.Link(context.TODO(), target.node, name, out)
if !code.Ok() {
return code
}
b.addNewChild(parent, name, child, nil, out)
b.setEntryOutTimeout(out)
return fuse.OK
}
func (b *rawBridge) Symlink(header *fuse.InHeader, target string, name string, out *fuse.EntryOut) (code fuse.Status) {
log.Println("symlink1")
parent, _ := b.inode(header.NodeId, 0)
child, code := parent.node.Symlink(context.TODO(), target, name, out)
if !code.Ok() {
......
......@@ -41,7 +41,7 @@ var _ Operations = &DefaultOperations{}
//
// To read node.inode atomic.LoadPointer is used, however it is not expensive
// since it translates to regular MOVQ on amd64.
func (dn *DefaultOperations) setInode(inode *Inode) *Inode {
func (dn *DefaultOperations) setInode(inode *Inode) bool {
return atomic.CompareAndSwapPointer(
(*unsafe.Pointer)(unsafe.Pointer(&dn.inode_)),
nil, unsafe.Pointer(inode))
......@@ -196,6 +196,9 @@ func (n *DefaultOperations) Open(ctx context.Context, flags uint32) (fh FileHand
func (n *DefaultOperations) Create(ctx context.Context, name string, flags uint32, mode uint32) (node *Inode, fh FileHandle, fuseFlags uint32, code fuse.Status) {
return nil, nil, 0, fuse.ENOSYS
}
func (n *DefaultOperations) Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (node *Inode, code fuse.Status) {
return nil, fuse.ENOSYS
}
type DefaultFile struct {
}
......
......@@ -135,6 +135,13 @@ func (n *loopbackNode) Unlink(ctx context.Context, name string) fuse.Status {
return fuse.ToStatus(err)
}
func toLoopbackNode(op Operations) *loopbackNode {
if r, ok := op.(*loopbackRoot); ok {
return &r.loopbackNode
}
return op.(*loopbackNode)
}
func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) fuse.Status {
if flags != 0 {
......@@ -142,12 +149,7 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Operat
}
p1 := filepath.Join(n.path(), name)
var newParentLoopback *loopbackNode
if r, ok := newParent.(*loopbackRoot); ok {
newParentLoopback = &r.loopbackNode
} else {
newParentLoopback = newParent.(*loopbackNode)
}
newParentLoopback := toLoopbackNode(newParent)
p2 := filepath.Join(newParentLoopback.path(), newName)
err := os.Rename(p1, p2)
......@@ -204,6 +206,26 @@ func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fu
return ch, fuse.OK
}
func (n *loopbackNode) Link(ctx context.Context, target Operations, name string, out *fuse.EntryOut) (*Inode, fuse.Status) {
p := filepath.Join(n.path(), name)
targetNode := toLoopbackNode(target)
err := syscall.Link(targetNode.path(), p)
if err != nil {
return nil, fuse.ToStatus(err)
}
st := syscall.Stat_t{}
if syscall.Lstat(p, &st); err != nil {
syscall.Unlink(p)
return nil, fuse.ToStatus(err)
}
node := n.rootNode.newLoopbackNode()
ch := n.inode().NewInode(node, st.Mode, idFromStat(&st))
out.Attr.FromStat(&st)
return ch, fuse.OK
}
func (n *loopbackNode) Readlink(ctx context.Context) (string, fuse.Status) {
p := n.path()
......
......@@ -424,3 +424,33 @@ func TestSymlink(t *testing.T) {
t.Errorf("Readlink: got %q, want %q", got, target)
}
}
func TestLink(t *testing.T) {
tc := newTestCase(t)
defer tc.Clean()
link := tc.mntDir + "/link"
target := tc.mntDir + "/target"
if err := ioutil.WriteFile(target, []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(target, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Link(target, link); err != nil {
t.Errorf("Link: %v", err)
}
if err := syscall.Lstat(link, &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if st.Ino != beforeIno {
t.Errorf("Lstat after: got %d, want %d", st.Ino, 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