btrfs: add remove_bdev() callback
Subsystems (7)
v3 cover btrfs: add remove_bdev() callback
[CHANGELOG]
v3:
- Also rename the callback functions inside each fs to *_remove_bdev() To keep the consistency between the interface and implementation.
- Add extra handling if the to-be-removed device is already missing in btrfs
I do not know if a device can be double-removed, but the handling inside btrfs is pretty simple, if the target device is already missing, nothing needs to be done and can exit immediately.
v2:
- Enhance and rename shutdown() callback
Rename it to remove_bdev() and add a @bdev parameter. For the existing call backs in filesystems, keep their callback function names, now something like ".remove_bdev = ext4_shutdown," will be a quick indicator of the behavior.
- Remove the @surprise parameter for the remove_bdev() parameter. The fs_bdev_mark_dead() is already trying to sync the fs if it's not a surprise removal.
So there isn't much a filesystem can do with the @surprise parameter.
- Fix btrfs error handling when the devices are not opened There are several cases that the fs_devices is not opened, including:
* sget_fc() failure
* an existing super block is returned
* a new super block is returned but btrfS_open_fs_devices() failed
Handle the error properly so that fs_devices is not freed twice.
RFC->v1:
- Add a new remove_bdev() callback
Thanks all the feedback from Christian, Christoph and Jan on this new name.
- Add a @surprise parameter to the remove_bdev() callback To keep it the same as the bdev_mark_dead().
- Hide the shutdown ioctl and remove_bdev callback behind experimental With the shutdown ioctl, there are at least 2 test failures (g/388, g/508).
G/388 is related to the error handling with COW fixup. G/508 looks like something related to log replay.
And the remove_bdev() doesn't have any btrfs specific test case yet to check the auto-degraded behavior, nor the auto-degraded behavior is fully discussed.
So hide both of them behind experimental features.
- Do not use btrfs_handle_fs_error() to avoid freeze/thaw behavior change btrfs_handle_fs_error() will flips the fs read-only, which will affect freeze/thaw behavior.
And no other fs set the fs read-only when shutting down, so follow the other fs to have a more consistent behavior.
Qu Wenruo (6):
fs: enhance and rename shutdown() callback to remove_bdev()
btrfs: introduce a new fs state, EMERGENCY_SHUTDOWN
btrfs: reject file operations if in shutdown state
btrfs: reject delalloc ranges if in shutdown state
btrfs: implement shutdown ioctl
btrfs: implement remove_bdev super operation callback
fs/btrfs/file.c | 25 ++++++++++++++++- fs/btrfs/fs.h | 28 +++++++++++++++++++ fs/btrfs/inode.c | 14 +++++++++- fs/btrfs/ioctl.c | 43 ++++++++++++++++++++++++++++ fs/btrfs/messages.c | 1 + fs/btrfs/reflink.c | 3 ++ fs/btrfs/super.c | 57 ++++++++++++++++++++++++++++++++++++++ fs/btrfs/volumes.c | 2 ++ fs/btrfs/volumes.h | 5 ++++ fs/exfat/super.c | 4 +-- fs/ext4/super.c | 4 +-- fs/f2fs/super.c | 4 +-- fs/ntfs3/super.c | 6 ++-- fs/super.c | 4 +-- fs/xfs/xfs_super.c | 7 +++-- include/linux/fs.h | 7 ++++- include/uapi/linux/btrfs.h | 9 ++++++ 17 files changed, 206 insertions(+), 17 deletions(-)
Signature · 1 line
2.50.0
PATCH v3 1/6 fs: enhance and rename shutdown() callback to remove_bdev()
Currently all the filesystems implementing the super_opearations::shutdown() call back can not afford losing a device.
Thus fs_bdev_mark_dead() will just call the shutdown() callback for the involved filesystem.
But it will no longer be the case, with multi-device filesystems like btrfs and bcachefs the filesystem can handle certain device loss without shutting down the whole filesystem.
To allow those multi-device filesystems to be integrated to use fs_holder_ops:
- Rename shutdown() call back to remove_bdev() To better describe when the call back is called.
- Add a new @bdev parameter to remove_bdev() callback To allow the fs to determine which device is missing, and do the proper handling when needed.
For the existing shutdown callback users, the change is minimal.
They only need to follow the rename and the new parameter list. Since the behavior is still to shutdown the fs, they shouldn't change their function names.
This has a good side effect that, a single line like ".remove_bdev = ext4_shutdown," will easily show the fs behavior and indicate the fs will shutdown when a device went missing.
Btrfs is going to implement the callback soon, which will either shutdown the fs or continue read-write operations.
--- fs/exfat/super.c | 4 ++-- fs/ext4/super.c | 4 ++-- fs/f2fs/super.c | 4 ++-- fs/ntfs3/super.c | 6 +++--- fs/super.c | 4 ++-- fs/xfs/xfs_super.c | 7 ++++--- include/linux/fs.h | 7 ++++++- 7 files changed, 21 insertions(+), 15 deletions(-)
fs/exfat/super.c +2 -2
@@ -172,7 +172,7 @@ int exfat_force_shutdown(struct super_block *sb, u32 flags)return 0 ; } -static void exfat_shutdown (struct super_block *sb) +static void exfat_remove_bdev (struct super_block *sb,struct block_device *bdev) {exfat_force_shutdown (sb,EXFAT_GOING_DOWN_NOSYNC ); }
@@ -202,7 +202,7 @@ static const struct super_operations exfat_sops = { .put_super = exfat_put_super, .statfs = exfat_statfs, .show_options = exfat_show_options, - .shutdown = exfat_shutdown, + .remove_bdev = exfat_remove_bdev, };enum {
fs/ext4/super.c +2 -2
@@ -1456,7 +1456,7 @@ static void ext4_destroy_inode(struct inode *inode) EXT4_I(inode)->i_reserved_data_blocks); } -static void ext4_shutdown (struct super_block *sb) +static void ext4_remove_bdev (struct super_block *sb,struct block_device *bdev) {ext4_force_shutdown (sb,EXT4_GOING_FLAGS_NOLOGFLUSH ); }
@@ -1620,7 +1620,7 @@ static const struct super_operations ext4_sops = { .unfreeze_fs = ext4_unfreeze, .statfs = ext4_statfs, .show_options = ext4_show_options, - .shutdown = ext4_shutdown, + .remove_bdev = ext4_remove_bdev, #ifdef CONFIG_QUOTA .quota_read = ext4_quota_read, .quota_write = ext4_quota_write,
fs/f2fs/super.c +2 -2
@@ -2640,7 +2640,7 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)return err; } -static void f2fs_shutdown (struct super_block *sb) +static void f2fs_remove_bdev (struct super_block *sb,struct block_device *bdev) {f2fs_do_shutdown (F2FS_SB (sb),F2FS_GOING_DOWN_NOSYNC , false, false); }
@@ -3264,7 +3264,7 @@ static const struct super_operations f2fs_sops = { .unfreeze_fs = f2fs_unfreeze, .statfs = f2fs_statfs, .remount_fs = f2fs_remount, - .shutdown = f2fs_shutdown, + .remove_bdev = f2fs_remove_bdev, };#ifdef CONFIG_FS_ENCRYPTION
fs/ntfs3/super.c +3 -3
@@ -762,9 +762,9 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root) }/* -* ntfs_shutdown - super_operations::shutdown +* ntfs_remove_bdev - super_operations::remove_bdev */ -static void ntfs_shutdown (struct super_block *sb) +static void ntfs_remove_bdev (struct super_block *sb,struct block_device *bdev) {set_bit (NTFS_FLAGS_SHUTDOWN_BIT , &ntfs_sb (sb)->flags); }
@@ -821,7 +821,7 @@ static const struct super_operations ntfs_sops = { .put_super = ntfs_put_super, .statfs = ntfs_statfs, .show_options = ntfs_show_options, - .shutdown = ntfs_shutdown, + .remove_bdev = ntfs_remove_bdev, .sync_fs = ntfs_sync_fs, .write_inode = ntfs3_write_inode, };
fs/super.c +2 -2
@@ -1463,8 +1463,8 @@ static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise)sync_filesystem (sb);shrink_dcache_sb (sb);evict_inodes (sb); -if (sb->s_op->shutdown) - sb->s_op->shutdown (sb); +if (sb->s_op->remove_bdev) + sb->s_op->remove_bdev (sb, bdev);super_unlock_shared (sb); }
fs/xfs/xfs_super.c +4 -3
@@ -1276,8 +1276,9 @@ xfs_fs_free_cached_objects( }static void -xfs_fs_shutdown ( -struct super_block *sb) +xfs_fs_remove_bdev ( +struct super_block *sb, +struct block_device *bdev) {xfs_force_shutdown (XFS_M (sb),SHUTDOWN_DEVICE_REMOVED ); }
@@ -1308,7 +1309,7 @@ static const struct super_operations xfs_super_operations = { .show_options = xfs_fs_show_options, .nr_cached_objects = xfs_fs_nr_cached_objects, .free_cached_objects = xfs_fs_free_cached_objects, - .shutdown = xfs_fs_shutdown, + .remove_bdev = xfs_fs_remove_bdev, .show_stats = xfs_fs_show_stats, };
include/linux/fs.h +6 -1
@@ -2367,7 +2367,12 @@ struct super_operations { struct shrink_control *);long (*free_cached_objects)(struct super_block *,struct shrink_control *); -void (*shutdown)(struct super_block *sb); +/* +* Called when block device @bdev belonging to @sb is removed. +* +* If the fs can't afford the device loss, it should be shutdown. +*/ +void (*remove_bdev)(struct super_block *sb,struct block_device *bdev); };/*
Signature · 1 line
2.50.0
Re: [PATCH v3 1/6] fs: enhance and rename shutdown() callback to remove_bdev()
Quoted 33 lines · Qu Wenruo
Currently all the filesystems implementing the
super_opearations::shutdown()call back can not afford losing a device.Thus
fs_bdev_mark_dead()will just call theshutdown()callback for the involved filesystem.But it will no longer be the case, with multi-device filesystems like btrfs and bcachefs the filesystem can handle certain device loss without shutting down the whole filesystem.
To allow those multi-device filesystems to be integrated to use
fs_holder_ops:- Rename
shutdown()call back toremove_bdev()To better describe when the call back is called.- Add a new @bdev parameter to
remove_bdev()callback To allow the fs to determine which device is missing, and do the proper handling when needed.For the existing shutdown callback users, the change is minimal.
They only need to follow the rename and the new parameter list. Since the behavior is still to shutdown the fs, they shouldn't change their function names.
This has a good side effect that, a single line like ".remove_bdev =
ext4_shutdown," will easily show the fs behavior and indicate the fs will shutdown when a device went missing.Btrfs is going to implement the callback soon, which will either shutdown the fs or continue read-write operations.
Quoted 6 lines
Signed-off-by Qu Wenruo <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 162 lines
--- fs/exfat/super.c | 4 ++-- fs/ext4/super.c | 4 ++-- fs/f2fs/super.c | 4 ++-- fs/ntfs3/super.c | 6 +++--- fs/super.c | 4 ++-- fs/xfs/xfs_super.c | 7 ++++--- include/linux/fs.h | 7 ++++++- 7 files changed, 21 insertions(+), 15 deletions(-)fs/exfat/super.c +2
-2@@ -172,7 +172,7 @@ int exfat_force_shutdown(struct super_block *sb, u32 flags)return 0 ; } -static void exfat_shutdown (struct super_block *sb) +static void exfat_remove_bdev (struct super_block *sb,struct block_device *bdev) {exfat_force_shutdown (sb,EXFAT_GOING_DOWN_NOSYNC ); }@@ -202,7 +202,7 @@ static const struct super_operations exfat_sops = { .put_super = exfat_put_super, .statfs = exfat_statfs, .show_options = exfat_show_options, - .shutdown = exfat_shutdown, + .remove_bdev = exfat_remove_bdev, };enum {fs/ext4/super.c +2
-2@@ -1456,7 +1456,7 @@ static void ext4_destroy_inode(struct inode *inode) EXT4_I(inode)->i_reserved_data_blocks); } -static void ext4_shutdown (struct super_block *sb) +static void ext4_remove_bdev (struct super_block *sb,struct block_device *bdev) {ext4_force_shutdown (sb,EXT4_GOING_FLAGS_NOLOGFLUSH ); }@@ -1620,7 +1620,7 @@ static const struct super_operations ext4_sops = { .unfreeze_fs = ext4_unfreeze, .statfs = ext4_statfs, .show_options = ext4_show_options, - .shutdown = ext4_shutdown, + .remove_bdev = ext4_remove_bdev, #ifdef CONFIG_QUOTA .quota_read = ext4_quota_read, .quota_write = ext4_quota_write,fs/f2fs/super.c +2
-2@@ -2640,7 +2640,7 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)return err; } -static void f2fs_shutdown (struct super_block *sb) +static void f2fs_remove_bdev (struct super_block *sb,struct block_device *bdev) {f2fs_do_shutdown (F2FS_SB (sb),F2FS_GOING_DOWN_NOSYNC , false, false); }@@ -3264,7 +3264,7 @@ static const struct super_operations f2fs_sops = { .unfreeze_fs = f2fs_unfreeze, .statfs = f2fs_statfs, .remount_fs = f2fs_remount, - .shutdown = f2fs_shutdown, + .remove_bdev = f2fs_remove_bdev, };#ifdef CONFIG_FS_ENCRYPTION fs/ntfs3/super.c +3
-3@@ -762,9 +762,9 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root) }/* -* ntfs_shutdown - super_operations::shutdown +* ntfs_remove_bdev - super_operations::remove_bdev */ -static void ntfs_shutdown (struct super_block *sb) +static void ntfs_remove_bdev (struct super_block *sb,struct block_device *bdev) {set_bit (NTFS_FLAGS_SHUTDOWN_BIT , &ntfs_sb (sb)->flags); }@@ -821,7 +821,7 @@ static const struct super_operations ntfs_sops = { .put_super = ntfs_put_super, .statfs = ntfs_statfs, .show_options = ntfs_show_options, - .shutdown = ntfs_shutdown, + .remove_bdev = ntfs_remove_bdev, .sync_fs = ntfs_sync_fs, .write_inode = ntfs3_write_inode, };fs/super.c +2
-2@@ -1463,8 +1463,8 @@ static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise)sync_filesystem (sb);shrink_dcache_sb (sb);evict_inodes (sb); -if (sb->s_op->shutdown) - sb->s_op->shutdown (sb); +if (sb->s_op->remove_bdev) + sb->s_op->remove_bdev (sb, bdev);super_unlock_shared (sb); }fs/xfs/xfs_super.c +4
-3@@ -1276,8 +1276,9 @@ xfs_fs_free_cached_objects( }static void -xfs_fs_shutdown ( -struct super_block *sb) +xfs_fs_remove_bdev ( +struct super_block *sb, +struct block_device *bdev) {xfs_force_shutdown (XFS_M (sb),SHUTDOWN_DEVICE_REMOVED ); }@@ -1308,7 +1309,7 @@ static const struct super_operations xfs_super_operations = { .show_options = xfs_fs_show_options, .nr_cached_objects = xfs_fs_nr_cached_objects, .free_cached_objects = xfs_fs_free_cached_objects, - .shutdown = xfs_fs_shutdown, + .remove_bdev = xfs_fs_remove_bdev, .show_stats = xfs_fs_show_stats, };include/linux/fs.h +6
-1@@ -2367,7 +2367,12 @@ struct super_operations { struct shrink_control *);long (*free_cached_objects)(struct super_block *,struct shrink_control *); -void (*shutdown)(struct super_block *sb); +/* +* Called when block device @bdev belonging to @sb is removed. +* +* If the fs can't afford the device loss, it should be shutdown. +*/ +void (*remove_bdev)(struct super_block *sb,struct block_device *bdev); };/* Signature · 1 line
2.50.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
PATCH v3 2/6 btrfs: introduce a new fs state, EMERGENCY_SHUTDOWN
In reply to Qu WenruoThis is btrfs' equivalent of XFS_IOC_GOINGDOWN or EXT4_IOC_SHUTDOWN, after entering the emergency shutdown state, all operations will return errors (-EIO), and can not be bring back to normal state until unmount.
A new helper, btrfs_force_shutdown() is introduced, which will:
- Mark the fs as error
But without flipping the fs read-only.
This is a special handling for the future shutdown ioctl, which will freeze the fs first, set the SHUTDOWN flag, thaw the fs.
But the thaw path will no longer call the unfreeze_fs() call back if the superblock is already read-only.
So to handle future shutdown correctly, we only mark the fs as error, without flipping it read-only.
- Set the SHUTDOWN flag and output an message
New users of those interfaces will be added when implementing shutdown ioctl support.
--- fs/btrfs/fs.h | 28 ++++++++++++++++++++++++++++ fs/btrfs/messages.c | 1 + 2 files changed, 29 insertions(+)
fs/btrfs/fs.h +28 -0
@@ -29,6 +29,7 @@#include "extent-io-tree.h" #include "async-thread.h" #include "block-rsv.h" +#include "messages.h" struct inode ;struct super_block ;
@@ -120,6 +121,12 @@ enum {/* No more delayed iput can be queued. */ BTRFS_FS_STATE_NO_DELAYED_IPUT , +/* +* Emergency shutdown, a step further than trans aborted by rejecting +* all operations. +*/ +BTRFS_FS_STATE_EMERGENCY_SHUTDOWN , + BTRFS_FS_STATE_COUNT };
@@ -1094,6 +1101,27 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info) (unlikely (test_bit (BTRFS_FS_STATE_LOG_CLEANUP_ERROR , \ &(fs_info)->fs_state))) +static inline bool btrfs_is_shutdown (struct btrfs_fs_info *fs_info) +{ +return test_bit (BTRFS_FS_STATE_EMERGENCY_SHUTDOWN , &fs_info->fs_state); +} + +static inline void btrfs_force_shutdown (struct btrfs_fs_info *fs_info) +{ +/* +* Here we do not want to use handle_fs_error(), which will mark +* the fs read-only. +* Some call sites like shutdown ioctl will mark the fs shutdown +* when the fs is frozen. But thaw path will handle RO and RW fs +* differently. +* +* So here we only mark the fs error without flipping it RO. +*/ +WRITE_ONCE (fs_info->fs_error, -EIO ); +if (!test_and_set_bit (BTRFS_FS_STATE_EMERGENCY_SHUTDOWN , &fs_info->fs_state)) +btrfs_info (fs_info,"emergency shutdown" ); +} +/* * We use folio flag owner_2 to indicate there is an ordered extent with * unfinished IO.
fs/btrfs/messages.c +1 -0
@@ -23,6 +23,7 @@ static const char fs_state_chars[] = { [BTRFS_FS_STATE_NO_DATA_CSUMS] = 'C', [BTRFS_FS_STATE_SKIP_META_CSUMS] = 'S', [BTRFS_FS_STATE_LOG_CLEANUP_ERROR] = 'L', + [BTRFS_FS_STATE_EMERGENCY_SHUTDOWN] = 'E', };static void btrfs_state_to_string (const struct btrfs_fs_info *info,char *buf)
Signature · 1 line
2.50.0
PATCH v3 3/6 btrfs: reject file operations if in shutdown state
In reply to Qu WenruoThis includes the following callbacks of file_operations:
- read_iter()
- write_iter()
- mmap()
- open()
- remap_file_range()
- uring_cmd()
- splice_read()
This requires a small wrapper to do the extra shutdown check, then call the regular filemap_splice_read() function
This should reject most of the file operations on a shutdown btrfs.
The callback ioctl() is intentionally skipped, as ext4 doesn't do the shutdown check on ioctl() either, thus I believe there is some special require for ioctl() callback even if the fs is fully shutdown.
--- fs/btrfs/file.c | 25 ++++++++++++++++++++++++- fs/btrfs/ioctl.c | 3 +++ fs/btrfs/reflink.c | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-)
fs/btrfs/file.c +24 -1
@@ -1419,6 +1419,8 @@ ssize_t btrfs_do_write_iter(struct kiocb *iocb, struct iov_iter *from,struct btrfs_inode *inode =BTRFS_I (file_inode (file));ssize_t num_written, num_sync; +if (unlikely (btrfs_is_shutdown (inode->root->fs_info))) +return -EIO ;/* * If the fs flips readonly due to some impossible error, although we * have opened a file as writable, we have to stop this write operation
@@ -1981,6 +1983,8 @@ static int btrfs_file_mmap(struct file *filp, struct vm_area_struct *vma) {struct address_space *mapping = filp->f_mapping; +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (file_inode (filp))))) +return -EIO ;if (!mapping->a_ops->read_folio)return -ENOEXEC ;
@@ -3040,6 +3044,9 @@ static long btrfs_fallocate(struct file *file, int mode,int blocksize =BTRFS_I (inode)->root->fs_info->sectorsize;int ret; +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (inode)))) +return -EIO ; +/* Do not allow fallocate in ZONED mode */ if (btrfs_is_zoned (inode_to_fs_info (inode)))return -EOPNOTSUPP ;
@@ -3731,6 +3738,9 @@ static int btrfs_file_open(struct inode *inode, struct file *filp) {int ret; +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (inode)))) +return -EIO ; + filp->f_mode |=FMODE_NOWAIT |FMODE_CAN_ODIRECT ; ret =fsverity_file_open (inode, filp);
@@ -3743,6 +3753,9 @@ static ssize_t btrfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) {ssize_t ret =0 ; +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (file_inode (iocb->ki_filp))))) +return -EIO ; +if (iocb->ki_flags &IOCB_DIRECT ) { ret =btrfs_direct_read (iocb, to); if (ret < 0 || !iov_iter_count(to) ||
@@ -3753,10 +3766,20 @@ static ssize_t btrfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)return filemap_read (iocb, to, ret); } +static ssize_t btrfs_file_splice_read (struct file *in,loff_t *ppos, +struct pipe_inode_info *pipe, +size_t len,unsigned int flags) +{ +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (file_inode (in))))) +return -EIO ; + +return filemap_splice_read (in, ppos, pipe, len, flags); +} +const struct file_operations btrfs_file_operations = { .llseek = btrfs_file_llseek, .read_iter = btrfs_file_read_iter, - .splice_read = filemap_splice_read, + .splice_read = btrfs_file_splice_read, .write_iter = btrfs_file_write_iter, .splice_write = iter_file_splice_write, .mmap = btrfs_file_mmap,
fs/btrfs/ioctl.c +3 -0
@@ -5048,6 +5048,9 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issuint btrfs_uring_cmd (struct io_uring_cmd *cmd,unsigned int issue_flags) { +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (file_inode (cmd->file))))) +return -EIO ; +switch (cmd->cmd_op) {case BTRFS_IOC_ENCODED_READ :#if defined(CONFIG_64BIT ) && defined(CONFIG_COMPAT )
fs/btrfs/reflink.c +3 -0
@@ -869,6 +869,9 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off,bool same_inode = dst_inode == src_inode;int ret; +if (unlikely (btrfs_is_shutdown (inode_to_fs_info (file_inode (src_file))))) +return -EIO ; +if (remap_flags & ~(REMAP_FILE_DEDUP |REMAP_FILE_ADVISORY ))return -EINVAL ;
Signature · 1 line
2.50.0
PATCH v3 4/6 btrfs: reject delalloc ranges if in shutdown state
In reply to Qu WenruoIf the filesystem has dirty pages before the fs is shutdown, we should no longer write them back, instead should treat them as writeback error.
Handle such situation by marking all those delalloc range as error and let error handling path to clean them up.
For ranges that already have ordered extent created, let them continue the writeback, and at ordered io finish time the file extent item update will be rejected as the fs is already marked error.
--- fs/btrfs/inode.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)
fs/btrfs/inode.c +13 -1
@@ -861,6 +861,9 @@ static void compress_file_range(struct btrfs_work *work)int compress_type = fs_info->compress_type;int compress_level = fs_info->compress_level; +if (unlikely (btrfs_is_shutdown (fs_info))) + goto cleanup_and_bail_uncompressed; +inode_should_defrag (inode, start, end, end - start +1 ,SZ_16K );/*
@@ -1276,6 +1279,11 @@ static noinline int cow_file_range(struct btrfs_inode *inode,unsigned long page_ops;int ret =0 ; +if (unlikely (btrfs_is_shutdown (fs_info))) { + ret = -EIO ; + goto out_unlock; + } +if (btrfs_is_free_space_inode (inode)) { ret = -EINVAL ; goto out_unlock;
@@ -2026,7 +2034,7 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, {struct btrfs_fs_info *fs_info = inode->root->fs_info;struct btrfs_root *root = inode->root; -struct btrfs_path *path; +struct btrfs_path *path =NULL ;u64 cow_start = (u64)-1 ;/* * If not 0, represents the inclusive end of the last fallback_to_cow()
@@ -2046,6 +2054,10 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,*/ ASSERT (!btrfs_is_zoned (fs_info) ||btrfs_is_data_reloc_root (root)); +if (unlikely (btrfs_is_shutdown (fs_info))) { + ret = -EIO ; + goto error; + } path =btrfs_alloc_path ();if (!path) { ret = -ENOMEM ;
Signature · 1 line
2.50.0
PATCH v3 5/6 btrfs: implement shutdown ioctl
In reply to Qu WenruoThe shutdown ioctl should follow the XFS one, which use magic number 'X', and ioctl number 125, with a u32 as flags.
For now btrfs don't distinguish DEFAULT and LOGFLUSH flags (just like f2fs), both will freeze the fs first (implies committing the current transaction), setting the SHUTDOWN flag and finally thaw the fs.
For NOLOGFLUSH flag, the freeze/thaw part is skipped thus the current transaction is aborted.
The new shutdown ioctl is hidden behind experimental features for more testing.
--- fs/btrfs/ioctl.c | 40 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/btrfs.h | 9 +++++++++ 2 files changed, 49 insertions(+)
fs/btrfs/ioctl.c +40 -0
@@ -5194,6 +5194,36 @@ static int btrfs_ioctl_subvol_sync(struct btrfs_fs_info *fs_info, void __user *areturn 0 ; } +#ifdef CONFIG_BTRFS_EXPERIMENTAL +static int btrfs_emergency_shutdown (struct btrfs_fs_info *fs_info,u32 flags) +{ +int ret =0 ; + +if (flags >=BTRFS_SHUTDOWN_FLAGS_LAST ) +return -EINVAL ; + +if (btrfs_is_shutdown (fs_info)) +return 0 ; + +switch (flags) { +case BTRFS_SHUTDOWN_FLAGS_LOGFLUSH : +case BTRFS_SHUTDOWN_FLAGS_DEFAULT : + ret =freeze_super (fs_info->sb,FREEZE_HOLDER_KERNEL ,NULL ); +if (ret) +return ret; +btrfs_force_shutdown (fs_info); + ret =thaw_super (fs_info->sb,FREEZE_HOLDER_KERNEL ,NULL ); +if (ret) +return ret; +break ; +case BTRFS_SHUTDOWN_FLAGS_NOLOGFLUSH : +btrfs_force_shutdown (fs_info); +break ; + } +return ret; +} +#endif +long btrfs_ioctl (struct file *file,unsigned int cmd,unsigned long arg) {
@@ -5201,6 +5231,8 @@ long btrfs_ioctl(struct file *file, unsigned intstruct btrfs_fs_info *fs_info =inode_to_fs_info (inode);struct btrfs_root *root =BTRFS_I (inode)->root;void __user *argp = (void __user *)arg; +/* If @arg is just an unsigned long value. */ +unsigned long flags;switch (cmd) {case FS_IOC_GETVERSION :
@@ -5349,6 +5381,14 @@ long btrfs_ioctl(struct file *file, unsigned int#endif case BTRFS_IOC_SUBVOL_SYNC_WAIT :return btrfs_ioctl_subvol_sync (fs_info, argp); +#ifdef CONFIG_BTRFS_EXPERIMENTAL +case BTRFS_IOC_SHUTDOWN : +if (!capable (CAP_SYS_ADMIN )) +return -EPERM ; +if (get_user (flags, (__u32 __user *)arg)) +return -EFAULT ; +return btrfs_emergency_shutdown (fs_info, flags); +#endif }return -ENOTTY ;
include/uapi/linux/btrfs.h +9 -0
@@ -1096,6 +1096,12 @@ enum btrfs_err_code { BTRFS_ERROR_DEV_RAID1C4_MIN_NOT_MET, }; +/* Flags for IOC_SHUTDOWN, should match XFS' flags. */ +#define BTRFS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#define BTRFS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 +#define BTRFS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 +#define BTRFS_SHUTDOWN_FLAGS_LAST 0x3 +#define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \ struct btrfs_ioctl_vol_args)#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \
@@ -1217,6 +1223,9 @@ enum btrfs_err_code {#define BTRFS_IOC_SUBVOL_SYNC_WAIT _IOW(BTRFS_IOCTL_MAGIC, 65, \ struct btrfs_ioctl_subvol_wait) +/* Shutdown ioctl should follow XFS's interfaces, thus not using btrfs magic. */ +#define BTRFS_IOC_SHUTDOWN _IOR('X', 125, __u32) + #ifdef __cplusplus }#endif
Signature · 1 line
2.50.0
Re: [PATCH v3 5/6] btrfs: implement shutdown ioctl
Quoted 14 lines · Qu Wenruo
long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {fs/btrfs/ioctl.c +2
-0@@ -5201,6 +5231,8 @@ long btrfs_ioctl(struct file *file, unsigned intstruct btrfs_fs_info *fs_info =inode_to_fs_info (inode);struct btrfs_root *root =BTRFS_I (inode)->root;void __user *argp = (void __user *)arg; +/* If @arg is just an unsigned long value. */ +unsigned long flags;switch (cmd) {case FS_IOC_GETVERSION :@@ -5349,6 +5381,14 @@ long btrfs_ioctl(struct file *file, unsigned int#endif
fs/btrfs/ioctl.c long btrfs_ioctl(struct file *file, unsigned int · line 5391
Quoted 10 lines
fs/btrfs/ioctl.c +8
-0case BTRFS_IOC_SUBVOL_SYNC_WAIT :return btrfs_ioctl_subvol_sync (fs_info, argp); +#ifdef CONFIG_BTRFS_EXPERIMENTAL +case BTRFS_IOC_SHUTDOWN : +if (!capable (CAP_SYS_ADMIN )) +return -EPERM ; +if (get_user (flags, (__u32 __user *)arg)) +return -EFAULT ; +return btrfs_emergency_shutdown (fs_info, flags); +#endif
With that you'll get buildbot complaints if CONFIG_BTRFS_EXPERIMENTAL=n because flags is unused.
I'd probably put the get_user(flags, ...) into btrfs_emergency_shutdown() to silence the compiler.
Re: [PATCH v3 5/6] btrfs: implement shutdown ioctl
Quoted 29 lines · Johannes Thumshirn
Quoted 14 lines · Qu Wenruo
long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {fs/btrfs/ioctl.c +2
-0@@ -5201,6 +5231,8 @@ long btrfs_ioctl(struct file *file, unsigned intstruct btrfs_fs_info *fs_info =inode_to_fs_info (inode);struct btrfs_root *root =BTRFS_I (inode)->root;void __user *argp = (void __user *)arg; +/* If @arg is just an unsigned long value. */ +unsigned long flags;switch (cmd) {case FS_IOC_GETVERSION :@@ -5349,6 +5381,14 @@ long btrfs_ioctl(struct file *file, unsigned int#endif fs/btrfs/ioctl.c long btrfs_ioctl(struct file *file, unsigned int · line 5391
Quoted 10 lines
fs/btrfs/ioctl.c +8
-0case BTRFS_IOC_SUBVOL_SYNC_WAIT :return btrfs_ioctl_subvol_sync (fs_info, argp); +#ifdef CONFIG_BTRFS_EXPERIMENTAL +case BTRFS_IOC_SHUTDOWN : +if (!capable (CAP_SYS_ADMIN )) +return -EPERM ; +if (get_user (flags, (__u32 __user *)arg)) +return -EFAULT ; +return btrfs_emergency_shutdown (fs_info, flags); +#endif With that you'll get buildbot complaints if
CONFIG_BTRFS_EXPERIMENTAL=nbecause flags is unused.
Quoted 2 lines
I'd probably put the
get_user(flags, ...)intobtrfs_emergency_shutdown()to silence the compiler.
That sounds a lot better than all my alternatives (a dedicated {} block to put @flags into, get_user() at the beginning of btrfs_ioctl())
Will go your path when merging the patches.
Thanks,
Qu
PATCH v3 6/6 btrfs: implement remove_bdev super operation callback
In reply to Qu WenruoFor this callback, btrfs will:
- Go degraded if the fs can still maintain RW operations And of course mark the target device as missing.
- Shutdown if the fs can not maintain RW operations
To support the lookup from bdev to a btrfs_device, btrfs_dev_lookup_args is enhanced to have a new @devt member. If set, we should be able to use that @devt member to uniquely locating a btrfs device.
I know the shutdown can be a little overkilled, if one has a RAID1 metadata and RAID0 data, in that case one can still read data with 50% chance to got some good data.
But a filesystem returning -EIO for half of the time is not really considered usable.
Further it can also be as bad as the only device went missing for a single device btrfs.
So here we go safe other than sorry when handling missing device.
And the remove_bdev callback will be hidden behind experimental features for now, the reasons are:
- There are not enough btrfs specific bdev removal test cases The existing test cases are all removing the only device, thus only exercises the shutdown branch.
- Not yet determined what's the expected behavior
Although the current auto-degrade behavior is no worse than the old behavior, it may not always be what the end users want.
Before there is a concrete interface, better hide the new feature from end users.
--- fs/btrfs/super.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++ fs/btrfs/volumes.c | 2 ++ fs/btrfs/volumes.h | 5 ++++ 3 files changed, 64 insertions(+)
fs/btrfs/super.c +57 -0
@@ -2421,6 +2421,60 @@ static long btrfs_free_cached_objects(struct super_block *sb, struct shrink_contreturn 0 ; } +#ifdef CONFIG_BTRFS_EXPERIMENTAL +static void btrfs_remove_bdev (struct super_block *sb,struct block_device *bdev) +{ +struct btrfs_fs_info *fs_info =btrfs_sb (sb); +struct btrfs_device *device; +struct btrfs_dev_lookup_args lookup_args = { .devt = bdev->bd_dev }; +bool can_rw; + +mutex_lock (&fs_info->fs_devices->device_list_mutex); + device =btrfs_find_device (fs_info->fs_devices, &lookup_args); +if (!device) { +mutex_unlock (&fs_info->fs_devices->device_list_mutex); +btrfs_warn (fs_info,"unable to find btrfs device for block device '%pg'" , + bdev); +return ; + } +/* +* The to-be-removed device is already missing? +* +* That's weird but no special handling needed and can exit right now. +*/ +if (unlikely (test_and_set_bit (BTRFS_DEV_STATE_MISSING , &device->dev_state))) { +mutex_unlock (&fs_info->fs_devices->device_list_mutex); +btrfs_warn (fs_info,"btrfs device id %llu is already missing" , + device->devid); +return ; + } + + device->fs_devices->missing_devices++; +if (test_and_clear_bit (BTRFS_DEV_STATE_WRITEABLE , &device->dev_state)) { +list_del_init (&device->dev_alloc_list); +WARN_ON (device->fs_devices->rw_devices <1 ); + device->fs_devices->rw_devices--; + } + can_rw =btrfs_check_rw_degradable (fs_info, device); +mutex_unlock (&fs_info->fs_devices->device_list_mutex); +/* +* Now device is considered missing, btrfs_device_name() won't give a +* meaningful result anymore, so only output the devid. +*/ +if (!can_rw) { +btrfs_crit (fs_info, +"btrfs device id %llu has gone missing, can not maintain read-write" , + device->devid); +btrfs_force_shutdown (fs_info); +return ; + } +btrfs_warn (fs_info, +"btrfs device id %llu has gone missing, continue as degraded" , + device->devid); +btrfs_set_opt (fs_info->mount_opt,DEGRADED ); +} +#endif +static const struct super_operations btrfs_super_ops = { .drop_inode = btrfs_drop_inode, .evict_inode = btrfs_evict_inode,
@@ -2436,6 +2490,9 @@ static const struct super_operations btrfs_super_ops = { .unfreeze_fs = btrfs_unfreeze, .nr_cached_objects = btrfs_nr_cached_objects, .free_cached_objects = btrfs_free_cached_objects, +#ifdef CONFIG_BTRFS_EXPERIMENTAL + .remove_bdev = btrfs_remove_bdev, +#endif };static const struct file_operations btrfs_ctl_fops = {
fs/btrfs/volumes.c +2 -0
@@ -6792,6 +6792,8 @@ static bool dev_args_match_fs_devices(const struct btrfs_dev_lookup_args *args,static bool dev_args_match_device (const struct btrfs_dev_lookup_args *args,const struct btrfs_device *device) { +if (args->devt) +return device->devt == args->devt;if (args->missing) {if (test_bit (BTRFS_DEV_STATE_IN_FS_METADATA , &device->dev_state) && !device->bdev)
fs/btrfs/volumes.h +5 -0
@@ -663,6 +663,11 @@ struct btrfs_dev_lookup_args {u64 devid;u8 *uuid;u8 *fsid; +/* +* If devt is specified, all other members will be ignored as it is +* enough to uniquely locate a device. +*/ +dev_t devt;bool missing; };
Signature · 1 line
2.50.0