v2api/reverse: finish -exclude

Tests pass now.
This commit is contained in:
Jakob Unterwurzacher 2020-08-15 17:31:25 +02:00
parent 15b0b4a5fd
commit 94e8fc12ea
10 changed files with 109 additions and 64 deletions

View File

@ -2,6 +2,7 @@ package fusefrontend_reverse
import ( import (
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"strings" "strings"
@ -15,12 +16,9 @@ import (
// prepareExcluder creates an object to check if paths are excluded // prepareExcluder creates an object to check if paths are excluded
// based on the patterns specified in the command line. // based on the patterns specified in the command line.
func prepareExcluder(args fusefrontend.Args) *ignore.GitIgnore { func prepareExcluder(args fusefrontend.Args) *ignore.GitIgnore {
if len(args.Exclude) == 0 && len(args.ExcludeWildcard) == 0 && len(args.ExcludeFrom) == 0 {
return nil
}
patterns := getExclusionPatterns(args) patterns := getExclusionPatterns(args)
if len(patterns) == 0 { if len(patterns) == 0 {
panic(patterns) log.Panic(patterns)
} }
excluder, err := ignore.CompileIgnoreLines(patterns...) excluder, err := ignore.CompileIgnoreLines(patterns...)
if err != nil { if err != nil {

View File

@ -9,14 +9,6 @@ import (
"github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/fusefrontend"
) )
func TestShouldNoCreateExcluderIfNoPattersWereSpecified(t *testing.T) {
var args fusefrontend.Args
excluder := prepareExcluder(args)
if excluder != nil {
t.Error("Should not have created excluder")
}
}
func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) { func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) {
var args fusefrontend.Args var args fusefrontend.Args
args.Exclude = []string{"file1", "dir1/file2.txt"} args.Exclude = []string{"file1", "dir1/file2.txt"}

View File

@ -26,8 +26,7 @@ type Node struct {
// Lookup - FUSE call for discovering a file. // Lookup - FUSE call for discovering a file.
func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
dirfd := int(-1) var d *dirfdPlus
pName := ""
t := n.lookupFileType(cName) t := n.lookupFileType(cName)
if t == typeDiriv { if t == typeDiriv {
// gocryptfs.diriv // gocryptfs.diriv
@ -40,14 +39,15 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
return n.lookupConf(ctx, out) return n.lookupConf(ctx, out)
} else if t == typeReal { } else if t == typeReal {
// real file // real file
dirfd, pName, errno = n.prepareAtSyscall(cName) d, errno = n.prepareAtSyscall(cName)
//fmt.Printf("Lookup: prepareAtSyscall -> d=%#v, errno=%d\n", d, errno)
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
} }
// Get device number and inode number into `st` // Get device number and inode number into `st`
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
return nil, fs.ToErrno(err) return nil, fs.ToErrno(err)
} }
@ -55,7 +55,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
ch = n.newChild(ctx, st, out) ch = n.newChild(ctx, st, out)
// Translate ciphertext size in `out.Attr.Size` to plaintext size // Translate ciphertext size in `out.Attr.Size` to plaintext size
if t == typeReal { if t == typeReal {
n.translateSize(dirfd, cName, pName, &out.Attr) n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
} }
return ch, 0 return ch, 0
} }
@ -69,13 +69,13 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
return f.(fs.FileGetattrer).Getattr(ctx, out) return f.(fs.FileGetattrer).Getattr(ctx, out)
} }
dirfd, pName, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
return fs.ToErrno(err) return fs.ToErrno(err)
} }
@ -87,7 +87,7 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
// Translate ciphertext size in `out.Attr.Size` to plaintext size // Translate ciphertext size in `out.Attr.Size` to plaintext size
cName := filepath.Base(n.Path()) cName := filepath.Base(n.Path())
n.translateSize(dirfd, cName, pName, &out.Attr) n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
if rn.args.ForceOwner != nil { if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner out.Owner = *rn.args.ForceOwner
@ -99,27 +99,26 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
// //
// Symlink-safe through openBackingDir() + Readlinkat(). // Symlink-safe through openBackingDir() + Readlinkat().
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) { func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
dirfd, pName, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
cName := filepath.Base(n.Path()) return n.readlink(d.dirfd, d.cName, d.pName)
return n.readlink(dirfd, cName, pName)
} }
// Open - FUSE call. Open already-existing file. // Open - FUSE call. Open already-existing file.
// //
// Symlink-safe through Openat(). // Symlink-safe through Openat().
func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
dirfd, pName, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
fd, err := syscallcompat.Openat(dirfd, pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
return return

View File

@ -23,15 +23,15 @@ import (
// This function is symlink-safe through use of openBackingDir() and // This function is symlink-safe through use of openBackingDir() and
// ReadDirIVAt(). // ReadDirIVAt().
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) { func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
dirfd, cName, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
// Read plaintext directory // Read plaintext directory
var entries []fuse.DirEntry var entries []fuse.DirEntry
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil { if err != nil {
return nil, fs.ToErrno(err) return nil, fs.ToErrno(err)
} }
@ -42,21 +42,20 @@ func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.
} }
rn := n.rootNode() rn := n.rootNode()
// Filter out excluded entries
entries = rn.excludeDirEntries(d, entries)
if rn.args.PlaintextNames { if rn.args.PlaintextNames {
return n.readdirPlaintextnames(entries) return n.readdirPlaintextnames(entries)
} }
// Filter out excluded entries
//TODO
//entries = rfs.excludeDirEntries(relPath, entries)
// Virtual files: at least one gocryptfs.diriv file // Virtual files: at least one gocryptfs.diriv file
virtualFiles := []fuse.DirEntry{ virtualFiles := []fuse.DirEntry{
{Mode: virtualFileMode, Name: nametransform.DirIVFilename}, {Mode: virtualFileMode, Name: nametransform.DirIVFilename},
} }
cipherPath := n.Path() dirIV := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
dirIV := pathiv.Derive(cipherPath, pathiv.PurposeDirIV)
// Encrypt names // Encrypt names
for i := range entries { for i := range entries {
var cName string var cName string

View File

@ -47,6 +47,20 @@ func (n *Node) rootNode() *RootNode {
return n.Root().Operations().(*RootNode) return n.Root().Operations().(*RootNode)
} }
// dirfdPlus gets filled out as we gather information about a node
type dirfdPlus struct {
// fd to the directory, opened with O_DIRECTORY|O_PATH
dirfd int
// Relative plaintext path
pPath string
// Plaintext basename: filepath.Base(pPath)
pName string
// Relative ciphertext path
cPath string
// Ciphertext basename: filepath.Base(cPath)
cName string
}
// prepareAtSyscall returns a (dirfd, cName) pair that can be used // prepareAtSyscall returns a (dirfd, cName) pair that can be used
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to // with the "___at" family of system calls (openat, fstatat, unlinkat...) to
// access the backing encrypted directory. // access the backing encrypted directory.
@ -54,16 +68,23 @@ func (n *Node) rootNode() *RootNode {
// If you pass a `child` file name, the (dirfd, cName) pair will refer to // If you pass a `child` file name, the (dirfd, cName) pair will refer to
// a child of this node. // a child of this node.
// If `child` is empty, the (dirfd, cName) pair refers to this node itself. // If `child` is empty, the (dirfd, cName) pair refers to this node itself.
func (n *Node) prepareAtSyscall(child string) (dirfd int, pName string, errno syscall.Errno) { func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno) {
p := n.Path() cPath := n.Path()
if child != "" { if child != "" {
p = filepath.Join(p, child) cPath = filepath.Join(cPath, child)
} }
rn := n.rootNode() rn := n.rootNode()
dirfd, pName, err := rn.openBackingDir(p) dirfd, pPath, err := rn.openBackingDir(cPath)
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
} }
d = &dirfdPlus{
dirfd: dirfd,
pPath: pPath,
pName: filepath.Base(pPath),
cPath: cPath,
cName: filepath.Base(cPath),
}
return return
} }
@ -91,28 +112,32 @@ func (n *Node) isRoot() bool {
} }
func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
dirfd, pName1, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
// Find the file the gocryptfs.longname.XYZ.name file belongs to in the // Find the file the gocryptfs.longname.XYZ.name file belongs to in the
// directory listing // directory listing
fd, err := syscallcompat.Openat(dirfd, pName1, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
return return
} }
defer syscall.Close(fd) defer syscall.Close(fd)
diriv := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) diriv := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
rn := n.rootNode() rn := n.rootNode()
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile) pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
if errno != 0 { if errno != 0 {
return return
} }
if rn.isExcludedPlain(filepath.Join(d.cPath, pName)) {
errno = syscall.EPERM
return
}
// Get attrs from parent file // Get attrs from parent file
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) st, err := syscallcompat.Fstatat2(fd, pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
return return
@ -132,17 +157,17 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`. // lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.
func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
dirfd, pName, errno := n.prepareAtSyscall("") d, errno := n.prepareAtSyscall("")
if errno != 0 { if errno != 0 {
return return
} }
defer syscall.Close(dirfd) defer syscall.Close(d.dirfd)
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
return return
} }
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
var vf *VirtualMemNode var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV) vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 { if errno != 0 {

View File

@ -2,12 +2,16 @@ package fusefrontend_reverse
import ( import (
"log" "log"
"path/filepath"
"strings" "strings"
"syscall" "syscall"
"github.com/rfjakob/gocryptfs/internal/tlog"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/fusefrontend"
@ -28,7 +32,7 @@ type RootNode struct {
// Content encryption helper // Content encryption helper
contentEnc *contentenc.ContentEnc contentEnc *contentenc.ContentEnc
// Tests whether a path is excluded (hidden) from the user. Used by -exclude. // Tests whether a path is excluded (hidden) from the user. Used by -exclude.
excluder *ignore.GitIgnore excluder ignore.IgnoreParser
// inoMap translates inode numbers from different devices to unique inode // inoMap translates inode numbers from different devices to unique inode
// numbers. // numbers.
inoMap *inomap.InoMap inoMap *inomap.InoMap
@ -38,17 +42,23 @@ type RootNode struct {
// In this case (reverse mode) the backing directory is plain-text and // In this case (reverse mode) the backing directory is plain-text and
// ReverseFS provides an encrypted view. // ReverseFS provides an encrypted view.
func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode { func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode {
return &RootNode{ rn := &RootNode{
args: args, args: args,
nameTransform: n, nameTransform: n,
contentEnc: c, contentEnc: c,
inoMap: inomap.New(), inoMap: inomap.New(),
excluder: prepareExcluder(args),
} }
if len(args.Exclude) > 0 || len(args.ExcludeWildcard) > 0 || len(args.ExcludeFrom) > 0 {
rn.excluder = prepareExcluder(args)
}
return rn
} }
// You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ. // You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ.
func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) { func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) {
defer func() {
tlog.Debug.Printf("findLongnameParent: %d %x %q -> %q %q %d\n", fd, diriv, longname, pName, cFullName, errno)
}()
if strings.HasSuffix(longname, nametransform.LongNameSuffix) { if strings.HasSuffix(longname, nametransform.LongNameSuffix) {
longname = nametransform.RemoveLongNameSuffix(longname) longname = nametransform.RemoveLongNameSuffix(longname)
} }
@ -84,3 +94,24 @@ func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (p
func (rn *RootNode) isExcludedPlain(pPath string) bool { func (rn *RootNode) isExcludedPlain(pPath string) bool {
return rn.excluder != nil && rn.excluder.MatchesPath(pPath) return rn.excluder != nil && rn.excluder.MatchesPath(pPath)
} }
// excludeDirEntries filters out directory entries that are "-exclude"d.
// pDir is the relative plaintext path to the directory these entries are
// from. The entries should be plaintext files.
func (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (filtered []fuse.DirEntry) {
if rn.excluder == nil {
return entries
}
filtered = make([]fuse.DirEntry, 0, len(entries))
for _, entry := range entries {
// filepath.Join handles the case of pDir="" correctly:
// Join("", "foo") -> "foo". This does not: pDir + "/" + name"
p := filepath.Join(d.pPath, entry.Name)
if rn.isExcludedPlain(p) {
// Skip file
continue
}
filtered = append(filtered, entry)
}
return filtered
}

View File

@ -100,25 +100,24 @@ func (rn *RootNode) decryptPath(cPath string) (string, error) {
// and returns the fd to the directory and the decrypted name of the // and returns the fd to the directory and the decrypted name of the
// target file. The fd/name pair is intended for use with fchownat and // target file. The fd/name pair is intended for use with fchownat and
// friends. // friends.
func (rn *RootNode) openBackingDir(cPath string) (dirfd int, pName string, err error) { func (rn *RootNode) openBackingDir(cPath string) (dirfd int, pPath string, err error) {
defer func() { defer func() {
tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pName, err) tlog.Debug.Printf("openBackingDir %q -> %d %q %v\n", cPath, dirfd, pPath, err)
}() }()
dirfd = -1 dirfd = -1
pRelPath, err := rn.decryptPath(cPath) pPath, err = rn.decryptPath(cPath)
if err != nil { if err != nil {
return return
} }
if rn.isExcludedPlain(pRelPath) { if rn.isExcludedPlain(pPath) {
err = syscall.EPERM err = syscall.EPERM
return return
} }
// Open directory, safe against symlink races // Open directory, safe against symlink races
pDir := filepath.Dir(pRelPath) pDir := filepath.Dir(pPath)
dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir) dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir)
if err != nil { if err != nil {
return return
} }
pName = filepath.Base(pRelPath) return dirfd, pPath, nil
return dirfd, pName, nil
} }

View File

@ -87,7 +87,7 @@ func TestSymlinkDentrySize(t *testing.T) {
fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result) fi, err := os.Lstat(mnt + "/" + symlinkResponse.Result)
if err != nil { if err != nil {
t.Errorf("Lstat: %v", err) t.Fatalf("Lstat: %v", err)
} }
target, err := os.Readlink(mnt + "/" + symlinkResponse.Result) target, err := os.Readlink(mnt + "/" + symlinkResponse.Result)

View File

@ -118,6 +118,7 @@ func testExclude(t *testing.T, flag string) {
cExclude := encryptExcludeTestPaths(t, sock, pExclude) cExclude := encryptExcludeTestPaths(t, sock, pExclude)
// Check that "excluded" paths are not there and "ok" paths are there // Check that "excluded" paths are not there and "ok" paths are there
for _, v := range cExclude { for _, v := range cExclude {
t.Logf("File %q should be invisible", v)
if test_helpers.VerifyExistence(t, mnt+"/"+v) { if test_helpers.VerifyExistence(t, mnt+"/"+v) {
t.Errorf("File %q is visible, but should be excluded", v) t.Errorf("File %q is visible, but should be excluded", v)
} }
@ -126,6 +127,7 @@ func testExclude(t *testing.T, flag string) {
} }
} }
for _, v := range cOk { for _, v := range cOk {
t.Logf("File %q should be visible", v)
if !test_helpers.VerifyExistence(t, mnt+"/"+v) { if !test_helpers.VerifyExistence(t, mnt+"/"+v) {
t.Errorf("File %q is hidden, but should be visible", v) t.Errorf("File %q is hidden, but should be visible", v)
} }

View File

@ -45,7 +45,7 @@ func findIno(dir string, ino uint64) string {
// ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child // ├── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50 <---- child
// └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name // └── gocryptfs.longname.e31v1ax4h_F0l4jhlN8kCjaWWMq8rO9VVBZ15IYsV50.name <---- name
// //
// And verifies that the inode numbers match what we expect. // It verifies that the inode numbers match what we expect.
func TestVirtualFileIno(t *testing.T) { func TestVirtualFileIno(t *testing.T) {
if plaintextnames { if plaintextnames {
t.Skip("plaintextnames mode does not have virtual files") t.Skip("plaintextnames mode does not have virtual files")
@ -111,7 +111,7 @@ func TestVirtualFileIno(t *testing.T) {
var st2 syscall.Stat_t var st2 syscall.Stat_t
err = syscall.Lstat(encryptedParent+"/"+entry, &st2) err = syscall.Lstat(encryptedParent+"/"+entry, &st2)
if err != nil { if err != nil {
t.Logf("stat %q: %v", entry, err) t.Errorf("stat %q: %v", entry, err)
continue continue
} }
if entry == "gocryptfs.diriv" { if entry == "gocryptfs.diriv" {