Skip to content

Commit 0489d08

Browse files
committed
fusefrontend: get the file ID from the open files table
This fixes the problem that a truncate can reset the file ID without the other open FDs noticing it.
1 parent e04dc05 commit 0489d08

File tree

3 files changed

+106
-84
lines changed

3 files changed

+106
-84
lines changed

internal/fusefrontend/file.go

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ type file struct {
4040
contentEnc *contentenc.ContentEnc
4141
// Device and inode number uniquely identify the backing file
4242
devIno DevInoStruct
43-
// File header
44-
header *contentenc.FileHeader
43+
// Entry in the open file map
44+
fileTableEntry *openFileEntryT
4545
// go-fuse nodefs.loopbackFile
4646
loopbackFile nodefs.File
47-
// Store what the last byte was written
47+
// Store where the last byte was written
4848
lastWrittenOffset int64
4949
// The opCount is used to judge whether "lastWrittenOffset" is still
5050
// guaranteed to be correct.
@@ -60,14 +60,15 @@ func NewFile(fd *os.File, writeOnly bool, contentEnc *contentenc.ContentEnc) (no
6060
return nil, fuse.ToStatus(err)
6161
}
6262
di := DevInoFromStat(&st)
63-
wlock.register(di)
63+
t := openFileMap.register(di)
6464

6565
return &file{
66-
fd: fd,
67-
writeOnly: writeOnly,
68-
contentEnc: contentEnc,
69-
devIno: di,
70-
loopbackFile: nodefs.NewLoopbackFile(fd),
66+
fd: fd,
67+
writeOnly: writeOnly,
68+
contentEnc: contentEnc,
69+
devIno: di,
70+
fileTableEntry: t,
71+
loopbackFile: nodefs.NewLoopbackFile(fd),
7172
}, fuse.OK
7273
}
7374

@@ -84,44 +85,39 @@ func (f *file) InnerFile() nodefs.File {
8485
func (f *file) SetInode(n *nodefs.Inode) {
8586
}
8687

87-
// readHeader - load the file header from disk
88-
//
89-
// Returns io.EOF if the file is empty
90-
func (f *file) readHeader() error {
88+
// readFileID loads the file header from disk and extracts the file ID.
89+
// Returns io.EOF if the file is empty.
90+
func (f *file) readFileID() ([]byte, error) {
9191
buf := make([]byte, contentenc.HeaderLen)
9292
_, err := f.fd.ReadAt(buf, 0)
9393
if err != nil {
94-
return err
94+
return nil, err
9595
}
9696
h, err := contentenc.ParseHeader(buf)
9797
if err != nil {
98-
return err
98+
return nil, err
9999
}
100-
f.header = h
101-
102-
return nil
100+
return h.ID, nil
103101
}
104102

105-
// createHeader - create a new random header and write it to disk
106-
func (f *file) createHeader() error {
103+
// createHeader creates a new random header and writes it to disk.
104+
// Returns the new file ID.
105+
// The caller must hold fileIDLock.Lock().
106+
func (f *file) createHeader() (fileID []byte, err error) {
107107
h := contentenc.RandomHeader()
108108
buf := h.Pack()
109-
110109
// Prevent partially written (=corrupt) header by preallocating the space beforehand
111-
err := syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
110+
err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen)
112111
if err != nil {
113112
tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.devIno.ino, err.Error())
114-
return err
113+
return nil, err
115114
}
116-
117115
// Actually write header
118116
_, err = f.fd.WriteAt(buf, 0)
119117
if err != nil {
120-
return err
118+
return nil, err
121119
}
122-
f.header = h
123-
124-
return nil
120+
return h.ID, err
125121
}
126122

127123
func (f *file) String() string {
@@ -137,25 +133,39 @@ func (f *file) String() string {
137133
// Called by Read() for normal reading,
138134
// by Write() and Truncate() for Read-Modify-Write
139135
func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
140-
141-
// Read file header
142-
if f.header == nil {
143-
err := f.readHeader()
136+
// Make sure we have the file ID.
137+
f.fileTableEntry.IDLock.RLock()
138+
if f.fileTableEntry.ID == nil {
139+
f.fileTableEntry.IDLock.RUnlock()
140+
// Yes, somebody else may take the lock before we can. This will get
141+
// the header read twice, but causes no harm otherwise.
142+
f.fileTableEntry.IDLock.Lock()
143+
tmpID, err := f.readFileID()
144144
if err == io.EOF {
145+
f.fileTableEntry.IDLock.Unlock()
145146
return nil, fuse.OK
146147
}
147148
if err != nil {
149+
f.fileTableEntry.IDLock.Unlock()
148150
return nil, fuse.ToStatus(err)
149151
}
152+
f.fileTableEntry.ID = tmpID
153+
// Downgrade the lock.
154+
f.fileTableEntry.IDLock.Unlock()
155+
// The file ID may change in here. This does no harm because we
156+
// re-read it after the RLock().
157+
f.fileTableEntry.IDLock.RLock()
150158
}
151-
159+
fileID := f.fileTableEntry.ID
152160
// Read the backing ciphertext in one go
153161
blocks := f.contentEnc.ExplodePlainRange(off, length)
154162
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
155163
skip := blocks[0].Skip
156164
tlog.Debug.Printf("JointCiphertextRange(%d, %d) -> %d, %d, %d", off, length, alignedOffset, alignedLength, skip)
157165
ciphertext := make([]byte, int(alignedLength))
158166
n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))
167+
// We don't care if the file ID changes after we have read the data. Drop the lock.
168+
f.fileTableEntry.IDLock.RUnlock()
159169
if err != nil && err != io.EOF {
160170
tlog.Warn.Printf("read: ReadAt: %s", err.Error())
161171
return nil, fuse.ToStatus(err)
@@ -167,7 +177,7 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
167177
tlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n)
168178

169179
// Decrypt it
170-
plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, f.header.ID)
180+
plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
171181
if err != nil {
172182
curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
173183
tlog.Warn.Printf("ino%d: doRead: corrupt block #%d: %v", f.devIno.ino, curruptBlockNo, err)
@@ -223,18 +233,28 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
223233
//
224234
// Empty writes do nothing and are allowed.
225235
func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
226-
227236
// Read header from disk, create a new one if the file is empty
228-
if f.header == nil {
229-
err := f.readHeader()
237+
f.fileTableEntry.IDLock.RLock()
238+
if f.fileTableEntry.ID == nil {
239+
f.fileTableEntry.IDLock.RUnlock()
240+
// Somebody else may write the header here, but this would do no harm.
241+
f.fileTableEntry.IDLock.Lock()
242+
tmpID, err := f.readFileID()
230243
if err == io.EOF {
231-
err = f.createHeader()
232-
244+
tmpID, err = f.createHeader()
233245
}
234246
if err != nil {
247+
f.fileTableEntry.IDLock.Unlock()
235248
return 0, fuse.ToStatus(err)
236249
}
250+
f.fileTableEntry.ID = tmpID
251+
f.fileTableEntry.IDLock.Unlock()
252+
// The file ID may change in here. This does no harm because we
253+
// re-read it after the RLock().
254+
f.fileTableEntry.IDLock.RLock()
237255
}
256+
fileID := f.fileTableEntry.ID
257+
defer f.fileTableEntry.IDLock.RUnlock()
238258

239259
var written uint32
240260
status := fuse.OK
@@ -261,7 +281,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
261281

262282
// Encrypt
263283
blockOffset := b.BlockCipherOff()
264-
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.ID)
284+
blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, fileID)
265285
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
266286
f.devIno.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo)
267287

@@ -292,7 +312,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
292312
// Stat() call is very expensive.
293313
// The caller must "wlock.lock(f.devIno.ino)" otherwise this check would be racy.
294314
func (f *file) isConsecutiveWrite(off int64) bool {
295-
opCount := atomic.LoadUint64(&wlock.opCount)
315+
opCount := atomic.LoadUint64(&openFileMap.opCount)
296316
return opCount == f.lastOpCount+1 && off == f.lastWrittenOffset+1
297317
}
298318

@@ -309,8 +329,8 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
309329
tlog.Warn.Printf("ino%d fh%d: Write on released file", f.devIno.ino, f.intFd())
310330
return 0, fuse.EBADF
311331
}
312-
wlock.lock(f.devIno)
313-
defer wlock.unlock(f.devIno)
332+
f.fileTableEntry.writeLock.Lock()
333+
defer f.fileTableEntry.writeLock.Unlock()
314334
tlog.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.devIno.ino, off, len(data))
315335
// If the write creates a file hole, we have to zero-pad the last block.
316336
// But if the write directly follows an earlier write, it cannot create a
@@ -323,7 +343,7 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
323343
}
324344
n, status := f.doWrite(data, off)
325345
if status.Ok() {
326-
f.lastOpCount = atomic.LoadUint64(&wlock.opCount)
346+
f.lastOpCount = atomic.LoadUint64(&openFileMap.opCount)
327347
f.lastWrittenOffset = off + int64(len(data)) - 1
328348
}
329349
return n, status
@@ -339,7 +359,7 @@ func (f *file) Release() {
339359
f.released = true
340360
f.fdLock.Unlock()
341361

342-
wlock.unregister(f.devIno)
362+
openFileMap.unregister(f.devIno)
343363
}
344364

345365
// Flush - FUSE call

internal/fusefrontend/file_allocate_truncate.go

Lines changed: 10 additions & 6 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.devIno)
54-
defer wlock.unlock(f.devIno)
53+
f.fileTableEntry.writeLock.Lock()
54+
defer f.fileTableEntry.writeLock.Unlock()
5555

5656
blocks := f.contentEnc.ExplodePlainRange(off, sz)
5757
firstBlock := blocks[0]
@@ -100,8 +100,8 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
100100
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.devIno)
104-
defer wlock.unlock(f.devIno)
103+
f.fileTableEntry.writeLock.Lock()
104+
defer f.fileTableEntry.writeLock.Unlock()
105105
var err error
106106
// Common case first: Truncate to zero
107107
if newSize == 0 {
@@ -111,7 +111,9 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
111111
return fuse.ToStatus(err)
112112
}
113113
// Truncate to zero kills the file header
114-
f.header = nil
114+
f.fileTableEntry.IDLock.Lock()
115+
f.fileTableEntry.ID = nil
116+
f.fileTableEntry.IDLock.Unlock()
115117
return fuse.OK
116118
}
117119
// We need the old file size to determine if we are growing or shrinking
@@ -184,7 +186,9 @@ func (f *file) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Statu
184186
var err error
185187
// File was empty, create new header
186188
if oldPlainSz == 0 {
187-
err = f.createHeader()
189+
f.fileTableEntry.IDLock.Lock()
190+
_, err = f.createHeader()
191+
f.fileTableEntry.IDLock.Unlock()
188192
if err != nil {
189193
return fuse.ToStatus(err)
190194
}

internal/fusefrontend/write_lock.go

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,81 +22,79 @@ func DevInoFromStat(st *syscall.Stat_t) DevInoStruct {
2222
}
2323

2424
func init() {
25-
wlock.inodeLocks = make(map[DevInoStruct]*refCntMutex)
25+
openFileMap.entries = make(map[DevInoStruct]*openFileEntryT)
2626
}
2727

2828
// wlock - serializes write accesses to each file (identified by inode number)
2929
// Writing partial blocks means we have to do read-modify-write cycles. We
3030
// really don't want concurrent writes there.
3131
// Concurrent full-block writes could actually be allowed, but are not to
3232
// keep the locking simple.
33-
var wlock wlockMap
33+
var openFileMap openFileMapT
3434

3535
// wlockMap - usage:
3636
// 1) register
3737
// 2) lock ... unlock ...
3838
// 3) unregister
39-
type wlockMap struct {
40-
// opCount counts lock() calls. As every operation that modifies a file should
39+
type openFileMapT struct {
40+
// opCount counts writeLock.Lock() calls. As every operation that modifies a file should
4141
// call it, this effectively serves as a write-operation counter.
4242
// The variable is accessed without holding any locks so atomic operations
4343
// must be used. It must be the first element of the struct to guarantee
4444
// 64-bit alignment.
4545
opCount uint64
4646
// Protects map access
4747
sync.Mutex
48-
inodeLocks map[DevInoStruct]*refCntMutex
48+
entries map[DevInoStruct]*openFileEntryT
4949
}
5050

51-
// refCntMutex - mutex with reference count
52-
type refCntMutex struct {
53-
// Write lock for this inode
51+
type opCountMutex struct {
5452
sync.Mutex
53+
// Points to the opCount variable of the parent openFileMapT
54+
opCount *uint64
55+
}
56+
57+
func (o *opCountMutex) Lock() {
58+
o.Mutex.Lock()
59+
atomic.AddUint64(o.opCount, 1)
60+
}
61+
62+
// refCntMutex - mutex with reference count
63+
type openFileEntryT struct {
5564
// Reference count
5665
refCnt int
66+
// Write lock for this inode
67+
writeLock *opCountMutex
68+
// ID is the file ID in the file header.
69+
ID []byte
70+
IDLock sync.RWMutex
5771
}
5872

5973
// register creates an entry for "ino", or incrementes the reference count
6074
// if the entry already exists.
61-
func (w *wlockMap) register(di DevInoStruct) {
75+
func (w *openFileMapT) register(di DevInoStruct) *openFileEntryT {
6276
w.Lock()
6377
defer w.Unlock()
6478

65-
r := w.inodeLocks[di]
79+
r := w.entries[di]
6680
if r == nil {
67-
r = &refCntMutex{}
68-
w.inodeLocks[di] = r
81+
o := opCountMutex{opCount: &w.opCount}
82+
r = &openFileEntryT{writeLock: &o}
83+
w.entries[di] = r
6984
}
7085
r.refCnt++
86+
return r
7187
}
7288

7389
// unregister decrements the reference count for "di" and deletes the entry if
7490
// the reference count has reached 0.
75-
func (w *wlockMap) unregister(di DevInoStruct) {
91+
func (w *openFileMapT) unregister(di DevInoStruct) {
7692
w.Lock()
7793
defer w.Unlock()
7894

79-
r := w.inodeLocks[di]
95+
r := w.entries[di]
8096
r.refCnt--
8197
if r.refCnt == 0 {
82-
delete(w.inodeLocks, di)
98+
delete(w.entries, di)
8399
}
84100
}
85-
86-
// lock retrieves the entry for "di" and locks it.
87-
func (w *wlockMap) lock(di DevInoStruct) {
88-
atomic.AddUint64(&w.opCount, 1)
89-
w.Lock()
90-
r := w.inodeLocks[di]
91-
w.Unlock()
92-
// this can take a long time - execute outside the wlockMap lock
93-
r.Lock()
94-
}
95-
96-
// unlock retrieves the entry for "di" and unlocks it.
97-
func (w *wlockMap) unlock(di DevInoStruct) {
98-
w.Lock()
99-
r := w.inodeLocks[di]
100-
w.Unlock()
101-
r.Unlock()
102-
}

0 commit comments

Comments
 (0)