btrfs: split out ro->rw and rw->ro helpers into their own functions
authorJosef Bacik <josef@toxicpanda.com>
Wed, 22 Nov 2023 17:17:42 +0000 (12:17 -0500)
committerDavid Sterba <dsterba@suse.com>
Fri, 15 Dec 2023 19:27:03 +0000 (20:27 +0100)
When we remount ro->rw or rw->ro we have some cleanup tasks that have to
be managed.  Split these out into their own function to make
btrfs_remount smaller.

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Anand Jain <anand.jain@oracle.com>
Acked-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/super.c

index 332d6d2..53d6d8f 100644 (file)
@@ -1676,6 +1676,115 @@ static inline void btrfs_remount_cleanup(struct btrfs_fs_info *fs_info,
                btrfs_set_free_space_cache_v1_active(fs_info, cache_opt);
 }
 
+static int btrfs_remount_rw(struct btrfs_fs_info *fs_info)
+{
+       int ret;
+
+       if (BTRFS_FS_ERROR(fs_info)) {
+               btrfs_err(fs_info,
+                         "remounting read-write after error is not allowed");
+               return -EINVAL;
+       }
+
+       if (fs_info->fs_devices->rw_devices == 0)
+               return -EACCES;
+
+       if (!btrfs_check_rw_degradable(fs_info, NULL)) {
+               btrfs_warn(fs_info,
+                          "too many missing devices, writable remount is not allowed");
+               return -EACCES;
+       }
+
+       if (btrfs_super_log_root(fs_info->super_copy) != 0) {
+               btrfs_warn(fs_info,
+                          "mount required to replay tree-log, cannot remount read-write");
+               return -EINVAL;
+       }
+
+       /*
+        * NOTE: when remounting with a change that does writes, don't put it
+        * anywhere above this point, as we are not sure to be safe to write
+        * until we pass the above checks.
+        */
+       ret = btrfs_start_pre_rw_mount(fs_info);
+       if (ret)
+               return ret;
+
+       btrfs_clear_sb_rdonly(fs_info->sb);
+
+       set_bit(BTRFS_FS_OPEN, &fs_info->flags);
+
+       /*
+        * If we've gone from readonly -> read-write, we need to get our
+        * sync/async discard lists in the right state.
+        */
+       btrfs_discard_resume(fs_info);
+
+       return 0;
+}
+
+static int btrfs_remount_ro(struct btrfs_fs_info *fs_info)
+{
+       /*
+        * This also happens on 'umount -rf' or on shutdown, when the
+        * filesystem is busy.
+        */
+       cancel_work_sync(&fs_info->async_reclaim_work);
+       cancel_work_sync(&fs_info->async_data_reclaim_work);
+
+       btrfs_discard_cleanup(fs_info);
+
+       /* Wait for the uuid_scan task to finish */
+       down(&fs_info->uuid_tree_rescan_sem);
+       /* Avoid complains from lockdep et al. */
+       up(&fs_info->uuid_tree_rescan_sem);
+
+       btrfs_set_sb_rdonly(fs_info->sb);
+
+       /*
+        * Setting SB_RDONLY will put the cleaner thread to sleep at the next
+        * loop if it's already active.  If it's already asleep, we'll leave
+        * unused block groups on disk until we're mounted read-write again
+        * unless we clean them up here.
+        */
+       btrfs_delete_unused_bgs(fs_info);
+
+       /*
+        * The cleaner task could be already running before we set the flag
+        * BTRFS_FS_STATE_RO (and SB_RDONLY in the superblock).  We must make
+        * sure that after we finish the remount, i.e. after we call
+        * btrfs_commit_super(), the cleaner can no longer start a transaction
+        * - either because it was dropping a dead root, running delayed iputs
+        *   or deleting an unused block group (the cleaner picked a block
+        *   group from the list of unused block groups before we were able to
+        *   in the previous call to btrfs_delete_unused_bgs()).
+        */
+       wait_on_bit(&fs_info->flags, BTRFS_FS_CLEANER_RUNNING, TASK_UNINTERRUPTIBLE);
+
+       /*
+        * We've set the superblock to RO mode, so we might have made the
+        * cleaner task sleep without running all pending delayed iputs. Go
+        * through all the delayed iputs here, so that if an unmount happens
+        * without remounting RW we don't end up at finishing close_ctree()
+        * with a non-empty list of delayed iputs.
+        */
+       btrfs_run_delayed_iputs(fs_info);
+
+       btrfs_dev_replace_suspend_for_unmount(fs_info);
+       btrfs_scrub_cancel(fs_info);
+       btrfs_pause_balance(fs_info);
+
+       /*
+        * Pause the qgroup rescan worker if it is running. We don't want it to
+        * be still running after we are in RO mode, as after that, by the time
+        * we unmount, it might have left a transaction open, so we would leak
+        * the transaction and/or crash.
+        */
+       btrfs_qgroup_wait_for_completion(fs_info, false);
+
+       return btrfs_commit_super(fs_info);
+}
+
 static int btrfs_remount(struct super_block *sb, int *flags, char *data)
 {
        struct btrfs_fs_info *fs_info = btrfs_sb(sb);
@@ -1729,120 +1838,14 @@ static int btrfs_remount(struct super_block *sb, int *flags, char *data)
                }
        }
 
-       if ((bool)(*flags & SB_RDONLY) == sb_rdonly(sb))
-               goto out;
-
-       if (*flags & SB_RDONLY) {
-               /*
-                * this also happens on 'umount -rf' or on shutdown, when
-                * the filesystem is busy.
-                */
-               cancel_work_sync(&fs_info->async_reclaim_work);
-               cancel_work_sync(&fs_info->async_data_reclaim_work);
-
-               btrfs_discard_cleanup(fs_info);
-
-               /* wait for the uuid_scan task to finish */
-               down(&fs_info->uuid_tree_rescan_sem);
-               /* avoid complains from lockdep et al. */
-               up(&fs_info->uuid_tree_rescan_sem);
-
-               btrfs_set_sb_rdonly(sb);
-
-               /*
-                * Setting SB_RDONLY will put the cleaner thread to
-                * sleep at the next loop if it's already active.
-                * If it's already asleep, we'll leave unused block
-                * groups on disk until we're mounted read-write again
-                * unless we clean them up here.
-                */
-               btrfs_delete_unused_bgs(fs_info);
-
-               /*
-                * The cleaner task could be already running before we set the
-                * flag BTRFS_FS_STATE_RO (and SB_RDONLY in the superblock).
-                * We must make sure that after we finish the remount, i.e. after
-                * we call btrfs_commit_super(), the cleaner can no longer start
-                * a transaction - either because it was dropping a dead root,
-                * running delayed iputs or deleting an unused block group (the
-                * cleaner picked a block group from the list of unused block
-                * groups before we were able to in the previous call to
-                * btrfs_delete_unused_bgs()).
-                */
-               wait_on_bit(&fs_info->flags, BTRFS_FS_CLEANER_RUNNING,
-                           TASK_UNINTERRUPTIBLE);
-
-               /*
-                * We've set the superblock to RO mode, so we might have made
-                * the cleaner task sleep without running all pending delayed
-                * iputs. Go through all the delayed iputs here, so that if an
-                * unmount happens without remounting RW we don't end up at
-                * finishing close_ctree() with a non-empty list of delayed
-                * iputs.
-                */
-               btrfs_run_delayed_iputs(fs_info);
-
-               btrfs_dev_replace_suspend_for_unmount(fs_info);
-               btrfs_scrub_cancel(fs_info);
-               btrfs_pause_balance(fs_info);
-
-               /*
-                * Pause the qgroup rescan worker if it is running. We don't want
-                * it to be still running after we are in RO mode, as after that,
-                * by the time we unmount, it might have left a transaction open,
-                * so we would leak the transaction and/or crash.
-                */
-               btrfs_qgroup_wait_for_completion(fs_info, false);
-
-               ret = btrfs_commit_super(fs_info);
-               if (ret)
-                       goto restore;
-       } else {
-               if (BTRFS_FS_ERROR(fs_info)) {
-                       btrfs_err(fs_info,
-                               "Remounting read-write after error is not allowed");
-                       ret = -EINVAL;
-                       goto restore;
-               }
-               if (fs_info->fs_devices->rw_devices == 0) {
-                       ret = -EACCES;
-                       goto restore;
-               }
-
-               if (!btrfs_check_rw_degradable(fs_info, NULL)) {
-                       btrfs_warn(fs_info,
-               "too many missing devices, writable remount is not allowed");
-                       ret = -EACCES;
-                       goto restore;
-               }
-
-               if (btrfs_super_log_root(fs_info->super_copy) != 0) {
-                       btrfs_warn(fs_info,
-               "mount required to replay tree-log, cannot remount read-write");
-                       ret = -EINVAL;
-                       goto restore;
-               }
-
-               /*
-                * NOTE: when remounting with a change that does writes, don't
-                * put it anywhere above this point, as we are not sure to be
-                * safe to write until we pass the above checks.
-                */
-               ret = btrfs_start_pre_rw_mount(fs_info);
-               if (ret)
-                       goto restore;
-
-               btrfs_clear_sb_rdonly(sb);
-
-               set_bit(BTRFS_FS_OPEN, &fs_info->flags);
+       ret = 0;
+       if (!sb_rdonly(sb) && (*flags & SB_RDONLY))
+               ret = btrfs_remount_ro(fs_info);
+       else if (sb_rdonly(sb) && !(*flags & SB_RDONLY))
+               ret = btrfs_remount_rw(fs_info);
+       if (ret)
+               goto restore;
 
-               /*
-                * If we've gone from readonly -> read/write, we need to get
-                * our sync/async discard lists in the right state.
-                */
-               btrfs_discard_resume(fs_info);
-       }
-out:
        /*
         * We need to set SB_I_VERSION here otherwise it'll get cleared by VFS,
         * since the absence of the flag means it can be toggled off by remount.