From: Darrick J. Wong <djwong@xxxxxxxxxx> generic/441 found a livelock in the unix IO manager. Let's say that write_primary_superblock decides to call io_channel_set_blksize in the process of writing the primary super. unix_set_blksize then takes the cache and bounce mutexes, and calls flush_cached_blocks. If there are dirty blocks in the cache, they will be written with raw_write_blk. Unfortunately, that function tries to take the bounce mutex, which we already hold. At that point, we livelock fuse2fs. Cc: <linux-ext4@xxxxxxxxxxxxxxx> # v1.46.0 Fixes: f20627cc639ab6 ("libext2fs: add threading support to the I/O manager abstraction") Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> --- lib/ext2fs/unix_io.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c index b98c44a84bb0af..be70fee38890c8 100644 --- a/lib/ext2fs/unix_io.c +++ b/lib/ext2fs/unix_io.c @@ -344,7 +344,8 @@ static errcode_t raw_read_blk(io_channel channel, return retval; } -#define RAW_WRITE_NO_HANDLER 1 +#define RAW_WRITE_NO_HANDLER (1U << 0) +#define RAW_WRITE_NOLOCK (1U << 1) static errcode_t raw_write_blk(io_channel channel, struct unix_private_data *data, @@ -404,13 +405,15 @@ static errcode_t raw_write_blk(io_channel channel, (IS_ALIGNED(buf, channel->align) && IS_ALIGNED(location, channel->align) && IS_ALIGNED(size, channel->align))) { - mutex_lock(data, BOUNCE_MTX); + if (!(flags & RAW_WRITE_NOLOCK)) + mutex_lock(data, BOUNCE_MTX); if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) { retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; goto error_unlock; } actual = write(data->dev, buf, size); - mutex_unlock(data, BOUNCE_MTX); + if (!(flags & RAW_WRITE_NOLOCK)) + mutex_unlock(data, BOUNCE_MTX); if (actual < 0) { retval = errno; goto error_out; @@ -445,7 +448,8 @@ static errcode_t raw_write_blk(io_channel channel, while (size > 0) { int actual_w; - mutex_lock(data, BOUNCE_MTX); + if (!(flags & RAW_WRITE_NOLOCK)) + mutex_lock(data, BOUNCE_MTX); if (size < align_size || offset) { if (ext2fs_llseek(data->dev, aligned_blk * align_size, SEEK_SET) < 0) { @@ -474,7 +478,8 @@ static errcode_t raw_write_blk(io_channel channel, goto error_unlock; } actual_w = write(data->dev, data->bounce, align_size); - mutex_unlock(data, BOUNCE_MTX); + if (!(flags & RAW_WRITE_NOLOCK)) + mutex_unlock(data, BOUNCE_MTX); if (actual_w < 0) { retval = errno; goto error_out; @@ -490,7 +495,8 @@ static errcode_t raw_write_blk(io_channel channel, return 0; error_unlock: - mutex_unlock(data, BOUNCE_MTX); + if (!(flags & RAW_WRITE_NOLOCK)) + mutex_unlock(data, BOUNCE_MTX); error_out: if (((flags & RAW_WRITE_NO_HANDLER) == 0) && channel->write_error) retval = (channel->write_error)(channel, block, count, buf, @@ -673,9 +679,14 @@ static errcode_t flush_cached_blocks(io_channel channel, if (!cache->in_use) continue; if (cache->dirty) { + int raw_flags = RAW_WRITE_NO_HANDLER; + + if (flags & FLUSH_NOLOCK) + raw_flags |= RAW_WRITE_NOLOCK; + retval = raw_write_blk(channel, data, cache->block, 1, cache->buf, - RAW_WRITE_NO_HANDLER); + raw_flags); if (retval) { cache->write_err = 1; errors_found = 1;