fs: distinguish between user initiated freeze and kernel initiated freeze
authorDarrick J. Wong <djwong@kernel.org>
Mon, 17 Jul 2023 16:00:09 +0000 (09:00 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 17 Jul 2023 16:00:09 +0000 (09:00 -0700)
Userspace can freeze a filesystem using the FIFREEZE ioctl or by
suspending the block device; this state persists until userspace thaws
the filesystem with the FITHAW ioctl or resuming the block device.
Since commit 18e9e5104fcd ("Introduce freeze_super and thaw_super for
the fsfreeze ioctl") we only allow the first freeze command to succeed.

The kernel may decide that it is necessary to freeze a filesystem for
its own internal purposes, such as suspends in progress, filesystem fsck
activities, or quiescing a device prior to removal.  Userspace thaw
commands must never break a kernel freeze, and kernel thaw commands
shouldn't undo userspace's freeze command.

Introduce a couple of freeze holder flags and wire it into the
sb_writers state.  One kernel and one userspace freeze are allowed to
coexist at the same time; the filesystem will not thaw until both are
lifted.

I wonder if the f2fs/gfs2 code should be using a kernel freeze here, but
for now we'll use FREEZE_HOLDER_USERSPACE to preserve existing
behaviors.

Cc: mcgrof@kernel.org
Cc: jack@suse.cz
Cc: hch@infradead.org
Cc: ruansy.fnst@fujitsu.com
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Documentation/filesystems/vfs.rst
block/bdev.c
fs/f2fs/gc.c
fs/gfs2/super.c
fs/gfs2/sys.c
fs/ioctl.c
fs/super.c
include/linux/fs.h

index cb2a97e..176dd46 100644 (file)
@@ -260,9 +260,11 @@ filesystem.  The following members are defined:
                void (*evict_inode) (struct inode *);
                void (*put_super) (struct super_block *);
                int (*sync_fs)(struct super_block *sb, int wait);
-               int (*freeze_super) (struct super_block *);
+               int (*freeze_super) (struct super_block *sb,
+                                       enum freeze_holder who);
                int (*freeze_fs) (struct super_block *);
-               int (*thaw_super) (struct super_block *);
+               int (*thaw_super) (struct super_block *sb,
+                                       enum freeze_wholder who);
                int (*unfreeze_fs) (struct super_block *);
                int (*statfs) (struct dentry *, struct kstatfs *);
                int (*remount_fs) (struct super_block *, int *, char *);
index 979e28a..80ea3fa 100644 (file)
@@ -248,9 +248,9 @@ int freeze_bdev(struct block_device *bdev)
        if (!sb)
                goto sync;
        if (sb->s_op->freeze_super)
-               error = sb->s_op->freeze_super(sb);
+               error = sb->s_op->freeze_super(sb, FREEZE_HOLDER_USERSPACE);
        else
-               error = freeze_super(sb);
+               error = freeze_super(sb, FREEZE_HOLDER_USERSPACE);
        deactivate_super(sb);
 
        if (error) {
@@ -291,9 +291,9 @@ int thaw_bdev(struct block_device *bdev)
                goto out;
 
        if (sb->s_op->thaw_super)
-               error = sb->s_op->thaw_super(sb);
+               error = sb->s_op->thaw_super(sb, FREEZE_HOLDER_USERSPACE);
        else
-               error = thaw_super(sb);
+               error = thaw_super(sb, FREEZE_HOLDER_USERSPACE);
        if (error)
                bdev->bd_fsfreeze_count++;
        else
index 01effd3..a1ca394 100644 (file)
@@ -2181,12 +2181,14 @@ out_drop_write:
        if (err)
                return err;
 
-       err = freeze_super(sbi->sb);
+       err = freeze_super(sbi->sb, FREEZE_HOLDER_USERSPACE);
        if (err)
                return err;
 
        if (f2fs_readonly(sbi->sb)) {
-               thaw_super(sbi->sb);
+               err = thaw_super(sbi->sb, FREEZE_HOLDER_USERSPACE);
+               if (err)
+                       return err;
                return -EROFS;
        }
 
@@ -2240,6 +2242,6 @@ recover_out:
 out_err:
        f2fs_up_write(&sbi->cp_global_sem);
        f2fs_up_write(&sbi->gc_lock);
-       thaw_super(sbi->sb);
+       thaw_super(sbi->sb, FREEZE_HOLDER_USERSPACE);
        return err;
 }
index 9f4d5d6..ae7fedc 100644 (file)
@@ -689,7 +689,7 @@ static int gfs2_freeze_locally(struct gfs2_sbd *sdp)
        struct super_block *sb = sdp->sd_vfs;
        int error;
 
-       error = freeze_super(sb);
+       error = freeze_super(sb, FREEZE_HOLDER_USERSPACE);
        if (error)
                return error;
 
@@ -697,7 +697,9 @@ static int gfs2_freeze_locally(struct gfs2_sbd *sdp)
                gfs2_log_flush(sdp, NULL, GFS2_LOG_HEAD_FLUSH_FREEZE |
                               GFS2_LFC_FREEZE_GO_SYNC);
                if (gfs2_withdrawn(sdp)) {
-                       thaw_super(sb);
+                       error = thaw_super(sb, FREEZE_HOLDER_USERSPACE);
+                       if (error)
+                               return error;
                        return -EIO;
                }
        }
@@ -712,7 +714,7 @@ static int gfs2_do_thaw(struct gfs2_sbd *sdp)
        error = gfs2_freeze_lock_shared(sdp);
        if (error)
                goto fail;
-       error = thaw_super(sb);
+       error = thaw_super(sb, FREEZE_HOLDER_USERSPACE);
        if (!error)
                return 0;
 
@@ -761,7 +763,7 @@ out:
  *
  */
 
-static int gfs2_freeze_super(struct super_block *sb)
+static int gfs2_freeze_super(struct super_block *sb, enum freeze_holder who)
 {
        struct gfs2_sbd *sdp = sb->s_fs_info;
        int error;
@@ -816,7 +818,7 @@ out:
  *
  */
 
-static int gfs2_thaw_super(struct super_block *sb)
+static int gfs2_thaw_super(struct super_block *sb, enum freeze_holder who)
 {
        struct gfs2_sbd *sdp = sb->s_fs_info;
        int error;
index 2dfbe2f..c60bc7f 100644 (file)
@@ -168,10 +168,10 @@ static ssize_t freeze_store(struct gfs2_sbd *sdp, const char *buf, size_t len)
 
        switch (n) {
        case 0:
-               error = thaw_super(sdp->sd_vfs);
+               error = thaw_super(sdp->sd_vfs, FREEZE_HOLDER_USERSPACE);
                break;
        case 1:
-               error = freeze_super(sdp->sd_vfs);
+               error = freeze_super(sdp->sd_vfs, FREEZE_HOLDER_USERSPACE);
                break;
        default:
                return -EINVAL;
index 5b2481c..a56cbce 100644 (file)
@@ -396,8 +396,8 @@ static int ioctl_fsfreeze(struct file *filp)
 
        /* Freeze */
        if (sb->s_op->freeze_super)
-               return sb->s_op->freeze_super(sb);
-       return freeze_super(sb);
+               return sb->s_op->freeze_super(sb, FREEZE_HOLDER_USERSPACE);
+       return freeze_super(sb, FREEZE_HOLDER_USERSPACE);
 }
 
 static int ioctl_fsthaw(struct file *filp)
@@ -409,8 +409,8 @@ static int ioctl_fsthaw(struct file *filp)
 
        /* Thaw */
        if (sb->s_op->thaw_super)
-               return sb->s_op->thaw_super(sb);
-       return thaw_super(sb);
+               return sb->s_op->thaw_super(sb, FREEZE_HOLDER_USERSPACE);
+       return thaw_super(sb, FREEZE_HOLDER_USERSPACE);
 }
 
 static int ioctl_file_dedupe_range(struct file *file,
index e781226..e6db39a 100644 (file)
@@ -39,7 +39,7 @@
 #include <uapi/linux/mount.h>
 #include "internal.h"
 
-static int thaw_super_locked(struct super_block *sb);
+static int thaw_super_locked(struct super_block *sb, enum freeze_holder who);
 
 static LIST_HEAD(super_blocks);
 static DEFINE_SPINLOCK(sb_lock);
@@ -1030,7 +1030,7 @@ static void do_thaw_all_callback(struct super_block *sb)
        down_write(&sb->s_umount);
        if (sb->s_root && sb->s_flags & SB_BORN) {
                emergency_thaw_bdev(sb);
-               thaw_super_locked(sb);
+               thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
        } else {
                up_write(&sb->s_umount);
        }
@@ -1647,11 +1647,22 @@ static void sb_freeze_unlock(struct super_block *sb, int level)
 /**
  * freeze_super - lock the filesystem and force it into a consistent state
  * @sb: the super to lock
+ * @who: context that wants to freeze
  *
  * Syncs the super to make sure the filesystem is consistent and calls the fs's
- * freeze_fs.  Subsequent calls to this without first thawing the fs will return
+ * freeze_fs.  Subsequent calls to this without first thawing the fs may return
  * -EBUSY.
  *
+ * @who should be:
+ * * %FREEZE_HOLDER_USERSPACE if userspace wants to freeze the fs;
+ * * %FREEZE_HOLDER_KERNEL if the kernel wants to freeze the fs.
+ *
+ * The @who argument distinguishes between the kernel and userspace trying to
+ * freeze the filesystem.  Although there cannot be multiple kernel freezes or
+ * multiple userspace freezes in effect at any given time, the kernel and
+ * userspace can both hold a filesystem frozen.  The filesystem remains frozen
+ * until there are no kernel or userspace freezes in effect.
+ *
  * During this function, sb->s_writers.frozen goes through these values:
  *
  * SB_UNFROZEN: File system is normal, all writes progress as usual.
@@ -1677,12 +1688,30 @@ static void sb_freeze_unlock(struct super_block *sb, int level)
  *
  * sb->s_writers.frozen is protected by sb->s_umount.
  */
-int freeze_super(struct super_block *sb)
+int freeze_super(struct super_block *sb, enum freeze_holder who)
 {
        int ret;
 
        atomic_inc(&sb->s_active);
        down_write(&sb->s_umount);
+
+       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;
+               up_write(&sb->s_umount);
+               return 0;
+       }
+
        if (sb->s_writers.frozen != SB_UNFROZEN) {
                deactivate_locked_super(sb);
                return -EBUSY;
@@ -1695,6 +1724,7 @@ int freeze_super(struct super_block *sb)
 
        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);
                return 0;
@@ -1738,6 +1768,7 @@ int freeze_super(struct super_block *sb)
         * 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;
        lockdep_sb_freeze_release(sb);
        up_write(&sb->s_umount);
@@ -1745,16 +1776,39 @@ int freeze_super(struct super_block *sb)
 }
 EXPORT_SYMBOL(freeze_super);
 
-static int thaw_super_locked(struct super_block *sb)
+/*
+ * Undoes the effect of a freeze_super_locked call.  If the filesystem is
+ * frozen both by userspace and the kernel, a thaw call from either source
+ * removes that state without releasing the other state or unlocking the
+ * filesystem.
+ */
+static int thaw_super_locked(struct super_block *sb, enum freeze_holder who)
 {
        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);
+                       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);
                return -EINVAL;
        }
 
        if (sb_rdonly(sb)) {
+               sb->s_writers.freeze_holders &= ~who;
                sb->s_writers.frozen = SB_UNFROZEN;
                goto out;
        }
@@ -1772,6 +1826,7 @@ static int thaw_super_locked(struct super_block *sb)
                }
        }
 
+       sb->s_writers.freeze_holders &= ~who;
        sb->s_writers.frozen = SB_UNFROZEN;
        sb_freeze_unlock(sb, SB_FREEZE_FS);
 out:
@@ -1782,13 +1837,19 @@ out:
 /**
  * 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);
-       return thaw_super_locked(sb);
+       return thaw_super_locked(sb, who);
 }
 EXPORT_SYMBOL(thaw_super);
 
index 6867512..5e8d812 100644 (file)
@@ -1147,7 +1147,8 @@ enum {
 #define SB_FREEZE_LEVELS (SB_FREEZE_COMPLETE - 1)
 
 struct sb_writers {
-       int                             frozen;         /* Is sb frozen? */
+       unsigned short                  frozen;         /* Is sb frozen? */
+       unsigned short                  freeze_holders; /* Who froze fs? */
        struct percpu_rw_semaphore      rw_sem[SB_FREEZE_LEVELS];
 };
 
@@ -1902,6 +1903,10 @@ extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
                                        struct file *dst_file, loff_t dst_pos,
                                        loff_t len, unsigned int remap_flags);
 
+enum freeze_holder {
+       FREEZE_HOLDER_KERNEL    = (1U << 0),
+       FREEZE_HOLDER_USERSPACE = (1U << 1),
+};
 
 struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);
@@ -1914,9 +1919,9 @@ struct super_operations {
        void (*evict_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        int (*sync_fs)(struct super_block *sb, int wait);
-       int (*freeze_super) (struct super_block *);
+       int (*freeze_super) (struct super_block *, enum freeze_holder who);
        int (*freeze_fs) (struct super_block *);
-       int (*thaw_super) (struct super_block *);
+       int (*thaw_super) (struct super_block *, enum freeze_holder who);
        int (*unfreeze_fs) (struct super_block *);
        int (*statfs) (struct dentry *, struct kstatfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
@@ -2290,8 +2295,8 @@ extern int unregister_filesystem(struct file_system_type *);
 extern int vfs_statfs(const struct path *, struct kstatfs *);
 extern int user_statfs(const char __user *, struct kstatfs *);
 extern int fd_statfs(int, struct kstatfs *);
-extern int freeze_super(struct super_block *super);
-extern int thaw_super(struct super_block *super);
+int freeze_super(struct super_block *super, enum freeze_holder who);
+int thaw_super(struct super_block *super, enum freeze_holder who);
 extern __printf(2, 3)
 int super_setup_bdi_name(struct super_block *sb, char *fmt, ...);
 extern int super_setup_bdi(struct super_block *sb);