diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index 8af7bed..0b2df12 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -9,6 +9,8 @@ import ( "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/pathiv" "github.com/rfjakob/gocryptfs/internal/syscallcompat" ) @@ -19,13 +21,56 @@ type Node struct { } // Lookup - FUSE call for discovering a file. -// TODO handle virtual files func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { - dirfd, pName, errno := n.prepareAtSyscall(name) - if errno != 0 { + dirfd := int(-1) + pName := "" + t := n.lookupFileType(name) + // gocryptfs.conf + if t == typeConfig { + var err error + rn := n.rootNode() + dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "") + if err != nil { + errno = fs.ToErrno(err) + return + } + defer syscall.Close(dirfd) + pName = configfile.ConfReverseName + } else if t == typeDiriv { + // gocryptfs.diriv + dirfd, pName, errno = n.prepareAtSyscall("") + if errno != 0 { + return + } + defer syscall.Close(dirfd) + st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + errno = fs.ToErrno(err) + return + } + content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) + var vf *virtualFile + vf, errno = n.newVirtualFile(content, st, inoTagDirIV) + if errno != 0 { + return nil, errno + } + out.Attr = vf.attr + // Create child node + id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino} + ch = n.NewInode(ctx, vf, id) return + } else if t == typeName { + // gocryptfs.longname.*.name + + // TODO + } else if t == typeReal { + // real file + dirfd, pName, errno = n.prepareAtSyscall(name) + if errno != 0 { + return + } + defer syscall.Close(dirfd) } - defer syscall.Close(dirfd) // Get device number and inode number into `st` st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW) @@ -36,8 +81,10 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch // Create new inode and fill `out` ch = n.newChild(ctx, st, out) - // Translate ciphertext size in `out.Attr.Size` to plaintext size - n.translateSize(dirfd, pName, &out.Attr) + if t == typeReal { + // Translate ciphertext size in `out.Attr.Size` to plaintext size + n.translateSize(dirfd, pName, &out.Attr) + } return ch, 0 } diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualfile.go index a92c127..a7f6913 100644 --- a/internal/fusefrontend_reverse/virtualfile.go +++ b/internal/fusefrontend_reverse/virtualfile.go @@ -1,7 +1,16 @@ package fusefrontend_reverse import ( + "context" + "log" "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/inomap" + "github.com/rfjakob/gocryptfs/internal/nametransform" ) const ( @@ -13,3 +22,94 @@ const ( inoTagDirIV = 1 inoTagNameFile = 2 ) + +type fileType int + +// Values returned by lookupFileType +const ( + // A real file/directory/symlink in the backing plaintext directory + typeReal fileType = iota + // A DirIV (gocryptfs.diriv) file + typeDiriv + // A gocryptfs.longname.*.name file for a file with a long name + typeName + // The config file gocryptfs.conf + typeConfig +) + +// lookupFileType returns the type of child file name +// (one of the fileType constants above). Called from Lookup(). +func (n *Node) lookupFileType(cName string) fileType { + rn := n.rootNode() + // In -plaintextname mode, neither diriv nor longname files exist. + if !rn.args.PlaintextNames { + // Is it a gocryptfs.diriv file? + if cName == nametransform.DirIVFilename { + return typeDiriv + } + // Is it a gocryptfs.longname.*.name file? + if t := nametransform.NameType(cName); t == nametransform.LongNameFilename { + return typeName + } + } + // gocryptfs.conf in the root directory. This is passed through to + // .gocryptfs.reverse.conf in the backing plaintext directory. + if n.isRoot() && !rn.args.ConfigCustom && cName == configfile.ConfDefaultName { + return typeConfig + } + return typeReal +} + +type virtualFile struct { + fs.Inode + + // file content + content []byte + // attributes for Getattr() + attr fuse.Attr +} + +// newVirtualFile creates a new in-memory file that does not have a representation +// on disk. "content" is the file content. Timestamps and file owner are copied +// from "parentFile" (file descriptor). +// For a "gocryptfs.diriv" file, you would use the parent directory as +// "parentFile". +func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *virtualFile, errno syscall.Errno) { + if inoTag == 0 { + log.Panicf("BUG: inoTag for virtual file is zero - this will cause ino collisions!") + } + + // Adjust inode number and size + rn := n.rootNode() + st := parentStat + q := inomap.NewQIno(uint64(st.Dev), inoTag, uint64(st.Ino)) + st.Ino = rn.inoMap.Translate(q) + st.Size = int64(len(content)) + st.Mode = virtualFileMode + st.Nlink = 1 + var a fuse.Attr + a.FromStat(st) + + vf = &virtualFile{content: content, attr: a} + return +} + +// Open - FUSE call +func (f *virtualFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { + return nil, fuse.FOPEN_KEEP_CACHE, 0 +} + +// GetAttr - FUSE call +func (f *virtualFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { + out.Attr = f.attr + return 0 +} + +// Read - FUSE call +func (f *virtualFile) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { + end := int(off) + len(dest) + if end > len(f.content) { + end = len(f.content) + } + return fuse.ReadResultData(f.content[off:end]), 0 +}