aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2020-06-21 12:42:18 +0200
committerJakob Unterwurzacher2020-06-21 12:42:18 +0200
commitebdf58b9ebf366ac6dbf500c0f4f4d211451bb44 (patch)
treed30c80ae667d844ce0081820b9b07e48940d4cf0
parent77632b7554000f2e832a0e9fded1a3894b2502c3 (diff)
v2api: implement GetAttr and Readdir
-rw-r--r--internal/fusefrontend/node.go27
-rw-r--r--internal/fusefrontend/node_dir_ops.go97
-rw-r--r--internal/fusefrontend/root_node.go24
3 files changed, 144 insertions, 4 deletions
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
+ }
+}