diff mbox series

btrfs-progs: filesystem-usage: handle missing seed device properly

Message ID 29a0e54c8461e3c25e63d5b7b3e48fa6f4254d3f.1676007519.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs-progs: filesystem-usage: handle missing seed device properly | expand

Commit Message

Qu Wenruo Feb. 10, 2023, 5:39 a.m. UTC
[BUG]
Test case btrfs/249 always fails since its introduction, the failure
comes from "btrfs filesystem usage" subcommand, and the error output
looks like this:

  QA output created by 249
  ERROR: unexpected number of devices: 1 >= 1
  ERROR: if seed device is used, try running this command as root
  FAILED: btrfs filesystem usage, ret 1. Check btrfs.ko and btrfs-progs version.
  (see /home/adam/xfstests/results//btrfs/249.full for details)

[CAUSE]
In function load_device_info(), we only allocate enough space for all
*RW* devices, expecting we can rule out all seed devices.

And in that function, we check if a device is a seed by checking its
super block fsid.

So if a seed device is missing (it can be an seed device without any
chunks on it, or a degraded RAID1 as seed), then we can not read the
super block.

In that case, we just assume it's not a seed device, and causing too
many devices than our expectation and cause the above failure.

[FIX]
Instead of unconditionally assume a missing device is not a seed, we add
a new safe net, is_seed_device_tree_search(), to search chunk tree and
determine if that device is a seed or not.

And if we found the device is still a seed, then just skip it as usual.

Now the test case btrfs/249 passes as expected:

  btrfs/249        2s
  Ran: btrfs/249
  Passed all 1 tests

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
This version is different from the original fix from Anand by:

- No need for kernel patching
  Thus no compatible problems

And also different from the fix from Flint:

- No need to search chunk tree unconditionally
  Tree search itself is a privileged operation while "filesystem usage"
  subcommand is not.

  Now we only needs root privilege if we hit a missing seed device,
  which is super rare.

  And we can still fallback to assume the device is not seed.

- Better commit message
---
 cmds/filesystem-usage.c | 72 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 70 insertions(+), 2 deletions(-)

Comments

Anand Jain Feb. 10, 2023, 4:01 p.m. UTC | #1
An alternative solution is to utilize a kernel interface to obtain the 
fsid [1]. Previous experiences have shown that attempting to directly 
read a mounted device's disk is not a reliable method and can result in 
various problems. As a result, it is advisable to use a kernel interface 
to read the fsid.

[PATCH 2/2] btrfs-progs: read fsid from the sysfs in device_is_seed
On 10/02/2023 13:39, Qu Wenruo wrote:
> [BUG]
> Test case btrfs/249 always fails since its introduction, the failure
> comes from "btrfs filesystem usage" subcommand, and the error output
> looks like this:
> 
>    QA output created by 249
>    ERROR: unexpected number of devices: 1 >= 1
>    ERROR: if seed device is used, try running this command as root
>    FAILED: btrfs filesystem usage, ret 1. Check btrfs.ko and btrfs-progs version.
>    (see /home/adam/xfstests/results//btrfs/249.full for details)
> 
> [CAUSE]
> In function load_device_info(), we only allocate enough space for all
> *RW* devices, expecting we can rule out all seed devices.
> 
> And in that function, we check if a device is a seed by checking its
> super block fsid.
> 
> So if a seed device is missing (it can be an seed device without any
> chunks on it, or a degraded RAID1 as seed), then we can not read the
> super block.
> 
> In that case, we just assume it's not a seed device, and causing too
> many devices than our expectation and cause the above failure.
> 
> [FIX]
> Instead of unconditionally assume a missing device is not a seed, we add
> a new safe net, is_seed_device_tree_search(), to search chunk tree and
> determine if that device is a seed or not.
> 
> And if we found the device is still a seed, then just skip it as usual.
> 
> Now the test case btrfs/249 passes as expected:
> 
>    btrfs/249        2s
>    Ran: btrfs/249
>    Passed all 1 tests
> 
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
> This version is different from the original fix from Anand by:
> 
> - No need for kernel patching
>    Thus no compatible problems
> 
> And also different from the fix from Flint:
> 
> - No need to search chunk tree unconditionally
>    Tree search itself is a privileged operation while "filesystem usage"
>    subcommand is not.
> 
>    Now we only needs root privilege if we hit a missing seed device,
>    which is super rare.
> 
>    And we can still fallback to assume the device is not seed.
> 
> - Better commit message
> ---
>   cmds/filesystem-usage.c | 72 +++++++++++++++++++++++++++++++++++++++--
>   1 file changed, 70 insertions(+), 2 deletions(-)
> 
> diff --git a/cmds/filesystem-usage.c b/cmds/filesystem-usage.c
> index 5810324f245e..214cad2fa75b 100644
> --- a/cmds/filesystem-usage.c
> +++ b/cmds/filesystem-usage.c
> @@ -700,6 +700,56 @@ out:
>   	return ret;
>   }
>   
> +/*
> + * Return 0 if this devid is not a seed device.
> + * Return 1 if this devid is a seed device.
> + * Return <0 if error (IO error or EPERM).
> + *
> + * Since this is done by tree search, it needs root privilege, and
> + * should not be triggered unless we hit a missing device and can not
> + * determine if it's a seed one.
> + */
> +static int is_seed_device_tree_search(int fd, u64 devid, u8 *fsid)
> +{
> +	struct btrfs_ioctl_search_args args = {0};
> +	struct btrfs_ioctl_search_key *sk = &args.key;
> +	struct btrfs_ioctl_search_header *sh;
> +	struct btrfs_dev_item *dev;
> +	unsigned long off = 0;
> +	int ret;
> +	int err;
> +
> +	sk->tree_id = BTRFS_CHUNK_TREE_OBJECTID;
> +	sk->min_objectid = BTRFS_DEV_ITEMS_OBJECTID;
> +	sk->max_objectid = BTRFS_DEV_ITEMS_OBJECTID;
> +	sk->min_type = BTRFS_DEV_ITEM_KEY;
> +	sk->max_type = BTRFS_DEV_ITEM_KEY;
> +	sk->min_offset = devid;
> +	sk->max_offset = devid;
> +	sk->max_transid = (u64)-1;
> +	sk->nr_items = 1;
> +
> +	ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
> +	err = errno;
> +	if (err == EPERM)
> +		return -err;
> +	if (ret < 0) {
> +		error("cannot lookup chunk tree info: %m");
> +		return ret;
> +	}
> +	/* No dev item found. */
> +	if (sk->nr_items == 0)
> +		return -ENOENT;
> +
> +	sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
> +	off += sizeof(*sh);
> +
> +	dev = (struct btrfs_dev_item *)(args.buf + off);
> +	if (memcmp(dev->fsid, fsid, BTRFS_UUID_SIZE) == 0)
> +		return 0;
> +	return 1;
> +}
> +
>   /*
>    *  This function loads the device_info structure and put them in an array
>    */
> @@ -708,7 +758,6 @@ static int load_device_info(int fd, struct device_info **devinfo_ret,
>   {
>   	int ret, i, ndevs;
>   	struct btrfs_ioctl_fs_info_args fi_args;
> -	struct btrfs_ioctl_dev_info_args dev_info;
>   	struct device_info *info;
>   	u8 fsid[BTRFS_UUID_SIZE];
>   
> @@ -730,6 +779,8 @@ static int load_device_info(int fd, struct device_info **devinfo_ret,
>   	}
>   
>   	for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) {
> +		struct btrfs_ioctl_dev_info_args dev_info = {0};
> +
>   		if (ndevs >= fi_args.num_devices) {
>   			error("unexpected number of devices: %d >= %llu", ndevs,
>   				fi_args.num_devices);
> @@ -737,7 +788,6 @@ static int load_device_info(int fd, struct device_info **devinfo_ret,
>   		"if seed device is used, try running this command as root");
>   			goto out;
>   		}
> -		memset(&dev_info, 0, sizeof(dev_info));
>   		ret = get_device_info(fd, i, &dev_info);
>   
>   		if (ret == -ENODEV)
> @@ -747,6 +797,24 @@ static int load_device_info(int fd, struct device_info **devinfo_ret,
>   			goto out;
>   		}
>   
> +		/*
> +		 * A missing device, we can not determing if it's a seed
> +		 * device by reading its super block.
> +		 * Thus we have to go tree-search to make sure if it's a seed
> +		 * device.
> +		 */
> +		if (!dev_info.path[0]) {
> +			ret = is_seed_device_tree_search(fd, i, fi_args.fsid);
> +			if (ret < 0) {
> +				errno = -ret;
> +				warning(
> +		"unable to determine if devid %u is seed: %m, assuming not", i);
> +			}
> +			/* Skip the missing seed device. */
> +			if (ret > 0)
> +				continue;
> +		}
> +
>   		/*
>   		 * Skip seed device by checking device's fsid (requires root).
>   		 * And we will skip only if dev_to_fsid is successful and dev
Anand Jain Feb. 10, 2023, 4:07 p.m. UTC | #2
OR

We could use both methods to accommodate older kernels; switch to disk 
reading if newer sysfs interface is unavailable.

Thanks, Anand

On 11/02/2023 00:01, Anand Jain wrote:
> 
> 
> 
> 
> An alternative solution is to utilize a kernel interface to obtain the 
> fsid [1]. Previous experiences have shown that attempting to directly 
> read a mounted device's disk is not a reliable method and can result in 
> various problems. As a result, it is advisable to use a kernel interface 
> to read the fsid.
> 
> [PATCH 2/2] btrfs-progs: read fsid from the sysfs in device_is_seed
> On 10/02/2023 13:39, Qu Wenruo wrote:
>> [BUG]
>> Test case btrfs/249 always fails since its introduction, the failure
>> comes from "btrfs filesystem usage" subcommand, and the error output
>> looks like this:
>>
>>    QA output created by 249
>>    ERROR: unexpected number of devices: 1 >= 1
>>    ERROR: if seed device is used, try running this command as root
>>    FAILED: btrfs filesystem usage, ret 1. Check btrfs.ko and 
>> btrfs-progs version.
>>    (see /home/adam/xfstests/results//btrfs/249.full for details)
>>
>> [CAUSE]
>> In function load_device_info(), we only allocate enough space for all
>> *RW* devices, expecting we can rule out all seed devices.
>>
>> And in that function, we check if a device is a seed by checking its
>> super block fsid.
>>
>> So if a seed device is missing (it can be an seed device without any
>> chunks on it, or a degraded RAID1 as seed), then we can not read the
>> super block.
>>
>> In that case, we just assume it's not a seed device, and causing too
>> many devices than our expectation and cause the above failure.
>>
>> [FIX]
>> Instead of unconditionally assume a missing device is not a seed, we add
>> a new safe net, is_seed_device_tree_search(), to search chunk tree and
>> determine if that device is a seed or not.
>>
>> And if we found the device is still a seed, then just skip it as usual.
>>
>> Now the test case btrfs/249 passes as expected:
>>
>>    btrfs/249        2s
>>    Ran: btrfs/249
>>    Passed all 1 tests
>>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>> This version is different from the original fix from Anand by:
>>
>> - No need for kernel patching
>>    Thus no compatible problems
>>
>> And also different from the fix from Flint:
>>
>> - No need to search chunk tree unconditionally
>>    Tree search itself is a privileged operation while "filesystem usage"
>>    subcommand is not.
>>
>>    Now we only needs root privilege if we hit a missing seed device,
>>    which is super rare.
>>
>>    And we can still fallback to assume the device is not seed.
>>
>> - Better commit message
>> ---
>>   cmds/filesystem-usage.c | 72 +++++++++++++++++++++++++++++++++++++++--
>>   1 file changed, 70 insertions(+), 2 deletions(-)
>>
>> diff --git a/cmds/filesystem-usage.c b/cmds/filesystem-usage.c
>> index 5810324f245e..214cad2fa75b 100644
>> --- a/cmds/filesystem-usage.c
>> +++ b/cmds/filesystem-usage.c
>> @@ -700,6 +700,56 @@ out:
>>       return ret;
>>   }
>> +/*
>> + * Return 0 if this devid is not a seed device.
>> + * Return 1 if this devid is a seed device.
>> + * Return <0 if error (IO error or EPERM).
>> + *
>> + * Since this is done by tree search, it needs root privilege, and
>> + * should not be triggered unless we hit a missing device and can not
>> + * determine if it's a seed one.
>> + */
>> +static int is_seed_device_tree_search(int fd, u64 devid, u8 *fsid)
>> +{
>> +    struct btrfs_ioctl_search_args args = {0};
>> +    struct btrfs_ioctl_search_key *sk = &args.key;
>> +    struct btrfs_ioctl_search_header *sh;
>> +    struct btrfs_dev_item *dev;
>> +    unsigned long off = 0;
>> +    int ret;
>> +    int err;
>> +
>> +    sk->tree_id = BTRFS_CHUNK_TREE_OBJECTID;
>> +    sk->min_objectid = BTRFS_DEV_ITEMS_OBJECTID;
>> +    sk->max_objectid = BTRFS_DEV_ITEMS_OBJECTID;
>> +    sk->min_type = BTRFS_DEV_ITEM_KEY;
>> +    sk->max_type = BTRFS_DEV_ITEM_KEY;
>> +    sk->min_offset = devid;
>> +    sk->max_offset = devid;
>> +    sk->max_transid = (u64)-1;
>> +    sk->nr_items = 1;
>> +
>> +    ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
>> +    err = errno;
>> +    if (err == EPERM)
>> +        return -err;
>> +    if (ret < 0) {
>> +        error("cannot lookup chunk tree info: %m");
>> +        return ret;
>> +    }
>> +    /* No dev item found. */
>> +    if (sk->nr_items == 0)
>> +        return -ENOENT;
>> +
>> +    sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
>> +    off += sizeof(*sh);
>> +
>> +    dev = (struct btrfs_dev_item *)(args.buf + off);
>> +    if (memcmp(dev->fsid, fsid, BTRFS_UUID_SIZE) == 0)
>> +        return 0;
>> +    return 1;
>> +}
>> +
>>   /*
>>    *  This function loads the device_info structure and put them in an 
>> array
>>    */
>> @@ -708,7 +758,6 @@ static int load_device_info(int fd, struct 
>> device_info **devinfo_ret,
>>   {
>>       int ret, i, ndevs;
>>       struct btrfs_ioctl_fs_info_args fi_args;
>> -    struct btrfs_ioctl_dev_info_args dev_info;
>>       struct device_info *info;
>>       u8 fsid[BTRFS_UUID_SIZE];
>> @@ -730,6 +779,8 @@ static int load_device_info(int fd, struct 
>> device_info **devinfo_ret,
>>       }
>>       for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) {
>> +        struct btrfs_ioctl_dev_info_args dev_info = {0};
>> +
>>           if (ndevs >= fi_args.num_devices) {
>>               error("unexpected number of devices: %d >= %llu", ndevs,
>>                   fi_args.num_devices);
>> @@ -737,7 +788,6 @@ static int load_device_info(int fd, struct 
>> device_info **devinfo_ret,
>>           "if seed device is used, try running this command as root");
>>               goto out;
>>           }
>> -        memset(&dev_info, 0, sizeof(dev_info));
>>           ret = get_device_info(fd, i, &dev_info);
>>           if (ret == -ENODEV)
>> @@ -747,6 +797,24 @@ static int load_device_info(int fd, struct 
>> device_info **devinfo_ret,
>>               goto out;
>>           }
>> +        /*
>> +         * A missing device, we can not determing if it's a seed
>> +         * device by reading its super block.
>> +         * Thus we have to go tree-search to make sure if it's a seed
>> +         * device.
>> +         */
>> +        if (!dev_info.path[0]) {
>> +            ret = is_seed_device_tree_search(fd, i, fi_args.fsid);
>> +            if (ret < 0) {
>> +                errno = -ret;
>> +                warning(
>> +        "unable to determine if devid %u is seed: %m, assuming not", i);
>> +            }
>> +            /* Skip the missing seed device. */
>> +            if (ret > 0)
>> +                continue;
>> +        }
>> +
>>           /*
>>            * Skip seed device by checking device's fsid (requires root).
>>            * And we will skip only if dev_to_fsid is successful and dev
>
Qu Wenruo Feb. 11, 2023, 1:05 a.m. UTC | #3
On 2023/2/11 00:07, Anand Jain wrote:
> 
> OR
> 
> We could use both methods to accommodate older kernels; switch to disk 
> reading if newer sysfs interface is unavailable.

I believe the tree-search is always needed as a fallback.

Thus the patch itself should always be needed.

For the sysfs part, the commit message has explained it in details, we 
need a proper way to get if a device is seed, from the IOC_DEV_INFO ioctl.

Thus indeed as you commented, if we can add an fsid field to 
btrfs_ioctl_dev_info_args, it can indeed solve the problem.

Thanks,
Qu
> 
> Thanks, Anand
> 
> On 11/02/2023 00:01, Anand Jain wrote:
>>
>>
>>
>>
>> An alternative solution is to utilize a kernel interface to obtain the 
>> fsid [1]. Previous experiences have shown that attempting to directly 
>> read a mounted device's disk is not a reliable method and can result 
>> in various problems. As a result, it is advisable to use a kernel 
>> interface to read the fsid.
>>
>> [PATCH 2/2] btrfs-progs: read fsid from the sysfs in device_is_seed
>> On 10/02/2023 13:39, Qu Wenruo wrote:
>>> [BUG]
>>> Test case btrfs/249 always fails since its introduction, the failure
>>> comes from "btrfs filesystem usage" subcommand, and the error output
>>> looks like this:
>>>
>>>    QA output created by 249
>>>    ERROR: unexpected number of devices: 1 >= 1
>>>    ERROR: if seed device is used, try running this command as root
>>>    FAILED: btrfs filesystem usage, ret 1. Check btrfs.ko and 
>>> btrfs-progs version.
>>>    (see /home/adam/xfstests/results//btrfs/249.full for details)
>>>
>>> [CAUSE]
>>> In function load_device_info(), we only allocate enough space for all
>>> *RW* devices, expecting we can rule out all seed devices.
>>>
>>> And in that function, we check if a device is a seed by checking its
>>> super block fsid.
>>>
>>> So if a seed device is missing (it can be an seed device without any
>>> chunks on it, or a degraded RAID1 as seed), then we can not read the
>>> super block.
>>>
>>> In that case, we just assume it's not a seed device, and causing too
>>> many devices than our expectation and cause the above failure.
>>>
>>> [FIX]
>>> Instead of unconditionally assume a missing device is not a seed, we add
>>> a new safe net, is_seed_device_tree_search(), to search chunk tree and
>>> determine if that device is a seed or not.
>>>
>>> And if we found the device is still a seed, then just skip it as usual.
>>>
>>> Now the test case btrfs/249 passes as expected:
>>>
>>>    btrfs/249        2s
>>>    Ran: btrfs/249
>>>    Passed all 1 tests
>>>
>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>> ---
>>> This version is different from the original fix from Anand by:
>>>
>>> - No need for kernel patching
>>>    Thus no compatible problems
>>>
>>> And also different from the fix from Flint:
>>>
>>> - No need to search chunk tree unconditionally
>>>    Tree search itself is a privileged operation while "filesystem usage"
>>>    subcommand is not.
>>>
>>>    Now we only needs root privilege if we hit a missing seed device,
>>>    which is super rare.
>>>
>>>    And we can still fallback to assume the device is not seed.
>>>
>>> - Better commit message
>>> ---
>>>   cmds/filesystem-usage.c | 72 +++++++++++++++++++++++++++++++++++++++--
>>>   1 file changed, 70 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/cmds/filesystem-usage.c b/cmds/filesystem-usage.c
>>> index 5810324f245e..214cad2fa75b 100644
>>> --- a/cmds/filesystem-usage.c
>>> +++ b/cmds/filesystem-usage.c
>>> @@ -700,6 +700,56 @@ out:
>>>       return ret;
>>>   }
>>> +/*
>>> + * Return 0 if this devid is not a seed device.
>>> + * Return 1 if this devid is a seed device.
>>> + * Return <0 if error (IO error or EPERM).
>>> + *
>>> + * Since this is done by tree search, it needs root privilege, and
>>> + * should not be triggered unless we hit a missing device and can not
>>> + * determine if it's a seed one.
>>> + */
>>> +static int is_seed_device_tree_search(int fd, u64 devid, u8 *fsid)
>>> +{
>>> +    struct btrfs_ioctl_search_args args = {0};
>>> +    struct btrfs_ioctl_search_key *sk = &args.key;
>>> +    struct btrfs_ioctl_search_header *sh;
>>> +    struct btrfs_dev_item *dev;
>>> +    unsigned long off = 0;
>>> +    int ret;
>>> +    int err;
>>> +
>>> +    sk->tree_id = BTRFS_CHUNK_TREE_OBJECTID;
>>> +    sk->min_objectid = BTRFS_DEV_ITEMS_OBJECTID;
>>> +    sk->max_objectid = BTRFS_DEV_ITEMS_OBJECTID;
>>> +    sk->min_type = BTRFS_DEV_ITEM_KEY;
>>> +    sk->max_type = BTRFS_DEV_ITEM_KEY;
>>> +    sk->min_offset = devid;
>>> +    sk->max_offset = devid;
>>> +    sk->max_transid = (u64)-1;
>>> +    sk->nr_items = 1;
>>> +
>>> +    ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
>>> +    err = errno;
>>> +    if (err == EPERM)
>>> +        return -err;
>>> +    if (ret < 0) {
>>> +        error("cannot lookup chunk tree info: %m");
>>> +        return ret;
>>> +    }
>>> +    /* No dev item found. */
>>> +    if (sk->nr_items == 0)
>>> +        return -ENOENT;
>>> +
>>> +    sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
>>> +    off += sizeof(*sh);
>>> +
>>> +    dev = (struct btrfs_dev_item *)(args.buf + off);
>>> +    if (memcmp(dev->fsid, fsid, BTRFS_UUID_SIZE) == 0)
>>> +        return 0;
>>> +    return 1;
>>> +}
>>> +
>>>   /*
>>>    *  This function loads the device_info structure and put them in 
>>> an array
>>>    */
>>> @@ -708,7 +758,6 @@ static int load_device_info(int fd, struct 
>>> device_info **devinfo_ret,
>>>   {
>>>       int ret, i, ndevs;
>>>       struct btrfs_ioctl_fs_info_args fi_args;
>>> -    struct btrfs_ioctl_dev_info_args dev_info;
>>>       struct device_info *info;
>>>       u8 fsid[BTRFS_UUID_SIZE];
>>> @@ -730,6 +779,8 @@ static int load_device_info(int fd, struct 
>>> device_info **devinfo_ret,
>>>       }
>>>       for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) {
>>> +        struct btrfs_ioctl_dev_info_args dev_info = {0};
>>> +
>>>           if (ndevs >= fi_args.num_devices) {
>>>               error("unexpected number of devices: %d >= %llu", ndevs,
>>>                   fi_args.num_devices);
>>> @@ -737,7 +788,6 @@ static int load_device_info(int fd, struct 
>>> device_info **devinfo_ret,
>>>           "if seed device is used, try running this command as root");
>>>               goto out;
>>>           }
>>> -        memset(&dev_info, 0, sizeof(dev_info));
>>>           ret = get_device_info(fd, i, &dev_info);
>>>           if (ret == -ENODEV)
>>> @@ -747,6 +797,24 @@ static int load_device_info(int fd, struct 
>>> device_info **devinfo_ret,
>>>               goto out;
>>>           }
>>> +        /*
>>> +         * A missing device, we can not determing if it's a seed
>>> +         * device by reading its super block.
>>> +         * Thus we have to go tree-search to make sure if it's a seed
>>> +         * device.
>>> +         */
>>> +        if (!dev_info.path[0]) {
>>> +            ret = is_seed_device_tree_search(fd, i, fi_args.fsid);
>>> +            if (ret < 0) {
>>> +                errno = -ret;
>>> +                warning(
>>> +        "unable to determine if devid %u is seed: %m, assuming not", 
>>> i);
>>> +            }
>>> +            /* Skip the missing seed device. */
>>> +            if (ret > 0)
>>> +                continue;
>>> +        }
>>> +
>>>           /*
>>>            * Skip seed device by checking device's fsid (requires root).
>>>            * And we will skip only if dev_to_fsid is successful and dev
>>
diff mbox series

Patch

diff --git a/cmds/filesystem-usage.c b/cmds/filesystem-usage.c
index 5810324f245e..214cad2fa75b 100644
--- a/cmds/filesystem-usage.c
+++ b/cmds/filesystem-usage.c
@@ -700,6 +700,56 @@  out:
 	return ret;
 }
 
+/*
+ * Return 0 if this devid is not a seed device.
+ * Return 1 if this devid is a seed device.
+ * Return <0 if error (IO error or EPERM).
+ *
+ * Since this is done by tree search, it needs root privilege, and
+ * should not be triggered unless we hit a missing device and can not
+ * determine if it's a seed one.
+ */
+static int is_seed_device_tree_search(int fd, u64 devid, u8 *fsid)
+{
+	struct btrfs_ioctl_search_args args = {0};
+	struct btrfs_ioctl_search_key *sk = &args.key;
+	struct btrfs_ioctl_search_header *sh;
+	struct btrfs_dev_item *dev;
+	unsigned long off = 0;
+	int ret;
+	int err;
+
+	sk->tree_id = BTRFS_CHUNK_TREE_OBJECTID;
+	sk->min_objectid = BTRFS_DEV_ITEMS_OBJECTID;
+	sk->max_objectid = BTRFS_DEV_ITEMS_OBJECTID;
+	sk->min_type = BTRFS_DEV_ITEM_KEY;
+	sk->max_type = BTRFS_DEV_ITEM_KEY;
+	sk->min_offset = devid;
+	sk->max_offset = devid;
+	sk->max_transid = (u64)-1;
+	sk->nr_items = 1;
+
+	ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+	err = errno;
+	if (err == EPERM)
+		return -err;
+	if (ret < 0) {
+		error("cannot lookup chunk tree info: %m");
+		return ret;
+	}
+	/* No dev item found. */
+	if (sk->nr_items == 0)
+		return -ENOENT;
+
+	sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+	off += sizeof(*sh);
+
+	dev = (struct btrfs_dev_item *)(args.buf + off);
+	if (memcmp(dev->fsid, fsid, BTRFS_UUID_SIZE) == 0)
+		return 0;
+	return 1;
+}
+
 /*
  *  This function loads the device_info structure and put them in an array
  */
@@ -708,7 +758,6 @@  static int load_device_info(int fd, struct device_info **devinfo_ret,
 {
 	int ret, i, ndevs;
 	struct btrfs_ioctl_fs_info_args fi_args;
-	struct btrfs_ioctl_dev_info_args dev_info;
 	struct device_info *info;
 	u8 fsid[BTRFS_UUID_SIZE];
 
@@ -730,6 +779,8 @@  static int load_device_info(int fd, struct device_info **devinfo_ret,
 	}
 
 	for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) {
+		struct btrfs_ioctl_dev_info_args dev_info = {0};
+
 		if (ndevs >= fi_args.num_devices) {
 			error("unexpected number of devices: %d >= %llu", ndevs,
 				fi_args.num_devices);
@@ -737,7 +788,6 @@  static int load_device_info(int fd, struct device_info **devinfo_ret,
 		"if seed device is used, try running this command as root");
 			goto out;
 		}
-		memset(&dev_info, 0, sizeof(dev_info));
 		ret = get_device_info(fd, i, &dev_info);
 
 		if (ret == -ENODEV)
@@ -747,6 +797,24 @@  static int load_device_info(int fd, struct device_info **devinfo_ret,
 			goto out;
 		}
 
+		/*
+		 * A missing device, we can not determing if it's a seed
+		 * device by reading its super block.
+		 * Thus we have to go tree-search to make sure if it's a seed
+		 * device.
+		 */
+		if (!dev_info.path[0]) {
+			ret = is_seed_device_tree_search(fd, i, fi_args.fsid);
+			if (ret < 0) {
+				errno = -ret;
+				warning(
+		"unable to determine if devid %u is seed: %m, assuming not", i);
+			}
+			/* Skip the missing seed device. */
+			if (ret > 0)
+				continue;
+		}
+
 		/*
 		 * Skip seed device by checking device's fsid (requires root).
 		 * And we will skip only if dev_to_fsid is successful and dev