Whamcloud - gitweb
libext2fs: fix livelock in the unix io manager
authorDarrick J. Wong <djwong@kernel.org>
Wed, 21 May 2025 22:35:26 +0000 (15:35 -0700)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 23 May 2025 05:10:10 +0000 (01:10 -0400)
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@vger.kernel.org # v1.46.0
Fixes: f20627cc639ab6 ("libext2fs: add threading support to the I/O manager abstraction")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Link: https://lore.kernel.org/r/174786677584.1383760.1107858755678329558.stgit@frogsfrogsfrogs
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
lib/ext2fs/unix_io.c

index 0ab9de6..c9736f6 100644 (file)
@@ -344,7 +344,8 @@ error_unlock:
        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 @@ bounce_write:
        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 @@ bounce_write:
                        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 @@ bounce_write:
        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;