2020-08-01 22:28:25 +02:00
|
|
|
package fusefrontend_reverse
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-15 15:05:25 +02:00
|
|
|
"fmt"
|
2020-08-01 22:28:25 +02:00
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
|
|
|
"github.com/hanwen/go-fuse/v2/fs"
|
|
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
|
|
|
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
2020-08-15 15:05:25 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
2020-08-01 22:28:25 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
2020-08-15 15:05:25 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
2020-08-01 22:28:25 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Readdir - FUSE call.
|
|
|
|
//
|
|
|
|
// This function is symlink-safe through use of openBackingDir() and
|
|
|
|
// ReadDirIVAt().
|
|
|
|
func (n *Node) Readdir(ctx context.Context) (stream fs.DirStream, errno syscall.Errno) {
|
2021-08-16 18:40:48 +02:00
|
|
|
rn := n.rootNode()
|
2021-08-20 17:06:18 +02:00
|
|
|
// Should we present a virtual gocryptfs.diriv?
|
|
|
|
var virtualFiles []fuse.DirEntry
|
|
|
|
if !rn.args.PlaintextNames && !rn.args.DeterministicNames {
|
|
|
|
virtualFiles = append(virtualFiles, fuse.DirEntry{Mode: virtualFileMode, Name: nametransform.DirIVFilename})
|
|
|
|
}
|
2021-08-16 18:40:48 +02:00
|
|
|
|
|
|
|
// This directory is a mountpoint. Present it as empty.
|
|
|
|
if rn.args.OneFileSystem && n.isOtherFilesystem {
|
2021-08-20 17:06:18 +02:00
|
|
|
return fs.NewListDirStream(virtualFiles), 0
|
2021-08-16 18:40:48 +02:00
|
|
|
}
|
|
|
|
|
2020-08-15 17:31:25 +02:00
|
|
|
d, errno := n.prepareAtSyscall("")
|
2020-08-01 22:28:25 +02:00
|
|
|
if errno != 0 {
|
|
|
|
return
|
|
|
|
}
|
2020-08-15 17:31:25 +02:00
|
|
|
defer syscall.Close(d.dirfd)
|
2020-08-01 22:28:25 +02:00
|
|
|
|
|
|
|
// Read plaintext directory
|
|
|
|
var entries []fuse.DirEntry
|
2020-08-15 17:31:25 +02:00
|
|
|
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
2020-08-01 22:28:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fs.ToErrno(err)
|
|
|
|
}
|
|
|
|
defer syscall.Close(fd)
|
|
|
|
entries, err = syscallcompat.Getdents(fd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fs.ToErrno(err)
|
|
|
|
}
|
|
|
|
|
2020-08-15 17:31:25 +02:00
|
|
|
// Filter out excluded entries
|
|
|
|
entries = rn.excludeDirEntries(d, entries)
|
|
|
|
|
2020-08-01 22:28:25 +02:00
|
|
|
if rn.args.PlaintextNames {
|
2020-08-15 15:05:25 +02:00
|
|
|
return n.readdirPlaintextnames(entries)
|
2020-08-01 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2021-08-20 17:06:18 +02:00
|
|
|
dirIV := rn.deriveDirIV(d.cPath)
|
2020-08-01 22:28:25 +02:00
|
|
|
// Encrypt names
|
|
|
|
for i := range entries {
|
|
|
|
var cName string
|
|
|
|
// ".gocryptfs.reverse.conf" in the root directory is mapped to "gocryptfs.conf"
|
|
|
|
if n.isRoot() && entries[i].Name == configfile.ConfReverseName &&
|
|
|
|
!rn.args.ConfigCustom {
|
|
|
|
cName = configfile.ConfDefaultName
|
|
|
|
} else {
|
2021-06-02 14:21:30 +02:00
|
|
|
cName, err = rn.nameTransform.EncryptName(entries[i].Name, dirIV)
|
|
|
|
if err != nil {
|
|
|
|
entries[i].Name = "___GOCRYPTFS_INVALID_NAME___"
|
|
|
|
continue
|
|
|
|
}
|
2020-08-01 22:28:25 +02:00
|
|
|
if len(cName) > unix.NAME_MAX {
|
|
|
|
cName = rn.nameTransform.HashLongName(cName)
|
|
|
|
dotNameFile := fuse.DirEntry{
|
|
|
|
Mode: virtualFileMode,
|
|
|
|
Name: cName + nametransform.LongNameSuffix,
|
|
|
|
}
|
|
|
|
virtualFiles = append(virtualFiles, dotNameFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entries[i].Name = cName
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add virtual files
|
|
|
|
entries = append(entries, virtualFiles...)
|
|
|
|
return fs.NewListDirStream(entries), 0
|
|
|
|
}
|
2020-08-15 15:05:25 +02:00
|
|
|
|
|
|
|
func (n *Node) readdirPlaintextnames(entries []fuse.DirEntry) (stream fs.DirStream, errno syscall.Errno) {
|
|
|
|
rn := n.rootNode()
|
|
|
|
// If we are not the root dir or a custom config path was used, we don't
|
|
|
|
// need to map anything
|
|
|
|
if !n.isRoot() || rn.args.ConfigCustom {
|
|
|
|
return fs.NewListDirStream(entries), 0
|
|
|
|
}
|
|
|
|
// We are in the root dir and the default config file name
|
|
|
|
// ".gocryptfs.reverse.conf" is used. We map it to "gocryptfs.conf".
|
|
|
|
dupe := -1
|
|
|
|
for i := range entries {
|
|
|
|
if entries[i].Name == configfile.ConfReverseName {
|
|
|
|
entries[i].Name = configfile.ConfDefaultName
|
|
|
|
} else if entries[i].Name == configfile.ConfDefaultName {
|
|
|
|
dupe = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dupe >= 0 {
|
|
|
|
// Warn the user loudly: The gocryptfs.conf_NAME_COLLISION file will
|
|
|
|
// throw ENOENT errors that are hard to miss.
|
|
|
|
tlog.Warn.Printf("The file %q is mapped to %q and shadows another file. Please rename %q in directory %q.",
|
|
|
|
configfile.ConfReverseName, configfile.ConfDefaultName, configfile.ConfDefaultName, rn.args.Cipherdir)
|
|
|
|
entries[dupe].Name = "gocryptfs.conf_NAME_COLLISION_" + fmt.Sprintf("%d", cryptocore.RandUint64())
|
|
|
|
}
|
|
|
|
return fs.NewListDirStream(entries), 0
|
|
|
|
}
|