Skip to content

Commit ae3c859

Browse files
committed
fusefrontend: switch to new go-fuse dir api
go-fuse 2.6.0, specifically, hanwen/go-fuse@e885cea introduced a new, file-based directory API while deprecating the old one. Switch to the new API. xfstests generic/035 now passes. Fixes hanwen/go-fuse#55
1 parent 7309412 commit ae3c859

File tree

4 files changed

+179
-87
lines changed

4 files changed

+179
-87
lines changed

internal/fusefrontend/file.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type File struct {
5050
lastOpCount uint64
5151
// Parent filesystem
5252
rootNode *RootNode
53+
// If this open file is a directory, dirHandle will be set, otherwise it's nil.
54+
dirHandle *DirHandle
5355
}
5456

5557
// NewFile returns a new go-fuse File instance based on an already-open file
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package fusefrontend
2+
3+
import (
4+
"context"
5+
"syscall"
6+
7+
"github.com/hanwen/go-fuse/v2/fs"
8+
"github.com/hanwen/go-fuse/v2/fuse"
9+
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
10+
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
11+
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
12+
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
13+
)
14+
15+
func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
16+
var fd int = -1
17+
var fdDup int = -1
18+
var file *File
19+
var dirIV []byte
20+
var ds fs.DirStream
21+
rn := n.rootNode()
22+
23+
dirfd, cName, errno := n.prepareAtSyscallMyself()
24+
if errno != 0 {
25+
return
26+
}
27+
defer syscall.Close(dirfd)
28+
29+
// Open backing directory
30+
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
31+
if err != nil {
32+
errno = fs.ToErrno(err)
33+
return
34+
}
35+
36+
// NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir
37+
fdDup, err = syscall.Dup(fd)
38+
39+
if err != nil {
40+
errno = fs.ToErrno(err)
41+
goto err_out
42+
}
43+
44+
ds, errno = fs.NewLoopbackDirStreamFd(fdDup)
45+
if errno != 0 {
46+
goto err_out
47+
}
48+
49+
if !rn.args.PlaintextNames {
50+
// Read the DirIV from disk
51+
dirIV, err = rn.nameTransform.ReadDirIVAt(fd)
52+
if err != nil {
53+
tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err)
54+
errno = syscall.EIO
55+
goto err_out
56+
}
57+
}
58+
59+
file, _, errno = NewFile(fd, cName, rn)
60+
if errno != 0 {
61+
goto err_out
62+
}
63+
64+
file.dirHandle = &DirHandle{
65+
ds: ds,
66+
dirIV: dirIV,
67+
isRootDir: n.IsRoot(),
68+
}
69+
70+
return file, fuseFlags, errno
71+
72+
err_out:
73+
if fd >= 0 {
74+
syscall.Close(fd)
75+
}
76+
if fdDup >= 0 {
77+
syscall.Close(fdDup)
78+
}
79+
if errno == 0 {
80+
tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0")
81+
errno = syscall.EIO
82+
}
83+
return nil, 0, errno
84+
}
85+
86+
type DirHandle struct {
87+
// Content of gocryptfs.diriv. nil if plaintextnames is used.
88+
dirIV []byte
89+
90+
isRootDir bool
91+
92+
// fs.loopbackDirStream with a private dup of the file descriptor
93+
ds fs.FileHandle
94+
}
95+
96+
var _ = (fs.FileReleasedirer)((*File)(nil))
97+
98+
func (f *File) Releasedir(ctx context.Context, flags uint32) {
99+
// Does its own locking
100+
f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags)
101+
// Does its own locking
102+
f.Release(ctx)
103+
}
104+
105+
var _ = (fs.FileSeekdirer)((*File)(nil))
106+
107+
func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno {
108+
return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off)
109+
}
110+
111+
var _ = (fs.FileFsyncdirer)((*File)(nil))
112+
113+
func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
114+
return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags)
115+
}
116+
117+
var _ = (fs.FileReaddirenter)((*File)(nil))
118+
119+
// This function is symlink-safe through use of openBackingDir() and
120+
// ReadDirIVAt().
121+
func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) {
122+
f.fdLock.RLock()
123+
defer f.fdLock.RUnlock()
124+
125+
for {
126+
entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx)
127+
if errno != 0 || entry == nil {
128+
return
129+
}
130+
131+
cName := entry.Name
132+
if cName == "." || cName == ".." {
133+
// We want these as-is
134+
return
135+
}
136+
if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName {
137+
// silently ignore "gocryptfs.conf" in the top level dir
138+
continue
139+
}
140+
if f.rootNode.args.PlaintextNames {
141+
return
142+
}
143+
if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename {
144+
// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
145+
continue
146+
}
147+
// Handle long file name
148+
isLong := nametransform.LongNameNone
149+
if f.rootNode.args.LongNames {
150+
isLong = nametransform.NameType(cName)
151+
}
152+
if isLong == nametransform.LongNameContent {
153+
cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName)
154+
if err != nil {
155+
tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v",
156+
cName, err)
157+
f.rootNode.reportMitigatedCorruption(cName)
158+
continue
159+
}
160+
cName = cNameLong
161+
} else if isLong == nametransform.LongNameFilename {
162+
// ignore "gocryptfs.longname.*.name"
163+
continue
164+
}
165+
name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV)
166+
if err != nil {
167+
tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v",
168+
cName, err)
169+
f.rootNode.reportMitigatedCorruption(cName)
170+
continue
171+
}
172+
// Override the ciphertext name with the plaintext name but reuse the rest
173+
// of the structure
174+
entry.Name = name
175+
return
176+
}
177+
}

internal/fusefrontend/node_api_check.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
// Check that we have implemented the fs.Node* interfaces
88
var _ = (fs.NodeGetattrer)((*Node)(nil))
99
var _ = (fs.NodeLookuper)((*Node)(nil))
10-
var _ = (fs.NodeReaddirer)((*Node)(nil))
1110
var _ = (fs.NodeCreater)((*Node)(nil))
1211
var _ = (fs.NodeMkdirer)((*Node)(nil))
1312
var _ = (fs.NodeRmdirer)((*Node)(nil))

internal/fusefrontend/node_dir_ops.go

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/hanwen/go-fuse/v2/fs"
1313
"github.com/hanwen/go-fuse/v2/fuse"
1414

15-
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
1615
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
1716
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
1817
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
@@ -159,91 +158,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
159158
return ch, 0
160159
}
161160

162-
// Readdir - FUSE call.
163-
//
164-
// This function is symlink-safe through use of openBackingDir() and
165-
// ReadDirIVAt().
166-
func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
167-
parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
168-
if errno != 0 {
169-
return nil, errno
170-
}
171-
defer syscall.Close(parentDirFd)
172-
173-
// Read ciphertext directory
174-
fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
175-
if err != nil {
176-
return nil, fs.ToErrno(err)
177-
}
178-
defer syscall.Close(fd)
179-
cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd)
180-
if err != nil {
181-
return nil, fs.ToErrno(err)
182-
}
183-
// Get DirIV (stays nil if PlaintextNames is used)
184-
var cachedIV []byte
185-
rn := n.rootNode()
186-
if !rn.args.PlaintextNames {
187-
// Read the DirIV from disk
188-
cachedIV, err = rn.nameTransform.ReadDirIVAt(fd)
189-
if err != nil {
190-
tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)
191-
return nil, syscall.EIO
192-
}
193-
}
194-
// Decrypted directory entries
195-
var plain []fuse.DirEntry
196-
// Add "." and ".."
197-
plain = append(plain, specialEntries...)
198-
// Filter and decrypt filenames
199-
for i := range cipherEntries {
200-
cName := cipherEntries[i].Name
201-
if n.IsRoot() && cName == configfile.ConfDefaultName {
202-
// silently ignore "gocryptfs.conf" in the top level dir
203-
continue
204-
}
205-
if rn.args.PlaintextNames {
206-
plain = append(plain, cipherEntries[i])
207-
continue
208-
}
209-
if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename {
210-
// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
211-
continue
212-
}
213-
// Handle long file name
214-
isLong := nametransform.LongNameNone
215-
if rn.args.LongNames {
216-
isLong = nametransform.NameType(cName)
217-
}
218-
if isLong == nametransform.LongNameContent {
219-
cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
220-
if err != nil {
221-
tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
222-
cDirName, cName, err)
223-
rn.reportMitigatedCorruption(cName)
224-
continue
225-
}
226-
cName = cNameLong
227-
} else if isLong == nametransform.LongNameFilename {
228-
// ignore "gocryptfs.longname.*.name"
229-
continue
230-
}
231-
name, err := rn.nameTransform.DecryptName(cName, cachedIV)
232-
if err != nil {
233-
tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
234-
cDirName, cName, err)
235-
rn.reportMitigatedCorruption(cName)
236-
continue
237-
}
238-
// Override the ciphertext name with the plaintext name but reuse the rest
239-
// of the structure
240-
cipherEntries[i].Name = name
241-
plain = append(plain, cipherEntries[i])
242-
}
243-
244-
return fs.NewListDirStream(plain), 0
245-
}
246-
247161
// Rmdir - FUSE call.
248162
//
249163
// Symlink-safe through Unlinkat() + AT_REMOVEDIR.

0 commit comments

Comments
 (0)