Merge tag 'vfs-6.6-merge-2' of ssh://gitolite.kernel.org/pub/scm/fs/xfs/xfs-linux
authorChristian Brauner <brauner@kernel.org>
Wed, 23 Aug 2023 11:06:55 +0000 (13:06 +0200)
committerChristian Brauner <brauner@kernel.org>
Wed, 23 Aug 2023 11:06:55 +0000 (13:06 +0200)
Pull filesystem freezing updates from Darrick Wong:

New code for 6.6:

 * Allow the kernel to initiate a freeze of a filesystem.  The kernel
   and userspace can both hold a freeze on a filesystem at the same
   time; the freeze is not lifted until /both/ holders lift it.  This
   will enable us to fix a longstanding bug in XFS online fsck.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Message-Id: <20230822182604.GB11286@frogsfrogsfrogs>
Signed-off-by: Christian Brauner <brauner@kernel.org>
1  2 
block/bdev.c
fs/super.c
include/linux/fs.h

diff --cc block/bdev.c
Simple merge
diff --cc fs/super.c
@@@ -1178,13 -1027,12 +1178,13 @@@ void emergency_remount(void
  
  static void do_thaw_all_callback(struct super_block *sb)
  {
 -      down_write(&sb->s_umount);
 -      if (sb->s_root && sb->s_flags & SB_BORN) {
 +      bool born = super_lock_excl(sb);
 +
 +      if (born && sb->s_root) {
                emergency_thaw_bdev(sb);
-               thaw_super_locked(sb);
+               thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
        } else {
 -              up_write(&sb->s_umount);
 +              super_unlock_excl(sb);
        }
  }
  
@@@ -1873,16 -1710,35 +1902,36 @@@ int freeze_super(struct super_block *sb
  {
        int ret;
  
-       /* Since the caller must already have an active reference... */
        atomic_inc(&sb->s_active);
-       /* ...@sb definitely can't be dying. */
 -      down_write(&sb->s_umount);
 +      if (!super_lock_excl(sb))
 +              WARN(1, "Dying superblock while freezing!");
  
 -              up_write(&sb->s_umount);
+ retry:
+       if (sb->s_writers.frozen == SB_FREEZE_COMPLETE) {
+               if (sb->s_writers.freeze_holders & who) {
+                       deactivate_locked_super(sb);
+                       return -EBUSY;
+               }
+               WARN_ON(sb->s_writers.freeze_holders == 0);
+               /*
+                * Someone else already holds this type of freeze; share the
+                * freeze and assign the active ref to the freeze.
+                */
+               sb->s_writers.freeze_holders |= who;
++              super_unlock_excl(sb);
+               return 0;
+       }
        if (sb->s_writers.frozen != SB_UNFROZEN) {
-               deactivate_locked_super(sb);
-               return -EBUSY;
+               ret = wait_for_partially_frozen(sb);
+               if (ret) {
+                       deactivate_locked_super(sb);
+                       return ret;
+               }
+               goto retry;
        }
  
        if (!(sb->s_flags & SB_BORN)) {
  
        if (sb_rdonly(sb)) {
                /* Nothing to do really... */
+               sb->s_writers.freeze_holders |= who;
                sb->s_writers.frozen = SB_FREEZE_COMPLETE;
 -              up_write(&sb->s_umount);
+               wake_up_var(&sb->s_writers.frozen);
 +              super_unlock_excl(sb);
                return 0;
        }
  
        sb->s_writers.frozen = SB_FREEZE_WRITE;
        /* Release s_umount to preserve sb_start_write -> s_umount ordering */
 -      up_write(&sb->s_umount);
 +      super_unlock_excl(sb);
        sb_wait_write(sb, SB_FREEZE_WRITE);
-       /* We're still holding an active reference. */
 -      down_write(&sb->s_umount);
 +      if (!super_lock_excl(sb))
 +              WARN(1, "Dying superblock while freezing!");
  
        /* Now we go and block page faults... */
        sb->s_writers.frozen = SB_FREEZE_PAGEFAULT;
         * For debugging purposes so that fs can warn if it sees write activity
         * when frozen is set to SB_FREEZE_COMPLETE, and for thaw_super().
         */
+       sb->s_writers.freeze_holders |= who;
        sb->s_writers.frozen = SB_FREEZE_COMPLETE;
+       wake_up_var(&sb->s_writers.frozen);
        lockdep_sb_freeze_release(sb);
 -      up_write(&sb->s_umount);
 +      super_unlock_excl(sb);
        return 0;
  }
  EXPORT_SYMBOL(freeze_super);
@@@ -1949,8 -1814,24 +2008,24 @@@ static int thaw_super_locked(struct sup
  {
        int error;
  
-       if (sb->s_writers.frozen != SB_FREEZE_COMPLETE) {
+       if (sb->s_writers.frozen == SB_FREEZE_COMPLETE) {
+               if (!(sb->s_writers.freeze_holders & who)) {
 -                      up_write(&sb->s_umount);
++                      super_unlock_excl(sb);
+                       return -EINVAL;
+               }
+               /*
+                * Freeze is shared with someone else.  Release our hold and
+                * drop the active ref that freeze_super assigned to the
+                * freezer.
+                */
+               if (sb->s_writers.freeze_holders & ~who) {
+                       sb->s_writers.freeze_holders &= ~who;
+                       deactivate_locked_super(sb);
+                       return 0;
+               }
+       } else {
 -              up_write(&sb->s_umount);
 +              super_unlock_excl(sb);
                return -EINVAL;
        }
  
        if (sb->s_op->unfreeze_fs) {
                error = sb->s_op->unfreeze_fs(sb);
                if (error) {
--                      printk(KERN_ERR
--                              "VFS:Filesystem thaw failed\n");
++                      printk(KERN_ERR "VFS:Filesystem thaw failed\n");
                        lockdep_sb_freeze_release(sb);
 -                      up_write(&sb->s_umount);
 +                      super_unlock_excl(sb);
                        return error;
                }
        }
  /**
   * thaw_super -- unlock filesystem
   * @sb: the super to thaw
+  * @who: context that wants to freeze
+  *
+  * Unlocks the filesystem and marks it writeable again after freeze_super()
+  * if there are no remaining freezes on the filesystem.
   *
-  * Unlocks the filesystem and marks it writeable again after freeze_super().
+  * @who should be:
+  * * %FREEZE_HOLDER_USERSPACE if userspace wants to thaw the fs;
+  * * %FREEZE_HOLDER_KERNEL if the kernel wants to thaw the fs.
   */
- int thaw_super(struct super_block *sb)
+ int thaw_super(struct super_block *sb, enum freeze_holder who)
  {
 -      down_write(&sb->s_umount);
 +      if (!super_lock_excl(sb))
 +              WARN(1, "Dying superblock while thawing!");
-       return thaw_super_locked(sb);
+       return thaw_super_locked(sb, who);
  }
  EXPORT_SYMBOL(thaw_super);
  
Simple merge