Commit c4765ac7 authored by Jakob Unterwurzacher's avatar Jakob Unterwurzacher Committed by Han-Wen Nienhuys

fs: ReadDirPlus: don't add ".." and "." to the tree

These are not actual directories, and adding them to
our tree causes directory cycles, so don't.

Before this change, running

  $ ./extractloop.bash -loopback

from https://github.com/rfjakob/gocryptfs/tree/master/tests/stress_tests
quickly caused loopback to get stuck and consume all
available memory. Things look stable now.

Add a test to verify that rawBridge does not call LOOKUP
on "." and "..".

Also, add a comment to the "for pd = range p.parents" loop,
that has caused some head-scratching during debugging.

Change-Id: I15eedeee30043dc57e5fa580fefe59a48674b193
parent 1cb24af2
......@@ -116,6 +116,9 @@ func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAt
// addNewChild inserts the child into the tree. Returns file handle if file != nil.
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) uint32 {
if name == "." || name == ".." {
log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name)
}
lockNodes(parent, child)
parent.setEntry(name, child)
b.mu.Lock()
......@@ -880,6 +883,15 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
return fuse.OK
}
// Virtual entries "." and ".." should be part of the
// directory listing, but not part of the filesystem tree.
// The values in EntryOut are ignored by Linux
// (see fuse_direntplus_link() in linux/fs/fuse/readdir.c), so leave
// them at zero-value.
if e.Name == "." || e.Name == ".." {
continue
}
child, errno := b.lookup(ctx, n, e.Name, entryOut)
if errno != 0 {
if b.options.NegativeTimeout != nil {
......
// Copyright 2019 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
import (
"testing"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
// TestBridgeReaddirPlusVirtualEntries looks at "." and ".." in the ReadDirPlus
// output. They should exist, but the NodeId should be zero.
func TestBridgeReaddirPlusVirtualEntries(t *testing.T) {
// Set suppressDebug as we do our own logging
tc := newTestCase(t, &testOptions{suppressDebug: true})
defer tc.Clean()
rb := tc.rawFS.(*rawBridge)
// We only populate what rawBridge.OpenDir() actually looks at.
openIn := fuse.OpenIn{}
openIn.NodeId = 1 // root node always has id 1 and always exists
openOut := fuse.OpenOut{}
status := rb.OpenDir(nil, &openIn, &openOut)
if !status.Ok() {
t.Fatal(status)
}
// We only populate what rawBridge.ReadDirPlus() actually looks at.
readIn := fuse.ReadIn{}
readIn.NodeId = 1
readIn.Fh = openOut.Fh
buf := make([]byte, 400)
dirents := fuse.NewDirEntryList(buf, 0)
status = rb.ReadDirPlus(nil, &readIn, dirents)
if !status.Ok() {
t.Fatal(status)
}
// Parse the output buffer. Looks like this in memory:
// 1) fuse.EntryOut
// 2) fuse._Dirent
// 3) Name (null-terminated)
// 4) Padding to align to 8 bytes
// [repeat]
const entryOutSize = int(unsafe.Sizeof(fuse.EntryOut{}))
// = unsafe.Sizeof(fuse._Dirent{}), see fuse/types.go
const direntSize = 24
// Round up to 8.
const entry2off = (entryOutSize + direntSize + len(".\x00") + 7) / 8 * 8
// 1st entry should be "."
entry1 := (*fuse.EntryOut)(unsafe.Pointer(&buf[0]))
name1 := string(buf[entryOutSize+direntSize : entryOutSize+direntSize+2])
if name1 != ".\x00" {
t.Errorf("Unexpected 1st entry %q", name1)
}
t.Logf("entry1 %q: %#v", name1, entry1)
// 2nd entry should be ".."
entry2 := (*fuse.EntryOut)(unsafe.Pointer(&buf[entry2off]))
name2 := string(buf[entry2off+entryOutSize+direntSize : entry2off+entryOutSize+direntSize+2])
if name2 != ".." {
t.Errorf("Unexpected 2nd entry %q", name2)
}
t.Logf("entry2 %q: %#v", name2, entry2)
if entry1.NodeId != 0 {
t.Errorf("entry1 NodeId should be 0, but is %d", entry1.NodeId)
}
if entry2.NodeId != 0 {
t.Errorf("entry2 NodeId should be 0, but is %d", entry2.NodeId)
}
}
......@@ -271,6 +271,7 @@ func (n *Inode) Path(root *Inode) string {
// We don't try to take all locks at the same time, because
// the caller won't use the "path" string under lock anyway.
p.mu.Lock()
// Select an arbitrary parent
for pd = range p.parents {
break
}
......
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