Skip to content

Commit 44c3329

Browse files
committed
make it thread-safe
This patch modifies this fork of lwext4 to make is safe to interact with by multiple threads running in OSv. The key assumption is, that OSv VFS layer provides necessary locking around all interactions with lwext4 to guard ext filesystem metadata (i-node table, directory entries, etc) modifications confined to specific vnode. Beyond that, we add necessary locking around 3 key common data structures: - i-node bitmaps in ext4_ialloc.c - data block bitmaps in ext4_balloc.c - metadata blockcache in ext4_bcache.c and related files More specifically following functions are protected with inode_alloc_lock()/unlock() to make sure no two files/directories get assigned same inode number: - ext4_ialloc_alloc_inode() - ext4_ialloc_free_inode() Next, following functions are protected with block_alloc_lock()/unlock() to make sure no two files/directories use same data block: - ext4_balloc_alloc_block() - ext4_balloc_free_block() - ext4_balloc_free_blocks() Finally, these functions in ext4_bcache.c and related source files are protected with bcache_lock()/unlock() to make sure the global metadata block cache access is synchronized: - ext4_bcache_invalidate_lba() in __ext4_balloc_free_block() and __ext4_balloc_free_blocks() - ext4_bcache_find_get(), ext4_block_flush_buf() and ext4_bcache_free() in ext4_block_flush_lba() - ext4_block_get_noread(), ext4_bcache_test_flag() ext4_bcache_free() in ext4_block_get() - ext4_bcache_free() in ext4_block_set() - ext4_block_get_noread() in ext4_trans_block_get_noread() Ref gkostka#83 Ref cloudius-systems/osv#1179 Signed-off-by: Waldemar Kozaczuk <[email protected]>
1 parent c6fed65 commit 44c3329

File tree

8 files changed

+134
-39
lines changed

8 files changed

+134
-39
lines changed

include/ext4_bcache.h

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,26 @@ struct ext4_block {
6565

6666
struct ext4_bcache;
6767

68+
/**@brief buffer state bits
69+
*
70+
* - BC_UPTODATE: Buffer contains valid data.
71+
* - BC_DIRTY: Buffer is dirty.
72+
* - BC_FLUSH: Buffer will be immediately flushed,
73+
* when no one references it.
74+
* - BC_TMP: Buffer will be dropped once its refctr
75+
* reaches zero.
76+
*/
77+
enum bcache_state_bits {
78+
BC_UPTODATE, // Mostly set in ext4_block_get() after reading from disk
79+
BC_DIRTY, // Most importantly set in ext4_bcache_set_dirty()
80+
BC_FLUSH, // Set in journal code
81+
BC_TMP // Set in journal code
82+
};
83+
6884
/**@brief Single block descriptor*/
6985
struct ext4_buf {
7086
/**@brief Flags*/
71-
int flags;
87+
char flags[BC_TMP + 1];
7288

7389
/**@brief Logical block address*/
7490
uint64_t lba;
@@ -148,30 +164,14 @@ struct ext4_bcache {
148164
SLIST_HEAD(ext4_buf_dirty, ext4_buf) dirty_list;
149165
};
150166

151-
/**@brief buffer state bits
152-
*
153-
* - BC♡UPTODATE: Buffer contains valid data.
154-
* - BC_DIRTY: Buffer is dirty.
155-
* - BC_FLUSH: Buffer will be immediately flushed,
156-
* when no one references it.
157-
* - BC_TMP: Buffer will be dropped once its refctr
158-
* reaches zero.
159-
*/
160-
enum bcache_state_bits {
161-
BC_UPTODATE,
162-
BC_DIRTY,
163-
BC_FLUSH,
164-
BC_TMP
165-
};
166-
167167
#define ext4_bcache_set_flag(buf, b) \
168-
(buf)->flags |= 1 << (b)
168+
(buf)->flags[b] = 1
169169

170170
#define ext4_bcache_clear_flag(buf, b) \
171-
(buf)->flags &= ~(1 << (b))
171+
(buf)->flags[b] = 0
172172

173173
#define ext4_bcache_test_flag(buf, b) \
174-
(((buf)->flags & (1 << (b))) >> (b))
174+
(((buf)->flags[b] == 1 ))
175175

176176
static inline void ext4_bcache_set_dirty(struct ext4_buf *buf) {
177177
ext4_bcache_set_flag(buf, BC_UPTODATE);
@@ -239,12 +239,6 @@ struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc);
239239
* @param buf buffer*/
240240
void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf);
241241

242-
/**@brief Invalidate a buffer.
243-
* @param bc block cache descriptor
244-
* @param buf buffer*/
245-
void ext4_bcache_invalidate_buf(struct ext4_bcache *bc,
246-
struct ext4_buf *buf);
247-
248242
/**@brief Invalidate a range of buffers.
249243
* @param bc block cache descriptor
250244
* @param from starting lba

include/ext4_fs.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ struct ext4_fs {
6767
struct jbd_fs *jbd_fs;
6868
struct jbd_journal *jbd_journal;
6969
struct jbd_trans *curr_trans;
70+
71+
void (*inode_alloc_lock)();
72+
void (*inode_alloc_unlock)();
73+
74+
void (*block_alloc_lock)();
75+
void (*block_alloc_unlock)();
76+
77+
void (*bcache_lock)();
78+
void (*bcache_unlock)();
7079
};
7180

7281
struct ext4_block_group_ref {

src/ext4_balloc.c

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ ext4_balloc_verify_bitmap_csum(struct ext4_sblock *sb,
149149
#define ext4_balloc_verify_bitmap_csum(...) true
150150
#endif
151151

152-
int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr)
152+
static int
153+
__ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr)
153154
{
154155
struct ext4_fs *fs = inode_ref->fs;
155156
struct ext4_sblock *sb = &fs->sb;
@@ -223,14 +224,25 @@ int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr)
223224
ext4_fs_put_block_group_ref(&bg_ref);
224225
return rc;
225226
}
227+
fs->bcache_lock();
226228
ext4_bcache_invalidate_lba(fs->bdev->bc, baddr, 1);
229+
fs->bcache_unlock();
227230
/* Release block group reference */
228231
rc = ext4_fs_put_block_group_ref(&bg_ref);
229232

230233
return rc;
231234
}
232235

233-
int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref,
236+
int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr)
237+
{
238+
inode_ref->fs->block_alloc_lock();
239+
int rc = __ext4_balloc_free_block(inode_ref, baddr);
240+
inode_ref->fs->block_alloc_unlock();
241+
return rc;
242+
}
243+
244+
static int
245+
__ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref,
234246
ext4_fsblk_t first, uint32_t count)
235247
{
236248
int rc = EOK;
@@ -342,14 +354,26 @@ int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref,
342354

343355
}
344356

357+
fs->bcache_lock();
345358
ext4_bcache_invalidate_lba(fs->bdev->bc, start_block, blk_cnt);
359+
fs->bcache_unlock();
346360
/*All blocks should be released*/
347361
ext4_assert(count == 0);
348362

349363
return rc;
350364
}
351365

352-
int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref,
366+
int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref,
367+
ext4_fsblk_t first, uint32_t count)
368+
{
369+
inode_ref->fs->block_alloc_lock();
370+
int rc = __ext4_balloc_free_blocks(inode_ref, first, count);
371+
inode_ref->fs->block_alloc_unlock();
372+
return rc;
373+
}
374+
375+
static int
376+
__ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref,
353377
ext4_fsblk_t goal,
354378
ext4_fsblk_t *fblock)
355379
{
@@ -582,6 +606,16 @@ int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref,
582606
return r;
583607
}
584608

609+
int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref,
610+
ext4_fsblk_t goal,
611+
ext4_fsblk_t *fblock)
612+
{
613+
inode_ref->fs->block_alloc_lock();
614+
int rc = __ext4_balloc_alloc_block(inode_ref, goal, fblock);
615+
inode_ref->fs->block_alloc_unlock();
616+
return rc;
617+
}
618+
585619
int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref,
586620
ext4_fsblk_t baddr, bool *free)
587621
{

src/ext4_bcache.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,19 @@ ext4_buf_lookup(struct ext4_bcache *bc, uint64_t lba)
153153
return RB_FIND(ext4_buf_lba, &bc->lba_root, &tmp);
154154
}
155155

156+
// No need for locking
157+
// Called by ext4_block_cache_shake() ONLY
158+
// PROTECTED in ext4_block_get_noread() because the only
159+
// caller - ext4_block_cache_shake() - is protected
156160
struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc)
157161
{
158162
return RB_MIN(ext4_buf_lru, &bc->lru_root);
159163
}
160164

165+
// No need for locking
166+
// Called by ext4_block_cache_shake() and functions in this file
167+
// PROTECTED in ext4_block_get_noread() because the
168+
// caller - ext4_block_cache_shake() - is protected
161169
void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf)
162170
{
163171
/* Warn on dropping any referenced buffers.*/
@@ -178,6 +186,10 @@ void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf)
178186
bc->ref_blocks--;
179187
}
180188

189+
/**@brief Invalidate a buffer.
190+
* @param bc block cache descriptor
191+
* @param buf buffer*/
192+
static
181193
void ext4_bcache_invalidate_buf(struct ext4_bcache *bc,
182194
struct ext4_buf *buf)
183195
{
@@ -191,6 +203,9 @@ void ext4_bcache_invalidate_buf(struct ext4_bcache *bc,
191203
ext4_bcache_clear_dirty(buf);
192204
}
193205

206+
// Called by ext4_balloc_free_block/s()
207+
// Protect by LOCK
208+
// PROTECTED in the callers
194209
void ext4_bcache_invalidate_lba(struct ext4_bcache *bc,
195210
uint64_t from,
196211
uint32_t cnt)
@@ -205,6 +220,10 @@ void ext4_bcache_invalidate_lba(struct ext4_bcache *bc,
205220
}
206221
}
207222

223+
// Called by:
224+
// - ext4_bcache_alloc() - No need to lock (see below)
225+
// - ext4_block_flush_lba() - probably need to lock there - PROTECTED
226+
// - 2 methods in ext4_journal() not used right now - ignore
208227
struct ext4_buf *
209228
ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b,
210229
uint64_t lba)
@@ -231,6 +250,10 @@ ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b,
231250
return buf;
232251
}
233252

253+
// Called by ext4_block_get_noread() ONLY which also calls ext4_block_cache_shake()
254+
// which calls numbers of function here. So no need for locking,
255+
// and maybe we should lock in ext4_block_get_noread()?
256+
// PROTECTED in ext4_block_get_noread()
234257
int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b,
235258
bool *is_new)
236259
{
@@ -267,6 +290,8 @@ int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b,
267290
return EOK;
268291
}
269292

293+
// Called by 3 functions - ext4_block_flush_lba(), ext4_block_get() and ext4_block_set()
294+
// Protected there
270295
int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b)
271296
{
272297
struct ext4_buf *buf = b->buf;

src/ext4_blockdev.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,19 @@ int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf)
170170
return EOK;
171171
}
172172

173+
//Used indirectly by journal-ing code
173174
int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba)
174175
{
175176
int r = EOK;
176177
struct ext4_buf *buf;
177178
struct ext4_block b;
179+
bdev->fs->bcache_lock();
178180
buf = ext4_bcache_find_get(bdev->bc, &b, lba);
179181
if (buf) {
180182
r = ext4_block_flush_buf(bdev, buf);
181183
ext4_bcache_free(bdev->bc, &b);
182184
}
185+
bdev->fs->bcache_unlock();
183186
return r;
184187
}
185188

@@ -244,26 +247,32 @@ int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b,
244247
int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b,
245248
uint64_t lba)
246249
{
250+
bdev->fs->bcache_lock();
247251
int r = ext4_block_get_noread(bdev, b, lba);
248-
if (r != EOK)
252+
if (r != EOK) {
253+
bdev->fs->bcache_unlock();
249254
return r;
255+
}
250256

251257
if (ext4_bcache_test_flag(b->buf, BC_UPTODATE)) {
252258
/* Data in the cache is up-to-date.
253259
* Reading from physical device is not required */
260+
bdev->fs->bcache_unlock();
254261
return EOK;
255262
}
256263

257264
r = ext4_blocks_get_direct(bdev, b->data, lba, 1);
258265
if (r != EOK) {
259266
ext4_bcache_free(bdev->bc, b);
267+
bdev->fs->bcache_unlock();
260268
b->lb_id = 0;
261269
return r;
262270
}
263271

264272
/* Mark buffer up-to-date, since
265273
* fresh data is read from physical device just now. */
266274
ext4_bcache_set_flag(b->buf, BC_UPTODATE);
275+
bdev->fs->bcache_unlock();
267276
return EOK;
268277
}
269278

@@ -275,7 +284,10 @@ int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b)
275284
if (!bdev->bdif->ph_refctr)
276285
return EIO;
277286

278-
return ext4_bcache_free(bdev->bc, b);
287+
bdev->fs->bcache_lock();
288+
int rc = ext4_bcache_free(bdev->bc, b);
289+
bdev->fs->bcache_unlock();
290+
return rc;
279291
}
280292

281293
int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba,

src/ext4_fs.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,8 +1657,10 @@ int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref,
16571657
if (rc != EOK)
16581658
return rc;
16591659

1660-
*fblock = current_fsblk;
1661-
ext4_assert(*fblock);
1660+
if (fblock) {
1661+
*fblock = current_fsblk;
1662+
ext4_assert(*fblock);
1663+
}
16621664

16631665
ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
16641666
inode_ref->dirty = true;
@@ -1702,7 +1704,8 @@ int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref,
17021704
ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
17031705
inode_ref->dirty = true;
17041706

1705-
*fblock = phys_block;
1707+
if (fblock)
1708+
*fblock = phys_block;
17061709
*iblock = new_block_idx;
17071710

17081711
return EOK;

src/ext4_ialloc.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ static uint32_t ext4_ialloc_bitmap_csum(struct ext4_sblock *sb, void *bitmap)
110110
#define ext4_ialloc_bitmap_csum(...) 0
111111
#endif
112112

113+
//TODO: Check this one when called by ext4_fs_get_block_group_ref()
113114
void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg,
114115
void *bitmap __unused)
115116
{
@@ -155,7 +156,8 @@ ext4_ialloc_verify_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg,
155156
#define ext4_ialloc_verify_bitmap_csum(...) true
156157
#endif
157158

158-
int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir)
159+
static int
160+
__ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir)
159161
{
160162
struct ext4_sblock *sb = &fs->sb;
161163

@@ -226,7 +228,16 @@ int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir)
226228
return EOK;
227229
}
228230

229-
int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir)
231+
int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir)
232+
{
233+
fs->inode_alloc_lock();
234+
int rc = __ext4_ialloc_free_inode(fs, index, is_dir);
235+
fs->inode_alloc_unlock();
236+
return rc;
237+
}
238+
239+
static int
240+
__ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir)
230241
{
231242
struct ext4_sblock *sb = &fs->sb;
232243

@@ -365,6 +376,14 @@ int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir)
365376
return ENOSPC;
366377
}
367378

379+
int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir)
380+
{
381+
fs->inode_alloc_lock();
382+
int rc = __ext4_ialloc_alloc_inode(fs, idx, is_dir);
383+
fs->inode_alloc_unlock();
384+
return rc;
385+
}
386+
368387
/**
369388
* @}
370389
*/

src/ext4_trans.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,9 @@ int ext4_trans_block_get_noread(struct ext4_blockdev *bdev,
6868
struct ext4_block *b,
6969
uint64_t lba)
7070
{
71+
bdev->fs->bcache_lock();
7172
int r = ext4_block_get_noread(bdev, b, lba);
72-
if (r != EOK)
73-
return r;
74-
73+
bdev->fs->bcache_unlock();
7574
return r;
7675
}
7776

0 commit comments

Comments
 (0)