1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
package fusefrontend_reverse
import (
"context"
"fmt"
"syscall"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
// 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) {
rn := n.rootNode()
// 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})
}
// This directory is a mountpoint. Present it as empty.
if rn.args.OneFileSystem && n.isOtherFilesystem {
return fs.NewListDirStream(virtualFiles), 0
}
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
// Read plaintext directory
var entries []fuse.DirEntry
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
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)
}
// Filter out excluded entries
entries = rn.excludeDirEntries(d, entries)
if rn.args.PlaintextNames {
return n.readdirPlaintextnames(entries)
}
dirIV := rn.deriveDirIV(d.cPath)
// 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 {
cName, err = rn.nameTransform.EncryptName(entries[i].Name, dirIV)
if err != nil {
entries[i].Name = "___GOCRYPTFS_INVALID_NAME___"
continue
}
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
}
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
}
|