fs: distinguish between user initiated freeze and kernel initiated freeze

JIRA: https://issues.redhat.com/browse/RHEL-12888

Conflicts: diffs from out-of-order application plus leave out f2fs and
	   Documentation hunks.

commit 880b9577855edddda1e732748e849c63199d489b
Author: Darrick J. Wong <djwong@kernel.org>
Date:   Mon Jul 17 09:00:09 2023 -0700

    fs: distinguish between user initiated freeze and kernel initiated freeze

    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 18e9e5104f ("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>

Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
This commit is contained in:
Bill O'Donnell 2024-04-05 11:58:48 -05:00
parent 6e5032d556
commit 35a1a2b8b9
6 changed files with 98 additions and 29 deletions

View File

@ -231,9 +231,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) {
@ -274,9 +274,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

View File

@ -680,7 +680,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;
@ -688,7 +688,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;
}
}
@ -703,7 +705,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;
@ -752,7 +754,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;
@ -807,7 +809,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;

View 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;

View File

@ -599,8 +599,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)
@ -612,8 +612,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,

View 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);
@ -1101,7 +1101,7 @@ static void do_thaw_all_callback(struct super_block *sb)
if (born && sb->s_root) {
emergency_thaw_bdev(sb);
thaw_super_locked(sb);
thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE);
} else {
super_unlock_excl(sb);
}
@ -1805,11 +1805,22 @@ static void sb_freeze_unlock(struct super_block *sb)
/**
* 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.
@ -1835,12 +1846,30 @@ static void sb_freeze_unlock(struct super_block *sb)
*
* 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);
__super_lock_excl(sb);
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;
@ -1853,6 +1882,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;
super_unlock_excl(sb);
return 0;
@ -1891,6 +1921,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);
super_unlock_excl(sb);
@ -1898,16 +1929,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 {
super_unlock_excl(sb);
return -EINVAL;
}
if (sb_rdonly(sb)) {
sb->s_writers.freeze_holders &= ~who;
sb->s_writers.frozen = SB_UNFROZEN;
goto out;
}
@ -1925,6 +1979,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);
out:
@ -1937,11 +1992,18 @@ out:
* thaw_super -- unlock filesystem
* @sb: the super to thaw
*
* Unlocks the filesystem and marks it writeable again after freeze_super().
* @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.
*
* @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)
{
__super_lock_excl(sb);
return thaw_super_locked(sb);
return thaw_super_locked(sb, who);
}
EXPORT_SYMBOL(thaw_super);

View File

@ -1512,7 +1512,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? */
wait_queue_head_t wait_unfrozen; /* wait for thaw */
struct percpu_rw_semaphore rw_sem[SB_FREEZE_LEVELS];
};
@ -2209,6 +2210,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);
@ -2221,9 +2226,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 *);
@ -2608,8 +2613,8 @@ extern int iterate_mounts(int (*)(struct vfsmount *, void *), void *,
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 bool our_mnt(struct vfsmount *mnt);
extern __printf(2, 3)
int super_setup_bdi_name(struct super_block *sb, char *fmt, ...);