Skip to content

Commit e04dc05

Browse files
committed
fusefrontend: upgrade wlockMap to use device AND inode number
If there are multiple filesystems backing the gocryptfs filesystems inode numbers are not guaranteed to be unique.
1 parent 081015a commit e04dc05

File tree

5 files changed

+63
-51
lines changed

5 files changed

+63
-51
lines changed

internal/fusefrontend/file.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ type file struct {
3838
writeOnly bool
3939
// Content encryption helper
4040
contentEnc *contentenc.ContentEnc
41-
// Inode number
42-
ino uint64
41+
// Device and inode number uniquely identify the backing file
42+
devIno DevInoStruct
4343
// File header
4444
header *contentenc.FileHeader
4545
// go-fuse nodefs.loopbackFile
@@ -59,13 +59,14 @@ func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) (no
5959
tlog.Warn.Printf("NewFile: Fstat on fd %d failed: %v\n", fd.Fd(), err)
6060
return nil, fuse.ToStatus(err)
6161
}
62-
wlock.register(st.Ino)
62+
di := DevInoFromStat(&st)
63+
wlock.register(di)
6364

6465
return &file{
6566
fd: fd,
6667
writeOnly: writeOnly,
6768
contentEnc: contentEnc,
68-
ino: st.Ino,
69+
devIno: di,
6970
loopbackFile: nodefs.NewLoopbackFile(fd),
7071
}, fuse.OK
7172
}
@@ -109,7 +110,7 @@ func (f *file) createHeader() error {
109110
// Prevent partially written (=corrupt) header by preallocating the space beforehand
110111
err := syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
111112
if err != nil {
112-
tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.ino, err.Error())
113+
tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.devIno.ino, err.Error())
113114
return err
114115
}
115116

@@ -169,7 +170,7 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
169170
plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, f.header.ID)
170171
if err != nil {
171172
curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
172-
tlog.Warn.Printf("ino%d: doRead: corrupt block #%d: %v", f.ino, curruptBlockNo, err)
173+
tlog.Warn.Printf("ino%d: doRead: corrupt block #%d: %v", f.devIno.ino, curruptBlockNo, err)
173174
return nil, fuse.EIO
174175
}
175176

@@ -192,23 +193,23 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
192193
f.fdLock.RLock()
193194
defer f.fdLock.RUnlock()
194195

195-
tlog.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.ino, len(buf), off)
196+
tlog.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d", f.devIno.ino, len(buf), off)
196197

197198
if f.writeOnly {
198-
tlog.Warn.Printf("ino%d: Tried to read from write-only file", f.ino)
199+
tlog.Warn.Printf("ino%d: Tried to read from write-only file", f.devIno.ino)
199200
return nil, fuse.EBADF
200201
}
201202

202203
out, status := f.doRead(uint64(off), uint64(len(buf)))
203204

204205
if status == fuse.EIO {
205-
tlog.Warn.Printf("ino%d: Read: returning EIO, offset=%d, length=%d", f.ino, len(buf), off)
206+
tlog.Warn.Printf("ino%d: Read: returning EIO, offset=%d, length=%d", f.devIno.ino, len(buf), off)
206207
}
207208
if status != fuse.OK {
208209
return nil, status
209210
}
210211

211-
tlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.ino, status, len(out))
212+
tlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.devIno.ino, status, len(out))
212213
return fuse.ReadResultData(out), status
213214
}
214215

@@ -250,7 +251,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
250251
var oldData []byte
251252
oldData, status = f.doRead(o, f.contentEnc.PlainBS())
252253
if status != fuse.OK {
253-
tlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.ino, f.intFd(), status.String())
254+
tlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.devIno.ino, f.intFd(), status.String())
254255
return written, status
255256
}
256257
// Modify
@@ -262,12 +263,12 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
262263
blockOffset := b.BlockCipherOff()
263264
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.ID)
264265
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
265-
f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
266+
f.devIno.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
266267

267268
// Prevent partially written (=corrupt) blocks by preallocating the space beforehand
268269
err := syscallcompat.EnospcPrealloc(int(f.fd.Fd()), int64(blockOffset), int64(len(blockData)))
269270
if err != nil {
270-
tlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.ino, f.intFd(), err.Error())
271+
tlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.devIno.ino, f.intFd(), err.Error())
271272
status = fuse.ToStatus(err)
272273
break
273274
}
@@ -289,7 +290,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
289290
// directly (in time and space) follows the last write.
290291
// This is an optimisation for streaming writes on NFS where a
291292
// Stat() call is very expensive.
292-
// The caller must "wlock.lock(f.ino)" otherwise this check would be racy.
293+
// The caller must "wlock.lock(f.devIno.ino)" otherwise this check would be racy.
293294
func (f *file) isConsecutiveWrite(off int64) bool {
294295
opCount := atomic.LoadUint64(&wlock.opCount)
295296
return opCount == f.lastOpCount+1 && off == f.lastWrittenOffset+1
@@ -305,12 +306,12 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
305306
// The file descriptor has been closed concurrently, which also means
306307
// the wlock has been freed. Exit here so we don't crash trying to access
307308
// it.
308-
tlog.Warn.Printf("ino%d fh%d: Write on released file", f.ino, f.intFd())
309+
tlog.Warn.Printf("ino%d fh%d: Write on released file", f.devIno.ino, f.intFd())
309310
return 0, fuse.EBADF
310311
}
311-
wlock.lock(f.ino)
312-
defer wlock.unlock(f.ino)
313-
tlog.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data))
312+
wlock.lock(f.devIno)
313+
defer wlock.unlock(f.devIno)
314+
tlog.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.devIno.ino, off, len(data))
314315
// If the write creates a file hole, we have to zero-pad the last block.
315316
// But if the write directly follows an earlier write, it cannot create a
316317
// hole, and we can save one Stat() call.
@@ -332,13 +333,13 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
332333
func (f *file) Release() {
333334
f.fdLock.Lock()
334335
if f.released {
335-
log.Panicf("ino%d fh%d: double release", f.ino, f.intFd())
336+
log.Panicf("ino%d fh%d: double release", f.devIno.ino, f.intFd())
336337
}
337338
f.fd.Close()
338339
f.released = true
339340
f.fdLock.Unlock()
340341

341-
wlock.unregister(f.ino)
342+
wlock.unregister(f.devIno)
342343
}
343344

344345
// Flush - FUSE call

internal/fusefrontend/file_allocate_truncate.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
5050
if f.released {
5151
return fuse.EBADF
5252
}
53-
wlock.lock(f.ino)
54-
defer wlock.unlock(f.ino)
53+
wlock.lock(f.devIno)
54+
defer wlock.unlock(f.devIno)
5555

5656
blocks := f.contentEnc.ExplodePlainRange(off, sz)
5757
firstBlock := blocks[0]
@@ -97,17 +97,17 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
9797
defer f.fdLock.RUnlock()
9898
if f.released {
9999
// The file descriptor has been closed concurrently.
100-
tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.ino, f.intFd())
100+
tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.devIno.ino, f.intFd())
101101
return fuse.EBADF
102102
}
103-
wlock.lock(f.ino)
104-
defer wlock.unlock(f.ino)
103+
wlock.lock(f.devIno)
104+
defer wlock.unlock(f.devIno)
105105
var err error
106106
// Common case first: Truncate to zero
107107
if newSize == 0 {
108108
err = syscall.Ftruncate(int(f.fd.Fd()), 0)
109109
if err != nil {
110-
tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.ino, f.intFd(), err)
110+
tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.devIno.ino, f.intFd(), err)
111111
return fuse.ToStatus(err)
112112
}
113113
// Truncate to zero kills the file header
@@ -123,7 +123,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
123123

124124
oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
125125
newB := float32(newSize) / float32(f.contentEnc.PlainBS())
126-
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
126+
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.devIno.ino, oldB, newB, oldSize, newSize)
127127

128128
// File size stays the same - nothing to do
129129
if newSize == oldSize {
@@ -166,7 +166,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
166166
func (f *file) statPlainSize() (uint64, error) {
167167
fi, err := f.fd.Stat()
168168
if err != nil {
169-
tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.ino, f.intFd(), err)
169+
tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.devIno.ino, f.intFd(), err)
170170
return 0, err
171171
}
172172
cipherSz := uint64(fi.Size())

internal/fusefrontend/write_lock.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@ package fusefrontend
33
import (
44
"sync"
55
"sync/atomic"
6+
"syscall"
67
)
78

9+
// DevInoStruct uniquely identifies a backing file through device number and
10+
// inode number.
11+
type DevInoStruct struct {
12+
dev uint64
13+
ino uint64
14+
}
15+
16+
func DevInoFromStat(st *syscall.Stat_t) DevInoStruct {
17+
// Explicit cast to uint64 to prevent build problems on 32-bit platforms
18+
return DevInoStruct{
19+
dev: uint64(st.Dev),
20+
ino: uint64(st.Ino),
21+
}
22+
}
23+
824
func init() {
9-
wlock.inodeLocks = make(map[uint64]*refCntMutex)
25+
wlock.inodeLocks = make(map[DevInoStruct]*refCntMutex)
1026
}
1127

1228
// wlock - serializes write accesses to each file (identified by inode number)
@@ -29,7 +45,7 @@ type wlockMap struct {
2945
opCount uint64
3046
// Protects map access
3147
sync.Mutex
32-
inodeLocks map[uint64]*refCntMutex
48+
inodeLocks map[DevInoStruct]*refCntMutex
3349
}
3450

3551
// refCntMutex - mutex with reference count
@@ -42,45 +58,45 @@ type refCntMutex struct {
4258

4359
// register creates an entry for "ino", or incrementes the reference count
4460
// if the entry already exists.
45-
func (w *wlockMap) register(ino uint64) {
61+
func (w *wlockMap) register(di DevInoStruct) {
4662
w.Lock()
4763
defer w.Unlock()
4864

49-
r := w.inodeLocks[ino]
65+
r := w.inodeLocks[di]
5066
if r == nil {
5167
r = &refCntMutex{}
52-
w.inodeLocks[ino] = r
68+
w.inodeLocks[di] = r
5369
}
5470
r.refCnt++
5571
}
5672

57-
// unregister decrements the reference count for "ino" and deletes the entry if
73+
// unregister decrements the reference count for "di" and deletes the entry if
5874
// the reference count has reached 0.
59-
func (w *wlockMap) unregister(ino uint64) {
75+
func (w *wlockMap) unregister(di DevInoStruct) {
6076
w.Lock()
6177
defer w.Unlock()
6278

63-
r := w.inodeLocks[ino]
79+
r := w.inodeLocks[di]
6480
r.refCnt--
6581
if r.refCnt == 0 {
66-
delete(w.inodeLocks, ino)
82+
delete(w.inodeLocks, di)
6783
}
6884
}
6985

70-
// lock retrieves the entry for "ino" and locks it.
71-
func (w *wlockMap) lock(ino uint64) {
86+
// lock retrieves the entry for "di" and locks it.
87+
func (w *wlockMap) lock(di DevInoStruct) {
7288
atomic.AddUint64(&w.opCount, 1)
7389
w.Lock()
74-
r := w.inodeLocks[ino]
90+
r := w.inodeLocks[di]
7591
w.Unlock()
7692
// this can take a long time - execute outside the wlockMap lock
7793
r.Lock()
7894
}
7995

80-
// unlock retrieves the entry for "ino" and unlocks it.
81-
func (w *wlockMap) unlock(ino uint64) {
96+
// unlock retrieves the entry for "di" and unlocks it.
97+
func (w *wlockMap) unlock(di DevInoStruct) {
8298
w.Lock()
83-
r := w.inodeLocks[ino]
99+
r := w.inodeLocks[di]
84100
w.Unlock()
85101
r.Unlock()
86102
}

internal/fusefrontend_reverse/ino_map.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,3 @@ type inoGenT struct {
1717
func (i *inoGenT) next() uint64 {
1818
return atomic.AddUint64(i.ino, 1)
1919
}
20-
21-
type devIno struct {
22-
dev uint64
23-
ino uint64
24-
}

internal/fusefrontend_reverse/rfs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type ReverseFS struct {
4141
// Inode number generator
4242
inoGen *inoGenT
4343
// Maps backing files device+inode pairs to user-facing unique inode numbers
44-
inoMap map[devIno]uint64
44+
inoMap map[fusefrontend.DevInoStruct]uint64
4545
// Protects map access
4646
inoMapLock sync.Mutex
4747
}
@@ -68,7 +68,7 @@ func NewFS(args fusefrontend.Args) *ReverseFS {
6868
nameTransform: nameTransform,
6969
contentEnc: contentEnc,
7070
inoGen: newInoGen(),
71-
inoMap: map[devIno]uint64{},
71+
inoMap: map[fusefrontend.DevInoStruct]uint64{},
7272
}
7373
}
7474

@@ -167,7 +167,7 @@ func (rfs *ReverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status
167167
// The file has hard links. We have to give it a stable inode number so
168168
// tar or rsync can find them.
169169
if fi.Mode().IsRegular() && st.Nlink > 1 {
170-
di := devIno{uint64(st.Dev), st.Ino}
170+
di := fusefrontend.DevInoFromStat(st)
171171
rfs.inoMapLock.Lock()
172172
stableIno := rfs.inoMap[di]
173173
if stableIno == 0 {

0 commit comments

Comments
 (0)