mirror of
https://github.com/adulau/aha.git
synced 2024-12-28 03:36:19 +00:00
freeze_bdev: grab active reference to frozen superblocks
Currently we held s_umount while a filesystem is frozen, despite that we might return to userspace and unlock it from a different process. Instead grab an active reference to keep the file system busy and add an explicit check for frozen filesystems in remount and reject the remount instead of blocking on s_umount. Add a new get_active_super helper to super.c for use by freeze_bdev that grabs an active reference to a superblock from a given block device. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
4fadd7bb20
commit
4504230a71
3 changed files with 118 additions and 59 deletions
|
@ -230,16 +230,27 @@ struct super_block *freeze_bdev(struct block_device *bdev)
|
|||
int error = 0;
|
||||
|
||||
mutex_lock(&bdev->bd_fsfreeze_mutex);
|
||||
if (bdev->bd_fsfreeze_count > 0) {
|
||||
bdev->bd_fsfreeze_count++;
|
||||
if (++bdev->bd_fsfreeze_count > 1) {
|
||||
/*
|
||||
* We don't even need to grab a reference - the first call
|
||||
* to freeze_bdev grab an active reference and only the last
|
||||
* thaw_bdev drops it.
|
||||
*/
|
||||
sb = get_super(bdev);
|
||||
drop_super(sb);
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return sb;
|
||||
}
|
||||
|
||||
sb = get_active_super(bdev);
|
||||
if (!sb)
|
||||
goto out;
|
||||
if (sb->s_flags & MS_RDONLY) {
|
||||
deactivate_locked_super(sb);
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return sb;
|
||||
}
|
||||
bdev->bd_fsfreeze_count++;
|
||||
|
||||
sb = get_super(bdev);
|
||||
if (sb && !(sb->s_flags & MS_RDONLY)) {
|
||||
sb->s_frozen = SB_FREEZE_WRITE;
|
||||
smp_wmb();
|
||||
|
||||
|
@ -256,17 +267,17 @@ struct super_block *freeze_bdev(struct block_device *bdev)
|
|||
printk(KERN_ERR
|
||||
"VFS:Filesystem freeze failed\n");
|
||||
sb->s_frozen = SB_UNFROZEN;
|
||||
drop_super(sb);
|
||||
deactivate_locked_super(sb);
|
||||
bdev->bd_fsfreeze_count--;
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
up_write(&sb->s_umount);
|
||||
|
||||
out:
|
||||
sync_blockdev(bdev);
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
|
||||
return sb; /* thaw_bdev releases s->s_umount */
|
||||
}
|
||||
EXPORT_SYMBOL(freeze_bdev);
|
||||
|
@ -280,25 +291,24 @@ EXPORT_SYMBOL(freeze_bdev);
|
|||
*/
|
||||
int thaw_bdev(struct block_device *bdev, struct super_block *sb)
|
||||
{
|
||||
int error = 0;
|
||||
int error = -EINVAL;
|
||||
|
||||
mutex_lock(&bdev->bd_fsfreeze_mutex);
|
||||
if (!bdev->bd_fsfreeze_count) {
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!bdev->bd_fsfreeze_count)
|
||||
goto out_unlock;
|
||||
|
||||
bdev->bd_fsfreeze_count--;
|
||||
if (bdev->bd_fsfreeze_count > 0) {
|
||||
if (sb)
|
||||
drop_super(sb);
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return 0;
|
||||
}
|
||||
error = 0;
|
||||
if (--bdev->bd_fsfreeze_count > 0)
|
||||
goto out_unlock;
|
||||
|
||||
if (!sb)
|
||||
goto out_unlock;
|
||||
|
||||
if (sb) {
|
||||
BUG_ON(sb->s_bdev != bdev);
|
||||
if (!(sb->s_flags & MS_RDONLY)) {
|
||||
down_write(&sb->s_umount);
|
||||
if (sb->s_flags & MS_RDONLY)
|
||||
goto out_deactivate;
|
||||
|
||||
if (sb->s_op->unfreeze_fs) {
|
||||
error = sb->s_op->unfreeze_fs(sb);
|
||||
if (error) {
|
||||
|
@ -310,13 +320,15 @@ int thaw_bdev(struct block_device *bdev, struct super_block *sb)
|
|||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
sb->s_frozen = SB_UNFROZEN;
|
||||
smp_wmb();
|
||||
wake_up(&sb->s_wait_unfrozen);
|
||||
}
|
||||
drop_super(sb);
|
||||
}
|
||||
|
||||
out_deactivate:
|
||||
if (sb)
|
||||
deactivate_locked_super(sb);
|
||||
out_unlock:
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
|
46
fs/super.c
46
fs/super.c
|
@ -466,6 +466,48 @@ rescan:
|
|||
|
||||
EXPORT_SYMBOL(get_super);
|
||||
|
||||
/**
|
||||
* get_active_super - get an active reference to the superblock of a device
|
||||
* @bdev: device to get the superblock for
|
||||
*
|
||||
* Scans the superblock list and finds the superblock of the file system
|
||||
* mounted on the device given. Returns the superblock with an active
|
||||
* reference and s_umount held exclusively or %NULL if none was found.
|
||||
*/
|
||||
struct super_block *get_active_super(struct block_device *bdev)
|
||||
{
|
||||
struct super_block *sb;
|
||||
|
||||
if (!bdev)
|
||||
return NULL;
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
list_for_each_entry(sb, &super_blocks, s_list) {
|
||||
if (sb->s_bdev != bdev)
|
||||
continue;
|
||||
|
||||
sb->s_count++;
|
||||
spin_unlock(&sb_lock);
|
||||
down_write(&sb->s_umount);
|
||||
if (sb->s_root) {
|
||||
spin_lock(&sb_lock);
|
||||
if (sb->s_count > S_BIAS) {
|
||||
atomic_inc(&sb->s_active);
|
||||
sb->s_count--;
|
||||
spin_unlock(&sb_lock);
|
||||
return sb;
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
}
|
||||
up_write(&sb->s_umount);
|
||||
put_super(sb);
|
||||
yield();
|
||||
spin_lock(&sb_lock);
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct super_block * user_get_super(dev_t dev)
|
||||
{
|
||||
struct super_block *sb;
|
||||
|
@ -528,10 +570,14 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
|
|||
int retval;
|
||||
int remount_rw;
|
||||
|
||||
if (sb->s_frozen != SB_UNFROZEN)
|
||||
return -EBUSY;
|
||||
|
||||
#ifdef CONFIG_BLOCK
|
||||
if (!(flags & MS_RDONLY) && bdev_read_only(sb->s_bdev))
|
||||
return -EACCES;
|
||||
#endif
|
||||
|
||||
if (flags & MS_RDONLY)
|
||||
acct_auto_close(sb);
|
||||
shrink_dcache_sb(sb);
|
||||
|
|
|
@ -2334,6 +2334,7 @@ extern void get_filesystem(struct file_system_type *fs);
|
|||
extern void put_filesystem(struct file_system_type *fs);
|
||||
extern struct file_system_type *get_fs_type(const char *name);
|
||||
extern struct super_block *get_super(struct block_device *);
|
||||
extern struct super_block *get_active_super(struct block_device *bdev);
|
||||
extern struct super_block *user_get_super(dev_t);
|
||||
extern void drop_super(struct super_block *sb);
|
||||
|
||||
|
|
Loading…
Reference in a new issue