mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:09:30 +02:00
Merge tag 'vfs-7.3-rc1.super' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs superblock updates from Christian Brauner: - Make it possible to share a block device between multiple filesystems. erofs can mount read-only blob devices shared between many superblocks, but because we only tracked a single superblock a freeze, thaw, removal or sync on such a device was never propagated to all the superblocks using it, and there was no way to find them. Add an efficient table to lookup all superblocks using a given block device. - A bunch of pre-existing fixes fell out of this work: A block-device freeze racing a btrfs device change could leave the whole filesystem stuck frozen. A bdev_freeze() issued by "dmsetup suspend" or an LVM snapshot resolves that holder to freeze the filesystem. and bdev_thaw() resolves it again to thaw. A freeze landing while btrfs is adding, removing or replacing a device freezes the filesystem. The membership change then drops that link. So the matching thaw could no longer find the superblock. Forbid freezing a device for the duration of a membership change, modelled on deny_write_access()/allow_write_access(). * tag 'vfs-7.3-rc1.super' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits) super: fix dying superblock warning messages block: reject block device inodes with i_rdev == 0 in lookup_bdev() selftests/filesystems: add ustat() coverage fs: look up the superblock via the device table in user_get_super() super: make fs_holder_ops private f2fs: open via dedicated fs bdev helpers erofs: open via dedicated fs bdev helpers fs: tolerate per-superblock freeze errors on shared devices fs: look up superblocks via the device table in fs_holder_ops ext4: open via dedicated fs bdev helpers btrfs: open via dedicated fs bdev helpers xfs: port to fs_bdev_file_open_by_path() fs: add dedicated block device open helpers for filesystems fs: maintain a global device-to-superblock table ocfs2: don't reset s_dev on dismount ext4: use anonymous devices for KUnit test superblocks fs, block: move blk_mode_t and fop_flags_t into <linux/types.h> super: take lock after last reference count super: convert s_count to refcount_t s_passive btrfs: deny freezing devices undergoing a replace ...
This commit is contained in:
+99
-26
@@ -304,7 +304,12 @@ int bdev_freeze(struct block_device *bdev)
|
||||
|
||||
mutex_lock(&bdev->bd_fsfreeze_mutex);
|
||||
|
||||
if (atomic_inc_return(&bdev->bd_fsfreeze_count) > 1) {
|
||||
/* A device being removed from its filesystem refuses freezes. */
|
||||
if (!atomic_inc_unless_negative(&bdev->bd_fsfreeze_count)) {
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
if (atomic_read(&bdev->bd_fsfreeze_count) > 1) {
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return 0;
|
||||
}
|
||||
@@ -340,18 +345,18 @@ int bdev_thaw(struct block_device *bdev)
|
||||
|
||||
mutex_lock(&bdev->bd_fsfreeze_mutex);
|
||||
|
||||
/*
|
||||
* If this returns < 0 it means that @bd_fsfreeze_count was
|
||||
* already 0 and no decrement was performed.
|
||||
*/
|
||||
nr_freeze = atomic_dec_if_positive(&bdev->bd_fsfreeze_count);
|
||||
if (nr_freeze < 0)
|
||||
/* <= 0: not frozen (0) or a freeze deny is held (< 0); leave it. */
|
||||
nr_freeze = atomic_read(&bdev->bd_fsfreeze_count);
|
||||
if (nr_freeze <= 0)
|
||||
goto out;
|
||||
|
||||
error = 0;
|
||||
if (nr_freeze > 0)
|
||||
if (nr_freeze > 1) {
|
||||
atomic_dec(&bdev->bd_fsfreeze_count);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Keep the count positive across the thaw so a deny is refused. */
|
||||
mutex_lock(&bdev->bd_holder_lock);
|
||||
if (bdev->bd_holder_ops && bdev->bd_holder_ops->thaw) {
|
||||
error = bdev->bd_holder_ops->thaw(bdev);
|
||||
@@ -360,14 +365,52 @@ int bdev_thaw(struct block_device *bdev)
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
}
|
||||
|
||||
if (error)
|
||||
atomic_inc(&bdev->bd_fsfreeze_count);
|
||||
if (!error)
|
||||
atomic_dec(&bdev->bd_fsfreeze_count);
|
||||
out:
|
||||
mutex_unlock(&bdev->bd_fsfreeze_mutex);
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL(bdev_thaw);
|
||||
|
||||
/**
|
||||
* bdev_deny_freeze - make a block device unfreezable
|
||||
* @bdev: block device
|
||||
*
|
||||
* Reserve @bdev against bdev_freeze() the way deny_write_access() reserves a
|
||||
* file against writers. bd_fsfreeze_count is sign-encoded: > 0 counts active
|
||||
* freezes, < 0 counts deniers, so a deny succeeds only while no freeze is in
|
||||
* progress. While held, bdev_freeze() returns -EBUSY. Pair with
|
||||
* bdev_allow_freeze().
|
||||
*
|
||||
* A filesystem removing, adding or replacing a member device denies freezes on
|
||||
* it for the duration, so a claim a freeze walk might act on is never torn down
|
||||
* behind the freezer's back. The deny is device-scoped, not (device,
|
||||
* superblock)-scoped: a device shared by several superblocks is refused for all
|
||||
* of them. No in-tree filesystem removes a shared claim from a live superblock.
|
||||
*
|
||||
* Return: 0, or -EBUSY if the device is currently frozen.
|
||||
*/
|
||||
int bdev_deny_freeze(struct block_device *bdev)
|
||||
{
|
||||
return atomic_dec_unless_positive(&bdev->bd_fsfreeze_count) ? 0 : -EBUSY;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bdev_deny_freeze);
|
||||
|
||||
/**
|
||||
* bdev_allow_freeze - allow freezing a block device again
|
||||
* @bdev: block device
|
||||
*
|
||||
* Undo one bdev_deny_freeze().
|
||||
*/
|
||||
void bdev_allow_freeze(struct block_device *bdev)
|
||||
{
|
||||
/* A deny must be held, i.e. the count must be negative. */
|
||||
WARN_ON_ONCE(atomic_read(&bdev->bd_fsfreeze_count) >= 0);
|
||||
atomic_inc(&bdev->bd_fsfreeze_count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bdev_allow_freeze);
|
||||
|
||||
/*
|
||||
* pseudo-fs
|
||||
*/
|
||||
@@ -1152,6 +1195,39 @@ put_no_open:
|
||||
blkdev_put_no_open(bdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* bdev_yield_claim - give up the holder claim on an open block device
|
||||
* @bdev_file: open block device
|
||||
*
|
||||
* Yield the holder and any write access for @bdev_file without closing it, so
|
||||
* the caller can still act on the device - e.g. bdev_allow_freeze() it - before
|
||||
* the final bdev_fput(). bdev_fput() yields too, so calling it afterwards is
|
||||
* safe.
|
||||
*/
|
||||
void bdev_yield_claim(struct file *bdev_file)
|
||||
{
|
||||
struct block_device *bdev;
|
||||
struct gendisk *disk;
|
||||
|
||||
if (!bdev_file->private_data)
|
||||
return;
|
||||
|
||||
bdev = file_bdev(bdev_file);
|
||||
disk = bdev->bd_disk;
|
||||
|
||||
mutex_lock(&disk->open_mutex);
|
||||
bdev_yield_write_access(bdev_file);
|
||||
bd_yield_claim(bdev_file);
|
||||
/*
|
||||
* Tell release we already gave up our hold on the
|
||||
* device and if write restrictions are available that
|
||||
* we already gave up write access to the device.
|
||||
*/
|
||||
bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
|
||||
mutex_unlock(&disk->open_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bdev_yield_claim);
|
||||
|
||||
/**
|
||||
* bdev_fput - yield claim to the block device and put the file
|
||||
* @bdev_file: open block device
|
||||
@@ -1165,22 +1241,7 @@ void bdev_fput(struct file *bdev_file)
|
||||
if (WARN_ON_ONCE(bdev_file->f_op != &def_blk_fops))
|
||||
return;
|
||||
|
||||
if (bdev_file->private_data) {
|
||||
struct block_device *bdev = file_bdev(bdev_file);
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
|
||||
mutex_lock(&disk->open_mutex);
|
||||
bdev_yield_write_access(bdev_file);
|
||||
bd_yield_claim(bdev_file);
|
||||
/*
|
||||
* Tell release we already gave up our hold on the
|
||||
* device and if write restrictions are available that
|
||||
* we already gave up write access to the device.
|
||||
*/
|
||||
bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
|
||||
mutex_unlock(&disk->open_mutex);
|
||||
}
|
||||
|
||||
bdev_yield_claim(bdev_file);
|
||||
fput(bdev_file);
|
||||
}
|
||||
EXPORT_SYMBOL(bdev_fput);
|
||||
@@ -1217,6 +1278,18 @@ int lookup_bdev(const char *pathname, dev_t *dev)
|
||||
if (!may_open_dev(&path))
|
||||
goto out_path_put;
|
||||
|
||||
/*
|
||||
* Reject a block device inode with i_rdev == 0. A dev_t of 0 is
|
||||
* never valid for a block device: no real block device driver
|
||||
* registers major 0. Fake block device inodes (e.g. fuse with
|
||||
* rootmode=S_IFBLK) can expose i_rdev == 0, and letting that
|
||||
* propagate would confuse superblock lookup and trigger warnings
|
||||
* in the device-to-superblock table (super_dev_register).
|
||||
*/
|
||||
error = -ENODEV;
|
||||
if (!inode->i_rdev)
|
||||
goto out_path_put;
|
||||
|
||||
*dev = inode->i_rdev;
|
||||
error = 0;
|
||||
out_path_put:
|
||||
|
||||
+58
-7
@@ -247,8 +247,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE,
|
||||
fs_info->sb, &fs_holder_ops);
|
||||
/* Unfreezable for the whole replace; see btrfs_dev_replace_start(). */
|
||||
bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb);
|
||||
if (IS_ERR(bdev_file)) {
|
||||
btrfs_err(fs_info, "target device %s is invalid!", device_path);
|
||||
return PTR_ERR(bdev_file);
|
||||
@@ -327,7 +327,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
|
||||
return 0;
|
||||
|
||||
error:
|
||||
bdev_fput(bdev_file);
|
||||
/* Undo the open-time freeze deny. */
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -624,6 +625,15 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Deny the source before mark, so every 'leave' unwinds both denied. */
|
||||
if (src_device->bdev) {
|
||||
ret = bdev_deny_freeze(src_device->bdev);
|
||||
if (ret) {
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mark_block_group_to_copy(fs_info, src_device);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -708,7 +718,9 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info,
|
||||
return ret;
|
||||
|
||||
leave:
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
if (src_device->bdev)
|
||||
bdev_allow_freeze(src_device->bdev);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -889,6 +901,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
||||
*/
|
||||
ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false);
|
||||
if (ret) {
|
||||
/* Stays started/resumable; keep both denied. */
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
return ret;
|
||||
}
|
||||
@@ -902,6 +915,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
|
||||
while (1) {
|
||||
trans = btrfs_start_transaction(root, 0);
|
||||
if (IS_ERR(trans)) {
|
||||
/* Stays started/resumable; keep both denied. */
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
return PTR_ERR(trans);
|
||||
}
|
||||
@@ -954,7 +968,10 @@ error:
|
||||
mutex_unlock(&fs_devices->device_list_mutex);
|
||||
btrfs_rm_dev_replace_blocked(fs_info);
|
||||
if (tgt_device)
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, true);
|
||||
/* The source stays a member; re-allow freezing it. */
|
||||
if (src_device->bdev)
|
||||
bdev_allow_freeze(src_device->bdev);
|
||||
btrfs_rm_dev_replace_unblocked(fs_info);
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
|
||||
@@ -1027,6 +1044,8 @@ error:
|
||||
|
||||
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);
|
||||
|
||||
/* The target is now a member; the source is freed (allow + release). */
|
||||
bdev_allow_freeze(tgt_device->bdev);
|
||||
btrfs_rm_dev_replace_free_srcdev(src_device);
|
||||
|
||||
return 0;
|
||||
@@ -1155,8 +1174,9 @@ int btrfs_dev_replace_cancel(struct btrfs_fs_info *fs_info)
|
||||
btrfs_dev_name(src_device), src_device->devid,
|
||||
btrfs_dev_name(tgt_device));
|
||||
|
||||
/* A suspended replace never re-denied freezing; do not allow. */
|
||||
if (tgt_device)
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device);
|
||||
btrfs_destroy_dev_replace_tgtdev(tgt_device, false);
|
||||
break;
|
||||
default:
|
||||
up_write(&dev_replace->rwsem);
|
||||
@@ -1186,6 +1206,11 @@ void btrfs_dev_replace_suspend_for_unmount(struct btrfs_fs_info *fs_info)
|
||||
dev_replace->time_stopped = ktime_get_real_seconds();
|
||||
dev_replace->item_needs_writeback = 1;
|
||||
btrfs_info(fs_info, "suspending dev_replace for unmount");
|
||||
/* Reopened freezable next mount; resume re-denies. */
|
||||
if (dev_replace->srcdev && dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
if (dev_replace->tgtdev && dev_replace->tgtdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->tgtdev->bdev);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1198,6 +1223,7 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace;
|
||||
int ret = 0;
|
||||
|
||||
down_write(&dev_replace->rwsem);
|
||||
|
||||
@@ -1241,8 +1267,33 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Re-deny for the resumed replace; stay suspended if frozen now. */
|
||||
if (dev_replace->srcdev->bdev &&
|
||||
bdev_deny_freeze(dev_replace->srcdev->bdev))
|
||||
goto suspend;
|
||||
if (bdev_deny_freeze(dev_replace->tgtdev->bdev)) {
|
||||
if (dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
goto suspend;
|
||||
}
|
||||
|
||||
task = kthread_run(btrfs_dev_replace_kthread, fs_info, "btrfs-devrepl");
|
||||
return PTR_ERR_OR_ZERO(task);
|
||||
if (IS_ERR(task)) {
|
||||
bdev_allow_freeze(dev_replace->tgtdev->bdev);
|
||||
if (dev_replace->srcdev->bdev)
|
||||
bdev_allow_freeze(dev_replace->srcdev->bdev);
|
||||
/* Undo the deny and suspend, but still fail the mount. */
|
||||
ret = PTR_ERR(task);
|
||||
goto suspend;
|
||||
}
|
||||
return 0;
|
||||
|
||||
suspend:
|
||||
btrfs_exclop_finish(fs_info);
|
||||
down_write(&dev_replace->rwsem);
|
||||
dev_replace->replace_state = BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED;
|
||||
up_write(&dev_replace->rwsem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int btrfs_dev_replace_kthread(void *data)
|
||||
|
||||
+2
-2
@@ -2629,7 +2629,7 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg)
|
||||
err_drop:
|
||||
mnt_drop_write_file(file);
|
||||
if (bdev_file)
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
out:
|
||||
btrfs_put_dev_args_from_path(&args);
|
||||
return ret;
|
||||
@@ -2679,7 +2679,7 @@ static long btrfs_ioctl_rm_dev(struct file *file, void __user *arg)
|
||||
|
||||
mnt_drop_write_file(file);
|
||||
if (bdev_file)
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
out:
|
||||
btrfs_put_dev_args_from_path(&args);
|
||||
return ret;
|
||||
|
||||
+89
-16
@@ -481,7 +481,12 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder,
|
||||
struct block_device *bdev;
|
||||
int ret;
|
||||
|
||||
*bdev_file = bdev_file_open_by_path(device_path, flags, holder, &fs_holder_ops);
|
||||
if (holder)
|
||||
*bdev_file = fs_bdev_file_open_by_path(device_path, flags,
|
||||
holder, holder);
|
||||
else
|
||||
*bdev_file = bdev_file_open_by_path(device_path, flags, NULL,
|
||||
NULL);
|
||||
|
||||
if (IS_ERR(*bdev_file)) {
|
||||
ret = PTR_ERR(*bdev_file);
|
||||
@@ -496,7 +501,7 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder,
|
||||
if (holder) {
|
||||
ret = set_blocksize(*bdev_file, BTRFS_BDEV_BLOCKSIZE);
|
||||
if (ret) {
|
||||
bdev_fput(*bdev_file);
|
||||
fs_bdev_file_release(*bdev_file, holder);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -504,7 +509,10 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder,
|
||||
*disk_super = btrfs_read_disk_super(bdev, 0, false);
|
||||
if (IS_ERR(*disk_super)) {
|
||||
ret = PTR_ERR(*disk_super);
|
||||
bdev_fput(*bdev_file);
|
||||
if (holder)
|
||||
fs_bdev_file_release(*bdev_file, holder);
|
||||
else
|
||||
bdev_fput(*bdev_file);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -728,7 +736,7 @@ static int btrfs_open_one_device(struct btrfs_fs_devices *fs_devices,
|
||||
|
||||
error_free_page:
|
||||
btrfs_release_disk_super(disk_super);
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, holder);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1088,7 +1096,7 @@ static void __btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices,
|
||||
continue;
|
||||
|
||||
if (device->bdev_file) {
|
||||
bdev_fput(device->bdev_file);
|
||||
fs_bdev_file_release(device->bdev_file, device->bdev_file->private_data);
|
||||
device->bdev = NULL;
|
||||
device->bdev_file = NULL;
|
||||
fs_devices->open_devices--;
|
||||
@@ -1125,7 +1133,18 @@ void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices)
|
||||
mutex_unlock(&uuid_mutex);
|
||||
}
|
||||
|
||||
static void btrfs_close_bdev(struct btrfs_device *device)
|
||||
/* Release a device that was made unfreezable for a membership change. */
|
||||
void btrfs_release_device_allow_freeze(struct file *bdev_file)
|
||||
{
|
||||
struct super_block *sb = bdev_file->private_data;
|
||||
|
||||
/* Unregister before re-allowing (strand-safe); file still open (UAF-safe). */
|
||||
fs_bdev_unregister(bdev_file, sb);
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
bdev_fput(bdev_file);
|
||||
}
|
||||
|
||||
static void btrfs_close_bdev(struct btrfs_device *device, bool allow_freeze)
|
||||
{
|
||||
if (!device->bdev)
|
||||
return;
|
||||
@@ -1135,7 +1154,12 @@ static void btrfs_close_bdev(struct btrfs_device *device)
|
||||
invalidate_bdev(device->bdev);
|
||||
}
|
||||
|
||||
bdev_fput(device->bdev_file);
|
||||
/* @allow_freeze undoes a replace-time deny; unmount-close was never denied. */
|
||||
if (allow_freeze)
|
||||
btrfs_release_device_allow_freeze(device->bdev_file);
|
||||
else
|
||||
fs_bdev_file_release(device->bdev_file,
|
||||
device->bdev_file->private_data);
|
||||
}
|
||||
|
||||
static void btrfs_close_one_device(struct btrfs_device *device)
|
||||
@@ -1156,7 +1180,7 @@ static void btrfs_close_one_device(struct btrfs_device *device)
|
||||
fs_devices->missing_devices--;
|
||||
}
|
||||
|
||||
btrfs_close_bdev(device);
|
||||
btrfs_close_bdev(device, false);
|
||||
if (device->bdev) {
|
||||
fs_devices->open_devices--;
|
||||
device->bdev = NULL;
|
||||
@@ -2382,6 +2406,13 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
||||
fs_info->fs_devices->rw_devices == 1)
|
||||
return BTRFS_ERROR_DEV_ONLY_WRITABLE;
|
||||
|
||||
/* Removal and freezing are mutually exclusive; refuse if frozen now. */
|
||||
if (device->bdev) {
|
||||
ret = bdev_deny_freeze(device->bdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
|
||||
mutex_lock(&fs_info->chunk_mutex);
|
||||
list_del_init(&device->dev_alloc_list);
|
||||
@@ -2408,6 +2439,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
||||
device->devid, ret);
|
||||
btrfs_abort_transaction(trans, ret);
|
||||
btrfs_end_transaction(trans);
|
||||
if (device->bdev)
|
||||
bdev_allow_freeze(device->bdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2499,6 +2532,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
|
||||
return btrfs_commit_transaction(trans);
|
||||
|
||||
error_undo:
|
||||
if (device->bdev)
|
||||
bdev_allow_freeze(device->bdev);
|
||||
if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
|
||||
mutex_lock(&fs_info->chunk_mutex);
|
||||
list_add(&device->dev_alloc_list,
|
||||
@@ -2543,7 +2578,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev)
|
||||
|
||||
mutex_lock(&uuid_mutex);
|
||||
|
||||
btrfs_close_bdev(srcdev);
|
||||
/* The source was made unfreezable for the replace; undo it. */
|
||||
btrfs_close_bdev(srcdev, true);
|
||||
synchronize_rcu();
|
||||
btrfs_free_device(srcdev);
|
||||
|
||||
@@ -2564,7 +2600,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev)
|
||||
mutex_unlock(&uuid_mutex);
|
||||
}
|
||||
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev)
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev,
|
||||
bool allow_freeze)
|
||||
{
|
||||
struct btrfs_fs_devices *fs_devices = tgtdev->fs_info->fs_devices;
|
||||
|
||||
@@ -2585,7 +2622,7 @@ void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev)
|
||||
|
||||
btrfs_scratch_superblocks(tgtdev->fs_info, tgtdev);
|
||||
|
||||
btrfs_close_bdev(tgtdev);
|
||||
btrfs_close_bdev(tgtdev, allow_freeze);
|
||||
synchronize_rcu();
|
||||
btrfs_free_device(tgtdev);
|
||||
}
|
||||
@@ -2854,6 +2891,37 @@ next_slot:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open @path for @sb with freezing denied before the holder claim is published,
|
||||
* so a racing bdev_freeze() can never reach a claim a device add or replace may
|
||||
* still abort. The deny is taken on a throwaway non-holder probe open, then the
|
||||
* holder is opened by the probe's dev_t. Balanced by the caller.
|
||||
*/
|
||||
struct file *btrfs_open_device_deny_freeze(const char *path,
|
||||
struct super_block *sb)
|
||||
{
|
||||
struct file *probe_file, *bdev_file;
|
||||
int ret;
|
||||
|
||||
/* WRITE so bdev_file_open_by_path() rejects a read-only device. */
|
||||
probe_file = bdev_file_open_by_path(path, BLK_OPEN_WRITE, NULL, NULL);
|
||||
if (IS_ERR(probe_file))
|
||||
return probe_file;
|
||||
|
||||
ret = bdev_deny_freeze(file_bdev(probe_file));
|
||||
if (ret) {
|
||||
bdev_fput(probe_file);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
bdev_file = fs_bdev_file_open_by_dev(file_bdev(probe_file)->bd_dev,
|
||||
BLK_OPEN_WRITE, sb, sb);
|
||||
if (IS_ERR(bdev_file))
|
||||
bdev_allow_freeze(file_bdev(probe_file));
|
||||
bdev_fput(probe_file);
|
||||
return bdev_file;
|
||||
}
|
||||
|
||||
int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path)
|
||||
{
|
||||
struct btrfs_root *root = fs_info->dev_root;
|
||||
@@ -2872,8 +2940,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
||||
if (sb_rdonly(sb) && !fs_devices->seeding)
|
||||
return -EROFS;
|
||||
|
||||
bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE,
|
||||
fs_info->sb, &fs_holder_ops);
|
||||
/* Forbid freezing until the device is a committed member (or unwound). */
|
||||
bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb);
|
||||
if (IS_ERR(bdev_file))
|
||||
return PTR_ERR(bdev_file);
|
||||
|
||||
@@ -3044,8 +3112,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
||||
up_write(&sb->s_umount);
|
||||
locked = false;
|
||||
|
||||
if (ret) /* transaction commit */
|
||||
if (ret) { /* transaction commit */
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = btrfs_relocate_sys_chunks(fs_info);
|
||||
if (ret < 0)
|
||||
@@ -3053,8 +3123,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
||||
"Failed to relocate sys chunks after device initialization. This can be fixed using the \"btrfs balance\" command.");
|
||||
trans = btrfs_attach_transaction(root);
|
||||
if (IS_ERR(trans)) {
|
||||
if (PTR_ERR(trans) == -ENOENT)
|
||||
if (PTR_ERR(trans) == -ENOENT) {
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return 0;
|
||||
}
|
||||
ret = PTR_ERR(trans);
|
||||
trans = NULL;
|
||||
goto error_sysfs;
|
||||
@@ -3074,6 +3146,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
|
||||
/* Update ctime/mtime for blkid or udev */
|
||||
update_dev_time(device_path);
|
||||
|
||||
bdev_allow_freeze(file_bdev(bdev_file));
|
||||
return ret;
|
||||
|
||||
error_sysfs:
|
||||
@@ -3103,7 +3176,7 @@ error_free_zone:
|
||||
error_free_device:
|
||||
btrfs_free_device(device);
|
||||
error:
|
||||
bdev_fput(bdev_file);
|
||||
btrfs_release_device_allow_freeze(bdev_file);
|
||||
if (locked) {
|
||||
mutex_unlock(&uuid_mutex);
|
||||
up_write(&sb->s_umount);
|
||||
|
||||
+5
-1
@@ -744,6 +744,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
|
||||
struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev);
|
||||
int btrfs_forget_devices(dev_t devt);
|
||||
void btrfs_close_devices(struct btrfs_fs_devices *fs_devices);
|
||||
void btrfs_release_device_allow_freeze(struct file *bdev_file);
|
||||
void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices);
|
||||
void btrfs_assign_next_active_device(struct btrfs_device *device,
|
||||
struct btrfs_device *this_dev);
|
||||
@@ -768,6 +769,8 @@ struct btrfs_device *btrfs_find_device(const struct btrfs_fs_devices *fs_devices
|
||||
const struct btrfs_dev_lookup_args *args);
|
||||
int btrfs_shrink_device(struct btrfs_device *device, u64 new_size);
|
||||
int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *path);
|
||||
struct file *btrfs_open_device_deny_freeze(const char *path,
|
||||
struct super_block *sb);
|
||||
int btrfs_balance(struct btrfs_fs_info *fs_info,
|
||||
struct btrfs_balance_control *bctl,
|
||||
struct btrfs_ioctl_balance_args *bargs);
|
||||
@@ -788,7 +791,8 @@ int btrfs_init_writeback_bio_size(struct btrfs_fs_info *fs_info);
|
||||
int btrfs_run_dev_stats(struct btrfs_trans_handle *trans);
|
||||
void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev);
|
||||
void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev);
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev);
|
||||
void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev,
|
||||
bool allow_freeze);
|
||||
unsigned long btrfs_full_stripe_len(struct btrfs_fs_info *fs_info,
|
||||
u64 logical);
|
||||
u64 btrfs_calc_stripe_length(const struct btrfs_chunk_map *map);
|
||||
|
||||
+1
-1
@@ -504,7 +504,7 @@ static void cramfs_kill_sb(struct super_block *sb)
|
||||
sb->s_mtd = NULL;
|
||||
} else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV) && sb->s_bdev) {
|
||||
sync_blockdev(sb->s_bdev);
|
||||
bdev_fput(sb->s_bdev_file);
|
||||
fs_bdev_file_release(sb->s_bdev_file, sb);
|
||||
}
|
||||
kfree(sbi);
|
||||
}
|
||||
|
||||
+23
-12
@@ -147,8 +147,8 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb,
|
||||
if (!sbi->devs->flatdev) {
|
||||
file = erofs_is_fileio_mode(sbi) ?
|
||||
filp_open(dif->path, O_RDONLY | O_LARGEFILE, 0) :
|
||||
bdev_file_open_by_path(dif->path,
|
||||
BLK_OPEN_READ, sb->s_type, NULL);
|
||||
fs_bdev_file_open_by_path(dif->path,
|
||||
BLK_OPEN_READ, sb->s_type, sb);
|
||||
if (IS_ERR(file)) {
|
||||
if (file == ERR_PTR(-ENOTBLK))
|
||||
return -EINVAL;
|
||||
@@ -790,28 +790,34 @@ static int erofs_fc_reconfigure(struct fs_context *fc)
|
||||
|
||||
static int erofs_release_device_info(int id, void *ptr, void *data)
|
||||
{
|
||||
struct super_block *sb = data;
|
||||
struct erofs_device_info *dif = ptr;
|
||||
|
||||
fs_put_dax(dif->dax_dev, NULL);
|
||||
if (dif->file)
|
||||
fput(dif->file);
|
||||
if (dif->file) {
|
||||
if (S_ISBLK(file_inode(dif->file)->i_mode))
|
||||
fs_bdev_file_release(dif->file, sb);
|
||||
else
|
||||
fput(dif->file);
|
||||
}
|
||||
kfree(dif->path);
|
||||
kfree(dif);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void erofs_free_dev_context(struct erofs_dev_context *devs)
|
||||
static void erofs_free_dev_context(struct erofs_dev_context *devs,
|
||||
struct super_block *sb)
|
||||
{
|
||||
if (!devs)
|
||||
return;
|
||||
idr_for_each(&devs->tree, &erofs_release_device_info, NULL);
|
||||
idr_for_each(&devs->tree, &erofs_release_device_info, sb);
|
||||
idr_destroy(&devs->tree);
|
||||
kfree(devs);
|
||||
}
|
||||
|
||||
static void erofs_sb_free(struct erofs_sb_info *sbi)
|
||||
static void erofs_sb_free(struct erofs_sb_info *sbi, struct super_block *sb)
|
||||
{
|
||||
erofs_free_dev_context(sbi->devs);
|
||||
erofs_free_dev_context(sbi->devs, sb);
|
||||
kfree_sensitive(sbi->domain_id);
|
||||
if (sbi->dif0.file)
|
||||
fput(sbi->dif0.file);
|
||||
@@ -823,8 +829,13 @@ static void erofs_fc_free(struct fs_context *fc)
|
||||
{
|
||||
struct erofs_sb_info *sbi = fc->s_fs_info;
|
||||
|
||||
if (sbi) /* free here if an error occurs before transferring to sb */
|
||||
erofs_sb_free(sbi);
|
||||
/*
|
||||
* Freed here only if an error occurs before the sb is set up; at that
|
||||
* point no block-backed device has been claimed (that happens in
|
||||
* fill_super), so the NULL sb never reaches fs_bdev_file_release().
|
||||
*/
|
||||
if (sbi)
|
||||
erofs_sb_free(sbi, NULL);
|
||||
}
|
||||
|
||||
static const struct fs_context_operations erofs_context_ops = {
|
||||
@@ -878,7 +889,7 @@ static void erofs_kill_sb(struct super_block *sb)
|
||||
kill_block_super(sb);
|
||||
erofs_drop_internal_inodes(sbi);
|
||||
fs_put_dax(sbi->dif0.dax_dev, NULL);
|
||||
erofs_sb_free(sbi);
|
||||
erofs_sb_free(sbi, sb);
|
||||
sb->s_fs_info = NULL;
|
||||
}
|
||||
|
||||
@@ -890,7 +901,7 @@ static void erofs_put_super(struct super_block *sb)
|
||||
erofs_shrinker_unregister(sb);
|
||||
erofs_xattr_prefixes_cleanup(sb);
|
||||
erofs_drop_internal_inodes(sbi);
|
||||
erofs_free_dev_context(sbi->devs);
|
||||
erofs_free_dev_context(sbi->devs, sb);
|
||||
sbi->devs = NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,11 +126,6 @@ struct kunit_ext_test_param {
|
||||
struct kunit_ext_data_state exp_data_state[3];
|
||||
};
|
||||
|
||||
static void ext_kill_sb(struct super_block *sb)
|
||||
{
|
||||
generic_shutdown_super(sb);
|
||||
}
|
||||
|
||||
static int ext_init_fs_context(struct fs_context *fc)
|
||||
{
|
||||
return 0;
|
||||
@@ -138,13 +133,13 @@ static int ext_init_fs_context(struct fs_context *fc)
|
||||
|
||||
static int ext_set(struct super_block *sb, struct fs_context *fc)
|
||||
{
|
||||
return 0;
|
||||
return set_anon_super_fc(sb, fc);
|
||||
}
|
||||
|
||||
static struct file_system_type ext_fs_type = {
|
||||
.name = "extents test",
|
||||
.init_fs_context = ext_init_fs_context,
|
||||
.kill_sb = ext_kill_sb,
|
||||
.kill_sb = kill_anon_super,
|
||||
};
|
||||
|
||||
static void extents_kunit_exit(struct kunit *test)
|
||||
|
||||
@@ -59,11 +59,6 @@ static const struct super_operations mbt_sops = {
|
||||
.free_inode = mbt_free_inode,
|
||||
};
|
||||
|
||||
static void mbt_kill_sb(struct super_block *sb)
|
||||
{
|
||||
generic_shutdown_super(sb);
|
||||
}
|
||||
|
||||
static int mbt_init_fs_context(struct fs_context *fc)
|
||||
{
|
||||
return 0;
|
||||
@@ -72,7 +67,7 @@ static int mbt_init_fs_context(struct fs_context *fc)
|
||||
static struct file_system_type mbt_fs_type = {
|
||||
.name = "mballoc test",
|
||||
.init_fs_context = mbt_init_fs_context,
|
||||
.kill_sb = mbt_kill_sb,
|
||||
.kill_sb = kill_anon_super,
|
||||
};
|
||||
|
||||
static int mbt_mb_init(struct super_block *sb)
|
||||
@@ -136,7 +131,7 @@ static void mbt_mb_release(struct super_block *sb)
|
||||
|
||||
static int mbt_set(struct super_block *sb, struct fs_context *fc)
|
||||
{
|
||||
return 0;
|
||||
return set_anon_super_fc(sb, fc);
|
||||
}
|
||||
|
||||
static struct super_block *mbt_ext4_alloc_super_block(void)
|
||||
|
||||
+6
-6
@@ -5797,7 +5797,7 @@ failed_mount:
|
||||
brelse(sbi->s_sbh);
|
||||
if (sbi->s_journal_bdev_file) {
|
||||
invalidate_bdev(file_bdev(sbi->s_journal_bdev_file));
|
||||
bdev_fput(sbi->s_journal_bdev_file);
|
||||
fs_bdev_file_release(sbi->s_journal_bdev_file, sb);
|
||||
}
|
||||
out_fail:
|
||||
invalidate_bdev(sb->s_bdev);
|
||||
@@ -5981,9 +5981,9 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb,
|
||||
struct ext4_super_block *es;
|
||||
int errno;
|
||||
|
||||
bdev_file = bdev_file_open_by_dev(j_dev,
|
||||
bdev_file = fs_bdev_file_open_by_dev(j_dev,
|
||||
BLK_OPEN_READ | BLK_OPEN_WRITE | BLK_OPEN_RESTRICT_WRITES,
|
||||
sb, &fs_holder_ops);
|
||||
sb, sb);
|
||||
if (IS_ERR(bdev_file)) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"failed to open journal device unknown-block(%u,%u) %pe",
|
||||
@@ -6043,7 +6043,7 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb,
|
||||
out_bh:
|
||||
brelse(bh);
|
||||
out_bdev:
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, sb);
|
||||
return ERR_PTR(errno);
|
||||
}
|
||||
|
||||
@@ -6082,7 +6082,7 @@ static journal_t *ext4_open_dev_journal(struct super_block *sb,
|
||||
out_journal:
|
||||
ext4_journal_destroy(EXT4_SB(sb), journal);
|
||||
out_bdev:
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, sb);
|
||||
return ERR_PTR(errno);
|
||||
}
|
||||
|
||||
@@ -7499,7 +7499,7 @@ static void ext4_kill_sb(struct super_block *sb)
|
||||
kill_block_super(sb);
|
||||
|
||||
if (bdev_file)
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, sb);
|
||||
}
|
||||
|
||||
static struct file_system_type ext4_fs_type = {
|
||||
|
||||
+3
-3
@@ -1971,7 +1971,7 @@ static void destroy_device_list(struct f2fs_sb_info *sbi)
|
||||
|
||||
for (i = 0; i < sbi->s_ndevs; i++) {
|
||||
if (i > 0)
|
||||
bdev_fput(FDEV(i).bdev_file);
|
||||
fs_bdev_file_release(FDEV(i).bdev_file, sbi->sb);
|
||||
#ifdef CONFIG_BLK_DEV_ZONED
|
||||
kvfree(FDEV(i).blkz_seq);
|
||||
#endif
|
||||
@@ -4901,8 +4901,8 @@ static int f2fs_scan_devices(struct f2fs_sb_info *sbi)
|
||||
FDEV(i).end_blk = FDEV(i).start_blk +
|
||||
SEGS_TO_BLKS(sbi,
|
||||
FDEV(i).total_segments) - 1;
|
||||
FDEV(i).bdev_file = bdev_file_open_by_path(
|
||||
FDEV(i).path, mode, sbi->sb, NULL);
|
||||
FDEV(i).bdev_file = fs_bdev_file_open_by_path(
|
||||
FDEV(i).path, mode, sbi->sb, sbi->sb);
|
||||
}
|
||||
}
|
||||
if (IS_ERR(FDEV(i).bdev_file))
|
||||
|
||||
@@ -137,6 +137,7 @@ extern int reconfigure_super(struct fs_context *);
|
||||
extern bool super_trylock_shared(struct super_block *sb);
|
||||
struct super_block *user_get_super(dev_t, bool excl);
|
||||
void put_super(struct super_block *sb);
|
||||
void __init super_dev_init(void);
|
||||
extern bool mount_capable(struct fs_context *);
|
||||
|
||||
/*
|
||||
|
||||
@@ -6270,6 +6270,8 @@ void __init mnt_init(void)
|
||||
HASH_ZERO,
|
||||
&mp_hash_shift, &mp_hash_mask, 0, 0);
|
||||
|
||||
super_dev_init();
|
||||
|
||||
kernfs_init();
|
||||
|
||||
err = sysfs_init();
|
||||
|
||||
@@ -1882,7 +1882,6 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err)
|
||||
|
||||
ocfs2_delete_osb(osb);
|
||||
kfree(osb);
|
||||
sb->s_dev = 0;
|
||||
sb->s_fs_info = NULL;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -595,7 +595,7 @@ static void romfs_kill_sb(struct super_block *sb)
|
||||
#ifdef CONFIG_ROMFS_ON_BLOCK
|
||||
if (sb->s_bdev) {
|
||||
sync_blockdev(sb->s_bdev);
|
||||
bdev_fput(sb->s_bdev_file);
|
||||
fs_bdev_file_release(sb->s_bdev_file, sb);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+428
-196
@@ -25,6 +25,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/writeback.h> /* for the emergency remount stuff */
|
||||
@@ -103,7 +104,7 @@ static bool super_flags(const struct super_block *sb, unsigned int flags)
|
||||
* creation will succeed and SB_BORN is set by vfs_get_tree() or we're
|
||||
* woken and we'll see SB_DYING.
|
||||
*
|
||||
* The caller must have acquired a temporary reference on @sb->s_count.
|
||||
* The caller must have acquired a temporary reference on @sb->s_passive.
|
||||
*
|
||||
* Return: The function returns true if SB_BORN was set and with
|
||||
* s_umount held. The function returns false if SB_DYING was
|
||||
@@ -287,6 +288,8 @@ static unsigned long super_cache_count(struct shrinker *shrink,
|
||||
return total_objects;
|
||||
}
|
||||
|
||||
static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb);
|
||||
|
||||
static void destroy_super_work(struct work_struct *work)
|
||||
{
|
||||
struct super_block *s = container_of(work, struct super_block,
|
||||
@@ -294,6 +297,8 @@ static void destroy_super_work(struct work_struct *work)
|
||||
fsnotify_sb_free(s);
|
||||
security_sb_free(s);
|
||||
put_user_ns(s->s_user_ns);
|
||||
/* Only an unregistered entry is still owned by the superblock. */
|
||||
kfree(s->s_super_dev);
|
||||
kfree(s->s_subtype);
|
||||
for (int i = 0; i < SB_FREEZE_LEVELS; i++)
|
||||
percpu_free_rwsem(&s->s_writers.rw_sem[i]);
|
||||
@@ -382,7 +387,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
||||
spin_lock_init(&s->s_inode_wblist_lock);
|
||||
fserror_mount(s);
|
||||
|
||||
s->s_count = 1;
|
||||
refcount_set(&s->s_passive, 1);
|
||||
atomic_set(&s->s_active, 1);
|
||||
mutex_init(&s->s_vfs_rename_mutex);
|
||||
lockdep_set_class(&s->s_vfs_rename_mutex, &type->s_vfs_rename_key);
|
||||
@@ -407,6 +412,10 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
||||
goto fail;
|
||||
if (list_lru_init_memcg(&s->s_inode_lru, s->s_shrink))
|
||||
goto fail;
|
||||
s->s_super_dev = super_dev_alloc(0, s);
|
||||
if (!s->s_super_dev)
|
||||
goto fail;
|
||||
|
||||
s->s_min_writeback_pages = MIN_WRITEBACK_PAGES;
|
||||
return s;
|
||||
|
||||
@@ -418,12 +427,17 @@ fail:
|
||||
/* Superblock refcounting */
|
||||
|
||||
/*
|
||||
* Drop a superblock's refcount. The caller must hold sb_lock.
|
||||
* Drop a superblock's passive reference. Must be called WITHOUT sb_lock held;
|
||||
* put_super() acquires sb_lock itself when the final reference is dropped.
|
||||
*/
|
||||
static void __put_super(struct super_block *s)
|
||||
void put_super(struct super_block *s)
|
||||
{
|
||||
if (!--s->s_count) {
|
||||
if (refcount_dec_and_test(&s->s_passive)) {
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
list_del_init(&s->s_list);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
WARN_ON(s->s_dentry_lru.node);
|
||||
WARN_ON(s->s_inode_lru.node);
|
||||
WARN_ON(s->s_mounts);
|
||||
@@ -431,18 +445,109 @@ static void __put_super(struct super_block *s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* put_super - drop a temporary reference to superblock
|
||||
* @sb: superblock in question
|
||||
*
|
||||
* Drops a temporary reference, frees superblock if there's no
|
||||
* references left.
|
||||
*/
|
||||
void put_super(struct super_block *sb)
|
||||
struct super_dev {
|
||||
dev_t sd_dev;
|
||||
struct super_block *sd_sb;
|
||||
refcount_t sd_ref;
|
||||
struct rhlist_head sd_node;
|
||||
struct rcu_head sd_rcu;
|
||||
};
|
||||
|
||||
static struct rhltable super_dev_table;
|
||||
static const struct rhashtable_params super_dev_params = {
|
||||
.key_len = sizeof(dev_t),
|
||||
.key_offset = offsetof(struct super_dev, sd_dev),
|
||||
.head_offset = offsetof(struct super_dev, sd_node),
|
||||
};
|
||||
|
||||
static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb)
|
||||
{
|
||||
spin_lock(&sb_lock);
|
||||
__put_super(sb);
|
||||
spin_unlock(&sb_lock);
|
||||
struct super_dev *fsd;
|
||||
|
||||
fsd = kzalloc_obj(*fsd);
|
||||
if (!fsd)
|
||||
return NULL;
|
||||
fsd->sd_dev = dev;
|
||||
fsd->sd_sb = sb;
|
||||
refcount_set(&fsd->sd_ref, 1);
|
||||
return fsd;
|
||||
}
|
||||
|
||||
static void super_dev_put(struct super_dev *fsd)
|
||||
{
|
||||
/* Unlink only once unpinned, so a cursor never resumes from a removed node. */
|
||||
if (fsd && refcount_dec_and_test(&fsd->sd_ref)) {
|
||||
rhltable_remove(&super_dev_table, &fsd->sd_node, super_dev_params);
|
||||
put_super(fsd->sd_sb);
|
||||
kfree_rcu(fsd, sd_rcu);
|
||||
}
|
||||
}
|
||||
|
||||
void __init super_dev_init(void)
|
||||
{
|
||||
if (rhltable_init(&super_dev_table, &super_dev_params))
|
||||
panic("VFS: Cannot initialise super_dev_table\n");
|
||||
}
|
||||
|
||||
static int super_dev_insert(struct super_dev *fsd)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = rhltable_insert(&super_dev_table, &fsd->sd_node, super_dev_params);
|
||||
if (!err)
|
||||
refcount_inc(&fsd->sd_sb->s_passive);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Register @sb under @sb->s_dev as the final fallible act of a set callback. */
|
||||
static int super_dev_register(struct super_block *sb)
|
||||
{
|
||||
struct super_dev *fsd = sb->s_super_dev;
|
||||
int err;
|
||||
|
||||
lockdep_assert_held(&sb_lock);
|
||||
VFS_WARN_ON_ONCE(!sb->s_dev);
|
||||
VFS_WARN_ON_ONCE(!fsd || fsd->sd_dev);
|
||||
|
||||
fsd->sd_dev = sb->s_dev;
|
||||
err = super_dev_insert(fsd);
|
||||
if (err)
|
||||
fsd->sd_dev = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct super_dev *super_dev_get(struct rhlist_head *pos)
|
||||
{
|
||||
struct super_dev *sb_dev;
|
||||
|
||||
for (; pos; pos = rcu_dereference_all(pos->next)) {
|
||||
sb_dev = container_of(pos, struct super_dev, sd_node);
|
||||
if (refcount_inc_not_zero(&sb_dev->sd_ref))
|
||||
return sb_dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct super_dev *super_dev_first(dev_t dev)
|
||||
{
|
||||
struct super_dev *sb_dev;
|
||||
|
||||
rcu_read_lock();
|
||||
sb_dev = super_dev_get(rhltable_lookup(&super_dev_table, &dev, super_dev_params));
|
||||
rcu_read_unlock();
|
||||
return sb_dev;
|
||||
}
|
||||
|
||||
static struct super_dev *super_dev_next(struct super_dev *prev)
|
||||
{
|
||||
struct super_dev *sb_dev;
|
||||
|
||||
rcu_read_lock();
|
||||
sb_dev = super_dev_get(rcu_dereference_all(prev->sd_node.next));
|
||||
rcu_read_unlock();
|
||||
|
||||
super_dev_put(prev);
|
||||
return sb_dev;
|
||||
}
|
||||
|
||||
static void kill_super_notify(struct super_block *sb)
|
||||
@@ -464,6 +569,12 @@ static void kill_super_notify(struct super_block *sb)
|
||||
hlist_del_init(&sb->s_instances);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
/* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */
|
||||
if (sb->s_super_dev->sd_dev) {
|
||||
super_dev_put(sb->s_super_dev);
|
||||
sb->s_super_dev = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let concurrent mounts know that this thing is really dead.
|
||||
* We don't need @sb->s_umount here as every concurrent caller
|
||||
@@ -493,11 +604,7 @@ void deactivate_locked_super(struct super_block *s)
|
||||
|
||||
kill_super_notify(s);
|
||||
|
||||
/*
|
||||
* Since list_lru_destroy() may sleep, we cannot call it from
|
||||
* put_super(), where we hold the sb_lock. Therefore we destroy
|
||||
* the lru lists right now.
|
||||
*/
|
||||
/* list_lru_destroy() may sleep; put_super() callers may not. */
|
||||
list_lru_destroy(&s->s_dentry_lru);
|
||||
list_lru_destroy(&s->s_inode_lru);
|
||||
|
||||
@@ -544,7 +651,7 @@ static bool grab_super(struct super_block *sb)
|
||||
{
|
||||
bool locked;
|
||||
|
||||
sb->s_count++;
|
||||
refcount_inc(&sb->s_passive);
|
||||
spin_unlock(&sb_lock);
|
||||
locked = super_lock_excl(sb);
|
||||
if (locked) {
|
||||
@@ -571,7 +678,7 @@ static bool grab_super(struct super_block *sb)
|
||||
* lock held in read mode in case of success. On successful return,
|
||||
* the caller must drop the s_umount lock when done.
|
||||
*
|
||||
* Note that unlike get_super() et.al. this one does *not* bump ->s_count.
|
||||
* Note that unlike get_super() et.al. this one does *not* bump ->s_passive.
|
||||
* The reason why it's safe is that we are OK with doing trylock instead
|
||||
* of down_read(). There's a couple of places that are OK with that, but
|
||||
* it's very much not a general-purpose interface.
|
||||
@@ -778,6 +885,7 @@ retry:
|
||||
}
|
||||
if (!s) {
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
s = alloc_super(fc->fs_type, fc->sb_flags, user_ns);
|
||||
if (!s)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
@@ -787,11 +895,13 @@ retry:
|
||||
s->s_fs_info = fc->s_fs_info;
|
||||
err = set(s, fc);
|
||||
if (err) {
|
||||
VFS_WARN_ON_ONCE(s->s_super_dev->sd_dev);
|
||||
s->s_fs_info = NULL;
|
||||
spin_unlock(&sb_lock);
|
||||
destroy_unused_super(s);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
VFS_WARN_ON_ONCE(!s->s_super_dev->sd_dev);
|
||||
fc->s_fs_info = NULL;
|
||||
s->s_type = fc->fs_type;
|
||||
s->s_iflags |= fc->s_iflags;
|
||||
@@ -866,14 +976,17 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
|
||||
struct super_block *sb, *p = NULL;
|
||||
bool excl = flags & SUPER_ITER_EXCL;
|
||||
|
||||
guard(spinlock)(&sb_lock);
|
||||
spin_lock(&sb_lock);
|
||||
|
||||
for (sb = first_super(flags);
|
||||
!list_entry_is_head(sb, &super_blocks, s_list);
|
||||
sb = next_super(sb, flags)) {
|
||||
if (super_flags(sb, SB_DYING))
|
||||
continue;
|
||||
sb->s_count++;
|
||||
|
||||
if (!refcount_inc_not_zero(&sb->s_passive))
|
||||
continue;
|
||||
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
if (flags & SUPER_ITER_UNLOCKED) {
|
||||
@@ -883,13 +996,14 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg,
|
||||
super_unlock(sb, excl);
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
p = sb;
|
||||
spin_lock(&sb_lock);
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
}
|
||||
|
||||
void iterate_supers(void (*f)(struct super_block *, void *), void *arg)
|
||||
@@ -918,7 +1032,9 @@ void iterate_supers_type(struct file_system_type *type,
|
||||
if (super_flags(sb, SB_DYING))
|
||||
continue;
|
||||
|
||||
sb->s_count++;
|
||||
if (!refcount_inc_not_zero(&sb->s_passive))
|
||||
continue;
|
||||
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
locked = super_lock_shared(sb);
|
||||
@@ -927,41 +1043,33 @@ void iterate_supers_type(struct file_system_type *type,
|
||||
super_unlock_shared(sb);
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
if (p)
|
||||
__put_super(p);
|
||||
put_super(p);
|
||||
p = sb;
|
||||
spin_lock(&sb_lock);
|
||||
}
|
||||
if (p)
|
||||
__put_super(p);
|
||||
spin_unlock(&sb_lock);
|
||||
if (p)
|
||||
put_super(p);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(iterate_supers_type);
|
||||
|
||||
struct super_block *user_get_super(dev_t dev, bool excl)
|
||||
{
|
||||
struct super_block *sb;
|
||||
struct super_dev *sb_dev;
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
list_for_each_entry(sb, &super_blocks, s_list) {
|
||||
bool locked;
|
||||
for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) {
|
||||
struct super_block *sb = sb_dev->sd_sb;
|
||||
|
||||
if (sb->s_dev != dev)
|
||||
if (!super_lock(sb, excl))
|
||||
continue;
|
||||
|
||||
sb->s_count++;
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
locked = super_lock(sb, excl);
|
||||
if (locked)
|
||||
return sb;
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
__put_super(sb);
|
||||
break;
|
||||
/* The pinned entry holds a passive reference, take our own. */
|
||||
refcount_inc(&sb->s_passive);
|
||||
super_dev_put(sb_dev);
|
||||
return sb;
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1243,7 +1351,16 @@ EXPORT_SYMBOL(free_anon_bdev);
|
||||
|
||||
int set_anon_super(struct super_block *s, void *data)
|
||||
{
|
||||
return get_anon_bdev(&s->s_dev);
|
||||
int error;
|
||||
|
||||
error = get_anon_bdev(&s->s_dev);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = super_dev_register(s);
|
||||
if (error)
|
||||
free_anon_bdev(s->s_dev);
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL(set_anon_super);
|
||||
|
||||
@@ -1329,7 +1446,7 @@ EXPORT_SYMBOL(get_tree_keyed);
|
||||
static int set_bdev_super(struct super_block *s, void *data)
|
||||
{
|
||||
s->s_dev = *(dev_t *)data;
|
||||
return 0;
|
||||
return super_dev_register(s);
|
||||
}
|
||||
|
||||
static int super_s_dev_set(struct super_block *s, struct fs_context *fc)
|
||||
@@ -1371,197 +1488,313 @@ struct super_block *sget_dev(struct fs_context *fc, dev_t dev)
|
||||
EXPORT_SYMBOL(sget_dev);
|
||||
|
||||
#ifdef CONFIG_BLOCK
|
||||
/*
|
||||
* Lock the superblock that is holder of the bdev. Returns the superblock
|
||||
* pointer if we successfully locked the superblock and it is alive. Otherwise
|
||||
* we return NULL and just unlock bdev->bd_holder_lock.
|
||||
*
|
||||
* The function must be called with bdev->bd_holder_lock and releases it.
|
||||
*/
|
||||
static struct super_block *bdev_super_lock(struct block_device *bdev, bool excl)
|
||||
__releases(&bdev->bd_holder_lock)
|
||||
static int fs_super_freeze(struct super_block *sb)
|
||||
{
|
||||
struct super_block *sb = bdev->bd_holder;
|
||||
bool locked;
|
||||
if (sb->s_op->freeze_super)
|
||||
return sb->s_op->freeze_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
return freeze_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
}
|
||||
|
||||
lockdep_assert_held(&bdev->bd_holder_lock);
|
||||
lockdep_assert_not_held(&sb->s_umount);
|
||||
lockdep_assert_not_held(&bdev->bd_disk->open_mutex);
|
||||
|
||||
/* Make sure sb doesn't go away from under us */
|
||||
spin_lock(&sb_lock);
|
||||
sb->s_count++;
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
locked = super_lock(sb, excl);
|
||||
|
||||
/*
|
||||
* If the superblock wasn't already SB_DYING then we hold
|
||||
* s_umount and can safely drop our temporary reference.
|
||||
*/
|
||||
put_super(sb);
|
||||
|
||||
if (!locked)
|
||||
return NULL;
|
||||
|
||||
if (!sb->s_root || !(sb->s_flags & SB_ACTIVE)) {
|
||||
super_unlock(sb, excl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sb;
|
||||
static int fs_super_thaw(struct super_block *sb)
|
||||
{
|
||||
if (sb->s_op->thaw_super)
|
||||
return sb->s_op->thaw_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
return thaw_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
}
|
||||
|
||||
static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise)
|
||||
{
|
||||
struct super_block *sb;
|
||||
struct super_dev *sb_dev;
|
||||
dev_t dev = bdev->bd_dev;
|
||||
|
||||
sb = bdev_super_lock(bdev, false);
|
||||
if (!sb)
|
||||
return;
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
if (sb->s_op->remove_bdev) {
|
||||
int ret;
|
||||
for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) {
|
||||
struct super_block *sb = sb_dev->sd_sb;
|
||||
|
||||
ret = sb->s_op->remove_bdev(sb, bdev);
|
||||
if (!ret) {
|
||||
super_unlock_shared(sb);
|
||||
return;
|
||||
if (!super_lock_shared(sb))
|
||||
continue;
|
||||
if (sb->s_root && (sb->s_flags & SB_ACTIVE)) {
|
||||
if (!sb->s_op->remove_bdev ||
|
||||
sb->s_op->remove_bdev(sb, bdev)) {
|
||||
if (!surprise)
|
||||
sync_filesystem(sb);
|
||||
shrink_dcache_sb(sb);
|
||||
evict_inodes(sb);
|
||||
if (sb->s_op->shutdown)
|
||||
sb->s_op->shutdown(sb);
|
||||
}
|
||||
}
|
||||
/* Fallback to shutdown. */
|
||||
super_unlock_shared(sb);
|
||||
}
|
||||
|
||||
if (!surprise)
|
||||
sync_filesystem(sb);
|
||||
shrink_dcache_sb(sb);
|
||||
evict_inodes(sb);
|
||||
if (sb->s_op->shutdown)
|
||||
sb->s_op->shutdown(sb);
|
||||
|
||||
super_unlock_shared(sb);
|
||||
}
|
||||
|
||||
static void fs_bdev_sync(struct block_device *bdev)
|
||||
{
|
||||
struct super_block *sb;
|
||||
struct super_dev *sb_dev;
|
||||
dev_t dev = bdev->bd_dev;
|
||||
|
||||
sb = bdev_super_lock(bdev, false);
|
||||
if (!sb)
|
||||
return;
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
sync_filesystem(sb);
|
||||
super_unlock_shared(sb);
|
||||
}
|
||||
for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) {
|
||||
struct super_block *sb = sb_dev->sd_sb;
|
||||
|
||||
static struct super_block *get_bdev_super(struct block_device *bdev)
|
||||
{
|
||||
bool active = false;
|
||||
struct super_block *sb;
|
||||
|
||||
sb = bdev_super_lock(bdev, true);
|
||||
if (sb) {
|
||||
active = atomic_inc_not_zero(&sb->s_active);
|
||||
super_unlock_excl(sb);
|
||||
if (!super_lock_shared(sb))
|
||||
continue;
|
||||
if (sb->s_root && (sb->s_flags & SB_ACTIVE))
|
||||
sync_filesystem(sb);
|
||||
super_unlock_shared(sb);
|
||||
}
|
||||
if (!active)
|
||||
return NULL;
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* fs_bdev_freeze - freeze owning filesystem of block device
|
||||
* fs_bdev_freeze - freeze every superblock using a block device
|
||||
* @bdev: block device
|
||||
*
|
||||
* Freeze the filesystem that owns this block device if it is still
|
||||
* active.
|
||||
* Freeze each live superblock using @bdev. A superblock owning several block
|
||||
* devices is frozen once per device and stays frozen until all are thawed; the
|
||||
* block layer nests these freezes so the count stays balanced.
|
||||
*
|
||||
* A filesystem that owns multiple block devices may be frozen from each
|
||||
* block device and won't be unfrozen until all block devices are
|
||||
* unfrozen. Each block device can only freeze the filesystem once as we
|
||||
* nest freezes for block devices in the block layer.
|
||||
*
|
||||
* Return: If the freeze was successful zero is returned. If the freeze
|
||||
* failed a negative error code is returned.
|
||||
* Return: 0, or the error from the one superblock on a single-fs device. When
|
||||
* several superblocks share @bdev a per-superblock failure is swallowed
|
||||
* (see below), but a sync_blockdev() failure is always reported.
|
||||
*/
|
||||
static int fs_bdev_freeze(struct block_device *bdev)
|
||||
{
|
||||
struct super_block *sb;
|
||||
int error = 0;
|
||||
dev_t dev = bdev->bd_dev;
|
||||
struct super_dev *sb_dev;
|
||||
unsigned int count = 0;
|
||||
int error = 0, err;
|
||||
|
||||
lockdep_assert_held(&bdev->bd_fsfreeze_mutex);
|
||||
|
||||
sb = get_bdev_super(bdev);
|
||||
if (!sb)
|
||||
return -EINVAL;
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
if (sb->s_op->freeze_super)
|
||||
error = sb->s_op->freeze_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
else
|
||||
error = freeze_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) {
|
||||
if (!get_active_super(sb_dev->sd_sb))
|
||||
continue;
|
||||
err = fs_super_freeze(sb_dev->sd_sb);
|
||||
if (err && !error)
|
||||
error = err;
|
||||
deactivate_super(sb_dev->sd_sb);
|
||||
count++;
|
||||
}
|
||||
|
||||
/*
|
||||
* When several superblocks share the device, keep it frozen even if some
|
||||
* of them failed to freeze and swallow the error: rolling the rest back
|
||||
* via thaw_super() can fail too, so neither is a clear win. A single
|
||||
* filesystem (count == 1) still reports its error.
|
||||
*/
|
||||
if (error && count > 1)
|
||||
error = 0;
|
||||
if (!error)
|
||||
error = sync_blockdev(bdev);
|
||||
deactivate_super(sb);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* fs_bdev_thaw - thaw owning filesystem of block device
|
||||
* fs_bdev_thaw - thaw every superblock using a block device
|
||||
* @bdev: block device
|
||||
*
|
||||
* Thaw the filesystem that owns this block device.
|
||||
* The counterpart to fs_bdev_freeze(): thaw each live superblock using @bdev.
|
||||
* A zero return does not imply a superblock is fully unfrozen; it may have been
|
||||
* frozen more than once (by the kernel or via another device).
|
||||
*
|
||||
* A filesystem that owns multiple block devices may be frozen from each
|
||||
* block device and won't be unfrozen until all block devices are
|
||||
* unfrozen. Each block device can only freeze the filesystem once as we
|
||||
* nest freezes for block devices in the block layer.
|
||||
*
|
||||
* Return: If the thaw was successful zero is returned. If the thaw
|
||||
* failed a negative error code is returned. If this function
|
||||
* returns zero it doesn't mean that the filesystem is unfrozen
|
||||
* as it may have been frozen multiple times (kernel may hold a
|
||||
* freeze or might be frozen from other block devices).
|
||||
* Return: 0, or the first error on a single-fs device; a shared device swallows
|
||||
* per-superblock errors, as fs_bdev_freeze() does.
|
||||
*/
|
||||
static int fs_bdev_thaw(struct block_device *bdev)
|
||||
{
|
||||
struct super_block *sb;
|
||||
int error;
|
||||
dev_t dev = bdev->bd_dev;
|
||||
struct super_dev *sb_dev;
|
||||
unsigned int count = 0;
|
||||
int error = 0, err;
|
||||
|
||||
lockdep_assert_held(&bdev->bd_fsfreeze_mutex);
|
||||
|
||||
/*
|
||||
* The block device may have been frozen before it was claimed by a
|
||||
* filesystem. Concurrently another process might try to mount that
|
||||
* frozen block device and has temporarily claimed the block device for
|
||||
* that purpose causing a concurrent fs_bdev_thaw() to end up here. The
|
||||
* mounter is already about to abort mounting because they still saw an
|
||||
* elevanted bdev->bd_fsfreeze_count so get_bdev_super() will return
|
||||
* NULL in that case.
|
||||
*/
|
||||
sb = get_bdev_super(bdev);
|
||||
if (!sb)
|
||||
return -EINVAL;
|
||||
mutex_unlock(&bdev->bd_holder_lock);
|
||||
|
||||
if (sb->s_op->thaw_super)
|
||||
error = sb->s_op->thaw_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
else
|
||||
error = thaw_super(sb,
|
||||
FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL);
|
||||
deactivate_super(sb);
|
||||
for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) {
|
||||
if (!get_active_super(sb_dev->sd_sb))
|
||||
continue;
|
||||
err = fs_super_thaw(sb_dev->sd_sb);
|
||||
if (err && !error)
|
||||
error = err;
|
||||
deactivate_super(sb_dev->sd_sb);
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Shared device: swallow per-superblock errors, like fs_bdev_freeze(). */
|
||||
if (error && count > 1)
|
||||
error = 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
const struct blk_holder_ops fs_holder_ops = {
|
||||
static const struct blk_holder_ops fs_holder_ops = {
|
||||
.mark_dead = fs_bdev_mark_dead,
|
||||
.sync = fs_bdev_sync,
|
||||
.freeze = fs_bdev_freeze,
|
||||
.thaw = fs_bdev_thaw,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fs_holder_ops);
|
||||
|
||||
static struct super_dev *super_dev_lookup(dev_t dev, struct super_block *sb)
|
||||
{
|
||||
struct super_dev *it;
|
||||
struct rhlist_head *list, *pos;
|
||||
|
||||
RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious super_dev_lookup() usage");
|
||||
VFS_WARN_ON_ONCE(!dev);
|
||||
VFS_WARN_ON_ONCE(!sb);
|
||||
|
||||
list = rhltable_lookup(&super_dev_table, &dev, super_dev_params);
|
||||
rhl_for_each_entry_rcu(it, pos, list, sd_node) {
|
||||
if (it->sd_sb == sb)
|
||||
return it;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int fs_bdev_register(struct file *bdev_file, struct super_block *sb)
|
||||
{
|
||||
struct super_dev *sb_dev __free(kfree) = NULL;
|
||||
dev_t dev = file_bdev(bdev_file)->bd_dev;
|
||||
int err;
|
||||
|
||||
scoped_guard(rcu) {
|
||||
sb_dev = super_dev_lookup(dev, sb);
|
||||
if (sb_dev && refcount_inc_not_zero(&sb_dev->sd_ref)) {
|
||||
retain_and_null_ptr(sb_dev);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sb_dev = super_dev_alloc(dev, sb);
|
||||
if (!sb_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
err = super_dev_insert(sb_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Publish the entry before reading the count; pairs with bdev_freeze(). */
|
||||
smp_mb();
|
||||
if (atomic_read(&file_bdev(bdev_file)->bd_fsfreeze_count) > 0) {
|
||||
err = -EBUSY;
|
||||
super_dev_put(sb_dev);
|
||||
}
|
||||
|
||||
retain_and_null_ptr(sb_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* fs_bdev_file_open_by_dev - claim a block device on behalf of a superblock
|
||||
* @dev: block device number
|
||||
* @mode: open mode
|
||||
* @holder: block-layer exclusivity token (a superblock, or the file_system_type
|
||||
* when the device may be shared by several superblocks of that type)
|
||||
* @sb: superblock to drive fs_holder_ops events for
|
||||
*
|
||||
* Open @dev with &fs_holder_ops and register that @sb uses it, so device
|
||||
* removal/sync/freeze/thaw are propagated to @sb (and any other superblock
|
||||
* sharing @dev). Must be paired with fs_bdev_file_release().
|
||||
*
|
||||
* Return: an opened block-device file or an ERR_PTR().
|
||||
*/
|
||||
struct file *fs_bdev_file_open_by_dev(dev_t dev, blk_mode_t mode, void *holder,
|
||||
struct super_block *sb)
|
||||
{
|
||||
struct file *bdev_file;
|
||||
int err;
|
||||
|
||||
bdev_file = bdev_file_open_by_dev(dev, mode, holder, &fs_holder_ops);
|
||||
if (IS_ERR(bdev_file))
|
||||
return bdev_file;
|
||||
|
||||
err = fs_bdev_register(bdev_file, sb);
|
||||
if (err) {
|
||||
bdev_fput(bdev_file);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
return bdev_file;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_dev);
|
||||
|
||||
/**
|
||||
* fs_bdev_file_open_by_path - claim a block device on behalf of a superblock
|
||||
* @path: path to the block device
|
||||
* @mode: open mode
|
||||
* @holder: block-layer exclusivity token (a superblock, or the file_system_type
|
||||
* when the device may be shared by several superblocks of that type)
|
||||
* @sb: superblock to drive fs_holder_ops events for
|
||||
*
|
||||
* Open the block device at @path with &fs_holder_ops and register that @sb
|
||||
* uses it, so device removal/sync/freeze/thaw are propagated to @sb (and any
|
||||
* other superblock sharing the device). Must be paired with
|
||||
* fs_bdev_file_release().
|
||||
*
|
||||
* Return: an opened block-device file or an ERR_PTR().
|
||||
*/
|
||||
struct file *fs_bdev_file_open_by_path(const char *path, blk_mode_t mode,
|
||||
void *holder, struct super_block *sb)
|
||||
{
|
||||
struct file *bdev_file;
|
||||
int err;
|
||||
|
||||
bdev_file = bdev_file_open_by_path(path, mode, holder, &fs_holder_ops);
|
||||
if (IS_ERR(bdev_file))
|
||||
return bdev_file;
|
||||
|
||||
err = fs_bdev_register(bdev_file, sb);
|
||||
if (err) {
|
||||
bdev_fput(bdev_file);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
return bdev_file;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_path);
|
||||
|
||||
/**
|
||||
* fs_bdev_unregister - drop a superblock's claim on a block device
|
||||
* @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}()
|
||||
* @sb: superblock the device was claimed for
|
||||
*
|
||||
* The inverse of fs_bdev_register(): drop one claim on the {dev, @sb} entry
|
||||
* (the last claim unregisters it; a pinning cursor defers the actual unlink)
|
||||
* without closing the device. A caller that must act on the still-open device
|
||||
* between unregistering and closing - e.g. re-allow freezing one denied for a
|
||||
* membership change - pairs this with bdev_fput(). fs_bdev_file_release() is
|
||||
* the common unregister-and-close.
|
||||
*/
|
||||
void fs_bdev_unregister(struct file *bdev_file, struct super_block *sb)
|
||||
{
|
||||
dev_t dev = file_bdev(bdev_file)->bd_dev;
|
||||
struct super_dev *sb_dev;
|
||||
|
||||
rcu_read_lock();
|
||||
sb_dev = super_dev_lookup(dev, sb);
|
||||
rcu_read_unlock();
|
||||
super_dev_put(sb_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fs_bdev_unregister);
|
||||
|
||||
/**
|
||||
* fs_bdev_file_release - release a block device claimed for a superblock
|
||||
* @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}()
|
||||
* @sb: superblock the device was claimed for
|
||||
*
|
||||
* Unregister the {dev, @sb} entry, then close the block device.
|
||||
*/
|
||||
void fs_bdev_file_release(struct file *bdev_file, struct super_block *sb)
|
||||
{
|
||||
fs_bdev_unregister(bdev_file, sb);
|
||||
bdev_fput(bdev_file);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fs_bdev_file_release);
|
||||
|
||||
int setup_bdev_super(struct super_block *sb, int sb_flags,
|
||||
struct fs_context *fc)
|
||||
@@ -1570,7 +1803,7 @@ int setup_bdev_super(struct super_block *sb, int sb_flags,
|
||||
struct file *bdev_file;
|
||||
struct block_device *bdev;
|
||||
|
||||
bdev_file = bdev_file_open_by_dev(sb->s_dev, mode, sb, &fs_holder_ops);
|
||||
bdev_file = fs_bdev_file_open_by_dev(sb->s_dev, mode, sb, sb);
|
||||
if (IS_ERR(bdev_file)) {
|
||||
if (fc)
|
||||
errorf(fc, "%s: Can't open blockdev", fc->source);
|
||||
@@ -1584,20 +1817,19 @@ int setup_bdev_super(struct super_block *sb, int sb_flags,
|
||||
* writable from userspace even for a read-only block device.
|
||||
*/
|
||||
if ((mode & BLK_OPEN_WRITE) && bdev_read_only(bdev)) {
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, sb);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is enough to check bdev was not frozen before we set
|
||||
* s_bdev as freezing will wait until SB_BORN is set.
|
||||
*/
|
||||
/* The sget_fc() entry is already published; pairs with bdev_freeze(). */
|
||||
smp_mb();
|
||||
if (atomic_read(&bdev->bd_fsfreeze_count) > 0) {
|
||||
if (fc)
|
||||
warnf(fc, "%pg: Can't mount, blockdev is frozen", bdev);
|
||||
bdev_fput(bdev_file);
|
||||
fs_bdev_file_release(bdev_file, sb);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
sb->s_bdev_file = bdev_file;
|
||||
sb->s_bdev = bdev;
|
||||
@@ -1686,7 +1918,7 @@ void kill_block_super(struct super_block *sb)
|
||||
generic_shutdown_super(sb);
|
||||
if (bdev) {
|
||||
sync_blockdev(bdev);
|
||||
bdev_fput(sb->s_bdev_file);
|
||||
fs_bdev_file_release(sb->s_bdev_file, sb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2033,7 +2265,7 @@ int freeze_super(struct super_block *sb, enum freeze_holder who, const void *fre
|
||||
int ret;
|
||||
|
||||
if (!super_lock_excl(sb)) {
|
||||
WARN_ON_ONCE("Dying superblock while freezing!");
|
||||
WARN_ONCE(1, "Dying superblock while freezing!");
|
||||
return -EINVAL;
|
||||
}
|
||||
atomic_inc(&sb->s_active);
|
||||
@@ -2197,7 +2429,7 @@ int thaw_super(struct super_block *sb, enum freeze_holder who,
|
||||
const void *freeze_owner)
|
||||
{
|
||||
if (!super_lock_excl(sb)) {
|
||||
WARN_ON_ONCE("Dying superblock while thawing!");
|
||||
WARN_ONCE(1, "Dying superblock while thawing!");
|
||||
return -EINVAL;
|
||||
}
|
||||
return thaw_super_locked(sb, who, freeze_owner);
|
||||
|
||||
+1
-1
@@ -1631,7 +1631,7 @@ xfs_free_buftarg(
|
||||
fs_put_dax(btp->bt_daxdev, btp->bt_mount);
|
||||
/* the main block device is closed by kill_block_super */
|
||||
if (btp->bt_bdev != btp->bt_mount->m_super->s_bdev)
|
||||
bdev_fput(btp->bt_file);
|
||||
fs_bdev_file_release(btp->bt_file, btp->bt_mount->m_super);
|
||||
kfree(btp);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -400,8 +400,8 @@ xfs_blkdev_get(
|
||||
blk_mode_t mode;
|
||||
|
||||
mode = sb_open_mode(mp->m_super->s_flags);
|
||||
*bdev_filep = bdev_file_open_by_path(name, mode,
|
||||
mp->m_super, &fs_holder_ops);
|
||||
*bdev_filep = fs_bdev_file_open_by_path(name, mode,
|
||||
mp->m_super, mp->m_super);
|
||||
if (IS_ERR(*bdev_filep)) {
|
||||
error = PTR_ERR(*bdev_filep);
|
||||
*bdev_filep = NULL;
|
||||
@@ -526,7 +526,7 @@ xfs_open_devices(
|
||||
mp->m_logdev_targp = mp->m_ddev_targp;
|
||||
/* Handle won't be used, drop it */
|
||||
if (logdev_file)
|
||||
bdev_fput(logdev_file);
|
||||
fs_bdev_file_release(logdev_file, mp->m_super);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -541,10 +541,10 @@ xfs_open_devices(
|
||||
mp->m_ddev_targp = NULL;
|
||||
out_close_rtdev:
|
||||
if (rtdev_file)
|
||||
bdev_fput(rtdev_file);
|
||||
fs_bdev_file_release(rtdev_file, mp->m_super);
|
||||
out_close_logdev:
|
||||
if (logdev_file)
|
||||
bdev_fput(logdev_file);
|
||||
fs_bdev_file_release(logdev_file, mp->m_super);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ struct block_device {
|
||||
int bd_holders;
|
||||
struct kobject *bd_holder_dir;
|
||||
|
||||
atomic_t bd_fsfreeze_count; /* number of freeze requests */
|
||||
atomic_t bd_fsfreeze_count; /* >0 freeze requests, <0 freeze deniers */
|
||||
struct mutex bd_fsfreeze_mutex; /* serialize freeze/thaw */
|
||||
|
||||
struct partition_meta_info *bd_meta_info;
|
||||
|
||||
@@ -126,8 +126,6 @@ struct blk_integrity {
|
||||
unsigned char pi_tuple_size;
|
||||
};
|
||||
|
||||
typedef unsigned int __bitwise blk_mode_t;
|
||||
|
||||
/* open for reading */
|
||||
#define BLK_OPEN_READ ((__force blk_mode_t)(1 << 0))
|
||||
/* open for writing */
|
||||
@@ -1770,13 +1768,6 @@ struct blk_holder_ops {
|
||||
__releases(&bdev->bd_holder_lock);
|
||||
};
|
||||
|
||||
/*
|
||||
* For filesystems using @fs_holder_ops, the @holder argument passed to
|
||||
* helpers used to open and claim block devices via
|
||||
* bd_prepare_to_claim() must point to a superblock.
|
||||
*/
|
||||
extern const struct blk_holder_ops fs_holder_ops;
|
||||
|
||||
/*
|
||||
* Return the correct open flags for blkdev_get_by_* for super block flags
|
||||
* as stored in sb->s_flags.
|
||||
@@ -1837,7 +1828,10 @@ static inline int early_lookup_bdev(const char *pathname, dev_t *dev)
|
||||
|
||||
int bdev_freeze(struct block_device *bdev);
|
||||
int bdev_thaw(struct block_device *bdev);
|
||||
int bdev_deny_freeze(struct block_device *bdev);
|
||||
void bdev_allow_freeze(struct block_device *bdev);
|
||||
void bdev_fput(struct file *bdev_file);
|
||||
void bdev_yield_claim(struct file *bdev_file);
|
||||
|
||||
struct io_comp_batch {
|
||||
struct rq_list req_list;
|
||||
|
||||
@@ -1916,8 +1916,6 @@ struct dir_context {
|
||||
struct io_uring_cmd;
|
||||
struct offset_ctx;
|
||||
|
||||
typedef unsigned int __bitwise fop_flags_t;
|
||||
|
||||
struct file_operations {
|
||||
struct module *owner;
|
||||
fop_flags_t fop_flags;
|
||||
|
||||
@@ -237,4 +237,12 @@ int thaw_super(struct super_block *super, enum freeze_holder who,
|
||||
|
||||
int sb_init_dio_done_wq(struct super_block *sb);
|
||||
|
||||
struct file;
|
||||
struct file *fs_bdev_file_open_by_dev(dev_t dev, blk_mode_t mode, void *holder,
|
||||
struct super_block *sb);
|
||||
struct file *fs_bdev_file_open_by_path(const char *path, blk_mode_t mode,
|
||||
void *holder, struct super_block *sb);
|
||||
void fs_bdev_unregister(struct file *bdev_file, struct super_block *sb);
|
||||
void fs_bdev_file_release(struct file *bdev_file, struct super_block *sb);
|
||||
|
||||
#endif /* _LINUX_FS_SUPER_H */
|
||||
|
||||
@@ -30,6 +30,7 @@ struct mount;
|
||||
struct mtd_info;
|
||||
struct quotactl_ops;
|
||||
struct shrinker;
|
||||
struct super_dev;
|
||||
struct unicode_map;
|
||||
struct user_namespace;
|
||||
struct workqueue_struct;
|
||||
@@ -132,6 +133,7 @@ struct super_operations {
|
||||
struct super_block {
|
||||
struct list_head s_list; /* Keep this first */
|
||||
dev_t s_dev; /* search index; _not_ kdev_t */
|
||||
struct super_dev *s_super_dev; /* sget_fc()'s device table claim */
|
||||
unsigned char s_blocksize_bits;
|
||||
unsigned long s_blocksize;
|
||||
loff_t s_maxbytes; /* Max file size */
|
||||
@@ -145,7 +147,7 @@ struct super_block {
|
||||
unsigned long s_magic;
|
||||
struct dentry *s_root;
|
||||
struct rw_semaphore s_umount;
|
||||
int s_count;
|
||||
refcount_t s_passive;
|
||||
atomic_t s_active;
|
||||
#ifdef CONFIG_SECURITY
|
||||
void *s_security;
|
||||
|
||||
@@ -163,6 +163,8 @@ typedef u32 dma_addr_t;
|
||||
typedef unsigned int __bitwise gfp_t;
|
||||
typedef unsigned int __bitwise slab_flags_t;
|
||||
typedef unsigned int __bitwise fmode_t;
|
||||
typedef unsigned int __bitwise blk_mode_t;
|
||||
typedef unsigned int __bitwise fop_flags_t;
|
||||
|
||||
#ifdef CONFIG_PHYS_ADDR_T_64BIT
|
||||
typedef u64 phys_addr_t;
|
||||
|
||||
@@ -6,3 +6,4 @@ file_stressor
|
||||
anon_inode_test
|
||||
kernfs_test
|
||||
idmapped_tmpfile
|
||||
ustat_test
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
|
||||
TEST_GEN_PROGS += idmapped_tmpfile
|
||||
TEST_GEN_PROGS_EXTENDED := dnotify_test
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test ustat(2): looking up superblocks by device number.
|
||||
*
|
||||
* ustat() resolves a device number to a mounted superblock via
|
||||
* user_get_super(). Check that the device number of a mounted tmpfs (an
|
||||
* anonymous device) resolves, that it stops resolving once the filesystem
|
||||
* is unmounted and that bogus device numbers report EINVAL.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
/* struct ustat is not exported through UAPI, mirror include/linux/types.h. */
|
||||
struct ustat_buf {
|
||||
int f_tfree;
|
||||
unsigned long f_tinode;
|
||||
char f_fname[6];
|
||||
char f_fpack[6];
|
||||
/* slack in case an architecture lays the struct out differently */
|
||||
char pad[64];
|
||||
};
|
||||
|
||||
#ifdef __NR_ustat
|
||||
|
||||
/*
|
||||
* The kernel decodes @dev with new_decode_dev(), which matches the low 32
|
||||
* bits of the st_dev encoding stat(2) returns for any major below 4096.
|
||||
*/
|
||||
static int sys_ustat(unsigned int dev, struct ustat_buf *buf)
|
||||
{
|
||||
return syscall(__NR_ustat, dev, buf);
|
||||
}
|
||||
|
||||
static int write_string(const char *path, const char *string)
|
||||
{
|
||||
ssize_t len = strlen(string);
|
||||
int fd;
|
||||
|
||||
fd = open(path, O_WRONLY);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
if (write(fd, string, len) != len) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
/* Enter namespaces in which mounting a tmpfs instance is allowed. */
|
||||
static int setup_namespaces(void)
|
||||
{
|
||||
uid_t uid = getuid();
|
||||
gid_t gid = getgid();
|
||||
char map[64];
|
||||
|
||||
if (unshare(CLONE_NEWNS | (uid ? CLONE_NEWUSER : 0)))
|
||||
return -1;
|
||||
|
||||
if (uid) {
|
||||
if (write_string("/proc/self/setgroups", "deny"))
|
||||
return -1;
|
||||
snprintf(map, sizeof(map), "0 %d 1", uid);
|
||||
if (write_string("/proc/self/uid_map", map))
|
||||
return -1;
|
||||
snprintf(map, sizeof(map), "0 %d 1", gid);
|
||||
if (write_string("/proc/self/gid_map", map))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);
|
||||
}
|
||||
|
||||
TEST(resolves_mounted_superblock)
|
||||
{
|
||||
char dir[] = "/tmp/ustat_test.XXXXXX";
|
||||
struct ustat_buf ub;
|
||||
struct stat st;
|
||||
|
||||
ASSERT_NE(NULL, mkdtemp(dir));
|
||||
|
||||
if (setup_namespaces()) {
|
||||
rmdir(dir);
|
||||
SKIP(return, "cannot set up namespaces: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, mount("ustat_test", dir, "tmpfs", 0, NULL));
|
||||
ASSERT_EQ(0, stat(dir, &st));
|
||||
|
||||
memset(&ub, 0xff, sizeof(ub));
|
||||
ASSERT_EQ(0, sys_ustat(st.st_dev, &ub))
|
||||
TH_LOG("ustat(%u): %s", (unsigned int)st.st_dev,
|
||||
strerror(errno));
|
||||
|
||||
ASSERT_EQ(0, umount(dir));
|
||||
|
||||
/* The unmount removed the superblock, the device is gone. */
|
||||
ASSERT_EQ(-1, sys_ustat(st.st_dev, &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
TEST(bogus_device_numbers)
|
||||
{
|
||||
struct ustat_buf ub;
|
||||
|
||||
ASSERT_EQ(-1, sys_ustat(0, &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
/* major 4095, minor 1048575: nothing plausible lives there */
|
||||
ASSERT_EQ(-1, sys_ustat((0xfffu << 8) | 0xffu | (0xfff00u << 12), &ub));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
}
|
||||
|
||||
#else /* !__NR_ustat */
|
||||
|
||||
TEST(unsupported)
|
||||
{
|
||||
SKIP(return, "ustat(2) is not available on this architecture");
|
||||
}
|
||||
|
||||
#endif /* __NR_ustat */
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Reference in New Issue
Block a user