From ebdf58b9ebf366ac6dbf500c0f4f4d211451bb44 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 21 Jun 2020 12:42:18 +0200 Subject: [PATCH] v2api: implement GetAttr and Readdir --- internal/fusefrontend/node.go | 27 ++++++-- internal/fusefrontend/node_dir_ops.go | 97 +++++++++++++++++++++++++++ internal/fusefrontend/root_node.go | 24 +++++++ 3 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 internal/fusefrontend/node_dir_ops.go create mode 100644 internal/fusefrontend/root_node.go diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index f246408..f4359a2 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -31,6 +31,15 @@ type RootNode struct { nameTransform nametransform.NameTransformer // Content encryption helper contentEnc *contentenc.ContentEnc + // MitigatedCorruptions is used to report data corruption that is internally + // mitigated by ignoring the corrupt item. For example, when OpenDir() finds + // a corrupt filename, we still return the other valid filenames. + // The corruption is logged to syslog to inform the user, and in addition, + // the corrupt filename is logged to this channel via + // reportMitigatedCorruption(). + // "gocryptfs -fsck" reads from the channel to also catch these transparently- + // mitigated corruptions. + MitigatedCorruptions chan string // IsIdle flag is set to zero each time fs.isFiltered() is called // (uint32 so that it can be reset with CompareAndSwapUint32). // When -idle was used when mounting, idleMonitor() sets it to 1 @@ -68,6 +77,7 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs if err != nil { return nil, fs.ToErrno(err) } + defer syscall.Close(dirfd) // Get device number and inode number into `st` st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) if err != nil { @@ -88,9 +98,18 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs } func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { - return 1 -} + rn := n.rootNode() + dirfd, cName, err := rn.openBackingDir(n.path()) + if err != nil { + return fs.ToErrno(err) + } + defer syscall.Close(dirfd) -func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { - return nil, 1 + st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + return fs.ToErrno(err) + } + rn.inoMap.TranslateStat(st) + out.Attr.FromStat(st) + return 0 } diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go new file mode 100644 index 0000000..38d0940 --- /dev/null +++ b/internal/fusefrontend/node_dir_ops.go @@ -0,0 +1,97 @@ +package fusefrontend + +import ( + "context" + "path/filepath" + "syscall" + + "github.com/hanwen/go-fuse/v2/fs" + "github.com/hanwen/go-fuse/v2/fuse" + + "github.com/rfjakob/gocryptfs/internal/configfile" + "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/syscallcompat" + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { + rn := n.rootNode() + p := n.path() + dirName := filepath.Base(p) + parentDirFd, cDirName, err := rn.openBackingDir(p) + if err != nil { + return nil, fs.ToErrno(err) + } + defer syscall.Close(parentDirFd) + + // Read ciphertext directory + var cipherEntries []fuse.DirEntry + fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + return nil, fs.ToErrno(err) + } + defer syscall.Close(fd) + cipherEntries, err = syscallcompat.Getdents(fd) + if err != nil { + return nil, fs.ToErrno(err) + } + // Get DirIV (stays nil if PlaintextNames is used) + var cachedIV []byte + if !rn.args.PlaintextNames { + // Read the DirIV from disk + cachedIV, err = nametransform.ReadDirIVAt(fd) + if err != nil { + tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err) + return nil, syscall.EIO + } + } + // Decrypted directory entries + var plain []fuse.DirEntry + // Filter and decrypt filenames + for i := range cipherEntries { + cName := cipherEntries[i].Name + if dirName == "." && cName == configfile.ConfDefaultName { + // silently ignore "gocryptfs.conf" in the top level dir + continue + } + if rn.args.PlaintextNames { + plain = append(plain, cipherEntries[i]) + continue + } + if cName == nametransform.DirIVFilename { + // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled + continue + } + // Handle long file name + isLong := nametransform.LongNameNone + if rn.args.LongNames { + isLong = nametransform.NameType(cName) + } + if isLong == nametransform.LongNameContent { + cNameLong, err := nametransform.ReadLongNameAt(fd, cName) + if err != nil { + tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v", + cDirName, cName, err) + rn.reportMitigatedCorruption(cName) + continue + } + cName = cNameLong + } else if isLong == nametransform.LongNameFilename { + // ignore "gocryptfs.longname.*.name" + continue + } + name, err := rn.nameTransform.DecryptName(cName, cachedIV) + if err != nil { + tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v", + cDirName, cName, err) + rn.reportMitigatedCorruption(cName) + continue + } + // Override the ciphertext name with the plaintext name but reuse the rest + // of the structure + cipherEntries[i].Name = name + plain = append(plain, cipherEntries[i]) + } + + return fs.NewListDirStream(plain), 0 +} diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go new file mode 100644 index 0000000..94fd021 --- /dev/null +++ b/internal/fusefrontend/root_node.go @@ -0,0 +1,24 @@ +package fusefrontend + +import ( + "time" + + "github.com/rfjakob/gocryptfs/internal/tlog" +) + +// reportMitigatedCorruption is used to report a corruption that was transparently +// mitigated and did not return an error to the user. Pass the name of the corrupt +// item (filename for OpenDir(), xattr name for ListXAttr() etc). +// See the MitigatedCorruptions channel for more info. +func (rn *RootNode) reportMitigatedCorruption(item string) { + if rn.MitigatedCorruptions == nil { + return + } + select { + case rn.MitigatedCorruptions <- item: + case <-time.After(1 * time.Second): + tlog.Warn.Printf("BUG: reportCorruptItem: timeout") + //debug.PrintStack() + return + } +}