diff mbox series

btrfs: free device without BTRFS_MAGIC

Message ID dbc067b24194241f6d87b8f9799d9b6484984a13.1600473987.git.anand.jain@oracle.com (mailing list archive)
State New, archived
Headers show
Series btrfs: free device without BTRFS_MAGIC | expand

Commit Message

Anand Jain Sept. 19, 2020, 2:52 a.m. UTC
Many things can happen after the device is scanned and before the device
is mounted.

One such thing is losing the BTRFS_MAGIC on the device.

If it happens we still won't free that device from the memory and causes
the userland to confuse.

For example: As the BTRFS_IOC_DEV_INFO still carries the device path which
does not have the BTRFS_MAGIC, the btrfs fi show still shows device
which does not belong. As shown below.

mkfs.btrfs -fq -draid1 -mraid1 /dev/sda /dev/sdb

wipefs -a /dev/sdb
mount -o degraded /dev/sda /btrfs
btrfs fi show -m

/dev/sdb does not contain BTRFS_MAGIC and we still show it as part of
btrfs.
Label: none  uuid: 470ec6fb-646b-4464-b3cb-df1b26c527bd
        Total devices 2 FS bytes used 128.00KiB
        devid    1 size 3.00GiB used 571.19MiB path /dev/sda
        devid    2 size 3.00GiB used 571.19MiB path /dev/sdb

Fix is to return -ENODATA error code in btrfs_read_dev_one_super()
when BTRFS_MAGIC check fails, so that its parent open_fs_devices()
shall free the device in the mount-thread.

Signed-off-by: Anand Jain <anand.jain@oracle.com>
---
Now the fstests btrfs/198 pass with this fix.

 fs/btrfs/disk-io.c |  2 +-
 fs/btrfs/volumes.c | 19 +++++++++++++------
 2 files changed, 14 insertions(+), 7 deletions(-)

Comments

Johannes Thumshirn Sept. 21, 2020, 9:44 a.m. UTC | #1
On 19/09/2020 04:53, Anand Jain wrote:
> Many things can happen after the device is scanned and before the device
> is mounted.
> 
> One such thing is losing the BTRFS_MAGIC on the device.
> 
> If it happens we still won't free that device from the memory and causes
> the userland to confuse.
> 
> For example: As the BTRFS_IOC_DEV_INFO still carries the device path which
> does not have the BTRFS_MAGIC, the btrfs fi show still shows device
> which does not belong. As shown below.
> 
> mkfs.btrfs -fq -draid1 -mraid1 /dev/sda /dev/sdb
> 
> wipefs -a /dev/sdb
> mount -o degraded /dev/sda /btrfs
> btrfs fi show -m
> 
> /dev/sdb does not contain BTRFS_MAGIC and we still show it as part of
> btrfs.
> Label: none  uuid: 470ec6fb-646b-4464-b3cb-df1b26c527bd
>         Total devices 2 FS bytes used 128.00KiB
>         devid    1 size 3.00GiB used 571.19MiB path /dev/sda
>         devid    2 size 3.00GiB used 571.19MiB path /dev/sdb
> 
> Fix is to return -ENODATA error code in btrfs_read_dev_one_super()
> when BTRFS_MAGIC check fails, so that its parent open_fs_devices()
> shall free the device in the mount-thread.
> 
> Signed-off-by: Anand Jain <anand.jain@oracle.com>
> ---
> Now the fstests btrfs/198 pass with this fix.
> 
>  fs/btrfs/disk-io.c |  2 +-
>  fs/btrfs/volumes.c | 19 +++++++++++++------
>  2 files changed, 14 insertions(+), 7 deletions(-)
> 
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 160b485d2cc0..9c91d12530a6 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -3432,7 +3432,7 @@ struct btrfs_super_block *btrfs_read_dev_one_super(struct block_device *bdev,
>  	if (btrfs_super_bytenr(super) != bytenr ||
>  		    btrfs_super_magic(super) != BTRFS_MAGIC) {
>  		btrfs_release_disk_super(super);
> -		return ERR_PTR(-EINVAL);
> +		return ERR_PTR(-ENODATA);

Is ENODATA the return value we've settled in the discussion? Sorry I 
haven't followed it closely.

>  	}
>  
>  	return super;
> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
> index 7cc677a7e544..ec9dac40b4f1 100644
> --- a/fs/btrfs/volumes.c
> +++ b/fs/btrfs/volumes.c
> @@ -1198,17 +1198,24 @@ static int open_fs_devices(struct btrfs_fs_devices *fs_devices,
>  {
>  	struct btrfs_device *device;
>  	struct btrfs_device *latest_dev = NULL;
> +	struct btrfs_device *tmp_device;
>  
>  	flags |= FMODE_EXCL;
>  
> -	list_for_each_entry(device, &fs_devices->devices, dev_list) {
> -		/* Just open everything we can; ignore failures here */
> -		if (btrfs_open_one_device(fs_devices, device, flags, holder))
> -			continue;
> +	list_for_each_entry_safe(device, tmp_device, &fs_devices->devices,
> +				 dev_list) {
> +		int ret;
>  
> -		if (!latest_dev ||
> -		    device->generation > latest_dev->generation)
> +		/* Just open everything we can; ignore failures here */
> +		ret = btrfs_open_one_device(fs_devices, device, flags, holder);
> +		if (ret == 0 && (!latest_dev ||
> +		    device->generation > latest_dev->generation)) {
>  			latest_dev = device;
> +		} else if (ret == -ENODATA) {
> +			fs_devices->num_devices--;
> +			list_del(&device->dev_list);
> +			btrfs_free_device(device);
> +		}
>  	}
>  	if (fs_devices->open_devices == 0)
>  		return -EINVAL;
> 


The code itself looks good I think, at least I haven't spotted anything
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Johannes Thumshirn Sept. 21, 2020, 10:52 a.m. UTC | #2
On 19/09/2020 04:53, Anand Jain wrote:
> Fix is to return -ENODATA error code in btrfs_read_dev_one_super()
> when BTRFS_MAGIC check fails, so that its parent open_fs_devices()
> shall free the device in the mount-thread.

But now it doesn't only fail if the BTRFS_MAGIC check failed but also,
if the offset of the superblock doesn't match the offset for the copy.

Sorry for not spotting this earlier.
Anand Jain Sept. 21, 2020, 11:19 a.m. UTC | #3
On 21/9/20 6:52 pm, Johannes Thumshirn wrote:
> On 19/09/2020 04:53, Anand Jain wrote:
>> Fix is to return -ENODATA error code in btrfs_read_dev_one_super()
>> when BTRFS_MAGIC check fails, so that its parent open_fs_devices()
>> shall free the device in the mount-thread.
> 
> But now it doesn't only fail if the BTRFS_MAGIC check failed but also,
> if the offset of the superblock doesn't match the offset for the copy.
> 
> Sorry for not spotting this earlier.
> 


Here are the links to the older comments.

  https://patchwork.kernel.org/patch/11177085/
  https://patchwork.kernel.org/patch/11177081/

I am not sure if -ENODATA is ok. It is open to comments. If it is not ok
then suggestions better alternative will help.

You are right I didn't intend to include btrfs_super_bytenr(super) != 
bytenr under -ENODATA thanks for spotting. Will fix.
diff mbox series

Patch

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 160b485d2cc0..9c91d12530a6 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3432,7 +3432,7 @@  struct btrfs_super_block *btrfs_read_dev_one_super(struct block_device *bdev,
 	if (btrfs_super_bytenr(super) != bytenr ||
 		    btrfs_super_magic(super) != BTRFS_MAGIC) {
 		btrfs_release_disk_super(super);
-		return ERR_PTR(-EINVAL);
+		return ERR_PTR(-ENODATA);
 	}
 
 	return super;
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 7cc677a7e544..ec9dac40b4f1 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -1198,17 +1198,24 @@  static int open_fs_devices(struct btrfs_fs_devices *fs_devices,
 {
 	struct btrfs_device *device;
 	struct btrfs_device *latest_dev = NULL;
+	struct btrfs_device *tmp_device;
 
 	flags |= FMODE_EXCL;
 
-	list_for_each_entry(device, &fs_devices->devices, dev_list) {
-		/* Just open everything we can; ignore failures here */
-		if (btrfs_open_one_device(fs_devices, device, flags, holder))
-			continue;
+	list_for_each_entry_safe(device, tmp_device, &fs_devices->devices,
+				 dev_list) {
+		int ret;
 
-		if (!latest_dev ||
-		    device->generation > latest_dev->generation)
+		/* Just open everything we can; ignore failures here */
+		ret = btrfs_open_one_device(fs_devices, device, flags, holder);
+		if (ret == 0 && (!latest_dev ||
+		    device->generation > latest_dev->generation)) {
 			latest_dev = device;
+		} else if (ret == -ENODATA) {
+			fs_devices->num_devices--;
+			list_del(&device->dev_list);
+			btrfs_free_device(device);
+		}
 	}
 	if (fs_devices->open_devices == 0)
 		return -EINVAL;