diff mbox series

btrfs-progs: remove loopback device resolution

Message ID 6094201431aa981c6e0d149b6d528bc4b7a5af91.1737020580.git.wqu@suse.com (mailing list archive)
State New
Headers show
Series btrfs-progs: remove loopback device resolution | expand

Commit Message

Qu Wenruo Jan. 16, 2025, 9:43 a.m. UTC
[BUG]
mkfs.btrfs has a built-in loopback device resolution, to avoid the same
file being added to the same fs, using loopback device and the file
itself.

But it has one big bug:

- It doesn't detect partition on loopback devices correctly
  The function is_loop_device() only utilize major number to detect a
  loopback device.
  But partitions on loopback devices doesn't use the same major number
  as the loopback device:

  NAME            MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
  loop0             7:0    0    5G  0 loop
  |-loop0p1       259:3    0  128M  0 part
  `-loop0p2       259:4    0  4.9G  0 part

  Thus `/dev/loop0p1` will not be treated as a loopback device, thus it
  will not even resolve the source file.

  And this can not even be fixed, as if we do extra "/dev/loop*" based
  file lookup, `/dev/loop0p1` and `/dev/loop0p2` will resolve to the
  same source file, and refuse to mkfs on two different partitions.

[FIX]
The loopback file detection is the baby sitting that no one asks for.

Just as I explained, it only brings new bugs, and we will never fix all
ways that an experienced user can come up with.
And I didn't see any other mkfs tool doing such baby sitting.

So remove the loopback file resolution, just regular is_same_blk_file()
is good enough.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 common/open-utils.c |   7 ++-
 common/path-utils.c | 127 +-------------------------------------------
 common/path-utils.h |   2 +-
 3 files changed, 5 insertions(+), 131 deletions(-)

Comments

David Sterba Jan. 16, 2025, 11:42 a.m. UTC | #1
On Thu, Jan 16, 2025 at 08:13:01PM +1030, Qu Wenruo wrote:
> [BUG]
> mkfs.btrfs has a built-in loopback device resolution, to avoid the same
> file being added to the same fs, using loopback device and the file
> itself.
> 
> But it has one big bug:
> 
> - It doesn't detect partition on loopback devices correctly
>   The function is_loop_device() only utilize major number to detect a
>   loopback device.
>   But partitions on loopback devices doesn't use the same major number
>   as the loopback device:
> 
>   NAME            MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
>   loop0             7:0    0    5G  0 loop
>   |-loop0p1       259:3    0  128M  0 part
>   `-loop0p2       259:4    0  4.9G  0 part
> 
>   Thus `/dev/loop0p1` will not be treated as a loopback device, thus it
>   will not even resolve the source file.
> 
>   And this can not even be fixed, as if we do extra "/dev/loop*" based
>   file lookup, `/dev/loop0p1` and `/dev/loop0p2` will resolve to the
>   same source file, and refuse to mkfs on two different partitions.
> 
> [FIX]
> The loopback file detection is the baby sitting that no one asks for.
> 
> Just as I explained, it only brings new bugs, and we will never fix all
> ways that an experienced user can come up with.
> And I didn't see any other mkfs tool doing such baby sitting.
> 
> So remove the loopback file resolution, just regular is_same_blk_file()
> is good enough.

The loop device resolution had some bugs in the past and was added for a
reason that's not mentioned in the changelogs.

The commits have some details why it's done, they also mention that
partitioned devices are resolved so it's not that there's no support for
that.

0cf3b78f404b01 ("btrfs-progs: Fix partitioned loop devices resolving")
abdb0ced0123d4 ("Btrfs-progs: fix resolving of loop devices")
09559bfe7bcd43 ("multidevice support for check_mounted")

and some fixup commits.

So before removing the code completely I'd like to see if there's a use
case that can be broken, but I don't have anything in particular.
There's only mkfs-tests/006-partitioned-loopdev that's quite simple and
does not try to do any tricks with symlinks or partitions on the
devices.
Qu Wenruo Jan. 16, 2025, 8:52 p.m. UTC | #2
在 2025/1/16 22:12, David Sterba 写道:
> On Thu, Jan 16, 2025 at 08:13:01PM +1030, Qu Wenruo wrote:
>> [BUG]
>> mkfs.btrfs has a built-in loopback device resolution, to avoid the same
>> file being added to the same fs, using loopback device and the file
>> itself.
>>
>> But it has one big bug:
>>
>> - It doesn't detect partition on loopback devices correctly
>>    The function is_loop_device() only utilize major number to detect a
>>    loopback device.
>>    But partitions on loopback devices doesn't use the same major number
>>    as the loopback device:
>>
>>    NAME            MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
>>    loop0             7:0    0    5G  0 loop
>>    |-loop0p1       259:3    0  128M  0 part
>>    `-loop0p2       259:4    0  4.9G  0 part
>>
>>    Thus `/dev/loop0p1` will not be treated as a loopback device, thus it
>>    will not even resolve the source file.
>>
>>    And this can not even be fixed, as if we do extra "/dev/loop*" based
>>    file lookup, `/dev/loop0p1` and `/dev/loop0p2` will resolve to the
>>    same source file, and refuse to mkfs on two different partitions.
>>
>> [FIX]
>> The loopback file detection is the baby sitting that no one asks for.
>>
>> Just as I explained, it only brings new bugs, and we will never fix all
>> ways that an experienced user can come up with.
>> And I didn't see any other mkfs tool doing such baby sitting.
>>
>> So remove the loopback file resolution, just regular is_same_blk_file()
>> is good enough.
>
> The loop device resolution had some bugs in the past and was added for a
> reason that's not mentioned in the changelogs.
>
> The commits have some details why it's done, they also mention that
> partitioned devices are resolved so it's not that there's no support for
> that.
>
> 0cf3b78f404b01 ("btrfs-progs: Fix partitioned loop devices resolving")
> abdb0ced0123d4 ("Btrfs-progs: fix resolving of loop devices")

The problem of those two fixes are pretty simple, they just do not work
in the first place.

resolve_loop_device() is only triggered if is_loop_dev() returns >0 values.

But as I explained, resolve_loop_device() won't return >0 if a
partitioned loopback device is passed.


So why those patches are introduced in the first place?

Thanks,
Qu
> 09559bfe7bcd43 ("multidevice support for check_mounted")
>
> and some fixup commits.
>
> So before removing the code completely I'd like to see if there's a use
> case that can be broken, but I don't have anything in particular.
> There's only mkfs-tests/006-partitioned-loopdev that's quite simple and
> does not try to do any tricks with symlinks or partitions on the
> devices.
>
David Sterba Jan. 17, 2025, 2:01 p.m. UTC | #3
On Fri, Jan 17, 2025 at 07:22:09AM +1030, Qu Wenruo wrote:
> 
> 
> 在 2025/1/16 22:12, David Sterba 写道:
> > On Thu, Jan 16, 2025 at 08:13:01PM +1030, Qu Wenruo wrote:
> >> [BUG]
> >> mkfs.btrfs has a built-in loopback device resolution, to avoid the same
> >> file being added to the same fs, using loopback device and the file
> >> itself.
> >>
> >> But it has one big bug:
> >>
> >> - It doesn't detect partition on loopback devices correctly
> >>    The function is_loop_device() only utilize major number to detect a
> >>    loopback device.
> >>    But partitions on loopback devices doesn't use the same major number
> >>    as the loopback device:
> >>
> >>    NAME            MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
> >>    loop0             7:0    0    5G  0 loop
> >>    |-loop0p1       259:3    0  128M  0 part
> >>    `-loop0p2       259:4    0  4.9G  0 part
> >>
> >>    Thus `/dev/loop0p1` will not be treated as a loopback device, thus it
> >>    will not even resolve the source file.
> >>
> >>    And this can not even be fixed, as if we do extra "/dev/loop*" based
> >>    file lookup, `/dev/loop0p1` and `/dev/loop0p2` will resolve to the
> >>    same source file, and refuse to mkfs on two different partitions.
> >>
> >> [FIX]
> >> The loopback file detection is the baby sitting that no one asks for.
> >>
> >> Just as I explained, it only brings new bugs, and we will never fix all
> >> ways that an experienced user can come up with.
> >> And I didn't see any other mkfs tool doing such baby sitting.
> >>
> >> So remove the loopback file resolution, just regular is_same_blk_file()
> >> is good enough.
> >
> > The loop device resolution had some bugs in the past and was added for a
> > reason that's not mentioned in the changelogs.
> >
> > The commits have some details why it's done, they also mention that
> > partitioned devices are resolved so it's not that there's no support for
> > that.
> >
> > 0cf3b78f404b01 ("btrfs-progs: Fix partitioned loop devices resolving")
> > abdb0ced0123d4 ("Btrfs-progs: fix resolving of loop devices")
> 
> The problem of those two fixes are pretty simple, they just do not work
> in the first place.
> 
> resolve_loop_device() is only triggered if is_loop_dev() returns >0 values.
> 
> But as I explained, resolve_loop_device() won't return >0 if a
> partitioned loopback device is passed.
> 
> So why those patches are introduced in the first place?

I don't know, all I'm asking here is to keep protection against some
accidental use of loop device with mkfs. I looks similar to what we do
with regular block devices, the same device cannot be specified twice.
If the loop device is buggy then it'll be better to fix it then delete
it.
Qu Wenruo Jan. 17, 2025, 8:35 p.m. UTC | #4
在 2025/1/18 00:31, David Sterba 写道:
> On Fri, Jan 17, 2025 at 07:22:09AM +1030, Qu Wenruo wrote:
>>
>>
>> 在 2025/1/16 22:12, David Sterba 写道:
>>> On Thu, Jan 16, 2025 at 08:13:01PM +1030, Qu Wenruo wrote:
>>>> [BUG]
>>>> mkfs.btrfs has a built-in loopback device resolution, to avoid the same
>>>> file being added to the same fs, using loopback device and the file
>>>> itself.
>>>>
>>>> But it has one big bug:
>>>>
>>>> - It doesn't detect partition on loopback devices correctly
>>>>     The function is_loop_device() only utilize major number to detect a
>>>>     loopback device.
>>>>     But partitions on loopback devices doesn't use the same major number
>>>>     as the loopback device:
>>>>
>>>>     NAME            MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
>>>>     loop0             7:0    0    5G  0 loop
>>>>     |-loop0p1       259:3    0  128M  0 part
>>>>     `-loop0p2       259:4    0  4.9G  0 part
>>>>
>>>>     Thus `/dev/loop0p1` will not be treated as a loopback device, thus it
>>>>     will not even resolve the source file.
>>>>
>>>>     And this can not even be fixed, as if we do extra "/dev/loop*" based
>>>>     file lookup, `/dev/loop0p1` and `/dev/loop0p2` will resolve to the
>>>>     same source file, and refuse to mkfs on two different partitions.
>>>>
>>>> [FIX]
>>>> The loopback file detection is the baby sitting that no one asks for.
>>>>
>>>> Just as I explained, it only brings new bugs, and we will never fix all
>>>> ways that an experienced user can come up with.
>>>> And I didn't see any other mkfs tool doing such baby sitting.
>>>>
>>>> So remove the loopback file resolution, just regular is_same_blk_file()
>>>> is good enough.
>>>
>>> The loop device resolution had some bugs in the past and was added for a
>>> reason that's not mentioned in the changelogs.
>>>
>>> The commits have some details why it's done, they also mention that
>>> partitioned devices are resolved so it's not that there's no support for
>>> that.
>>>
>>> 0cf3b78f404b01 ("btrfs-progs: Fix partitioned loop devices resolving")
>>> abdb0ced0123d4 ("Btrfs-progs: fix resolving of loop devices")
>>
>> The problem of those two fixes are pretty simple, they just do not work
>> in the first place.
>>
>> resolve_loop_device() is only triggered if is_loop_dev() returns >0 values.
>>
>> But as I explained, resolve_loop_device() won't return >0 if a
>> partitioned loopback device is passed.
>>
>> So why those patches are introduced in the first place?
>
> I don't know, all I'm asking here is to keep protection against some
> accidental use of loop device with mkfs. I looks similar to what we do
> with regular block devices, the same device cannot be specified twice.

That's fine with or without the loopback device detection.

We have duplicated device detection already.

Just try this:

# losetup /dev/loop0 test.img
# mkfs.btrfs -f /dev/loop0 test.img
ERROR: skipping duplicate device /dev/loop0 in the filesystem
ERROR: not enough free space to allocate chunk

This only triggers the duplicated device code, not any loopback related
warning.

> If the loop device is buggy then it'll be better to fix it then delete
> it.
>

As I already mentioned in the commit message, even if I fix
is_loop_device(), then it will cause more problems because we do the
stupid idea of resolving the source file.

Consider this case:

/dev/loop0
|- /dev/loop0p1   BTRFS  /mnt/data
|- /dev/loop0p2

Currently we just do not treat /dev/loop0p1/p2 as loop back devices, but
regular block devices.

If we treat them as loop devices (by flawed ideas like checking
"/dev/loop*"), then at check_mounted_where(), /devloop0p1 will be
resolved to the source file, and treated as the whole source file being
mounted at /mnt/data.

Then if we try to mkfs on /dev/loop0p2, we also do the source file
resolution, and find that both p1 and p2 are backed by the same file,
thus unable to mkfs on p2.


Loop device is not only buggy, but flawed in design by day 1.

You need stronger argument to prove the necessary of this feature,
especially no other filesystem do such check at mkfs.

Thanks,
Qu
diff mbox series

Patch

diff --git a/common/open-utils.c b/common/open-utils.c
index 8490be4af070..a292177691e7 100644
--- a/common/open-utils.c
+++ b/common/open-utils.c
@@ -36,8 +36,7 @@ 
 #include "common/open-utils.h"
 
 /*
- * Check if a file is used (directly or indirectly via a loop device) by a
- * device in fs_devices
+ * Check if a file is used  by a device in fs_devices
  */
 static int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices,
 		const char* file)
@@ -46,7 +45,7 @@  static int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices,
 	struct btrfs_device *device;
 
 	list_for_each_entry(device, &fs_devices->devices, dev_list) {
-		if((ret = is_same_loop_file(device->name, file)))
+		if((ret = is_same_blk_file(device->name, file)))
 			return ret;
 	}
 
@@ -94,7 +93,7 @@  int check_mounted_where(int fd, const char *file, char *where, int size,
 			else if(!ret)
 				continue;
 
-			ret = is_same_loop_file(file, mnt->mnt_fsname);
+			ret = is_same_blk_file(file, mnt->mnt_fsname);
 		}
 
 		if(ret < 0)
diff --git a/common/path-utils.c b/common/path-utils.c
index 04861d1668cb..3030ddc47f0c 100644
--- a/common/path-utils.c
+++ b/common/path-utils.c
@@ -107,87 +107,11 @@  int path_exists(const char *path)
 	return 1;
 }
 
-/* checks if a device is a loop device */
-static int is_loop_device(const char *device)
-{
-	struct stat statbuf;
-
-	if(stat(device, &statbuf) < 0)
-		return -errno;
-
-	return (S_ISBLK(statbuf.st_mode) &&
-		MAJOR(statbuf.st_rdev) == LOOP_MAJOR);
-}
-
-/*
- * Takes a loop device path (e.g. /dev/loop0) and returns
- * the associated file (e.g. /images/my_btrfs.img) using
- * loopdev API
- */
-static int resolve_loop_device_with_loopdev(const char* loop_dev, char* loop_file)
-{
-	int fd;
-	int ret;
-	struct loop_info64 lo64;
-
-	fd = open(loop_dev, O_RDONLY | O_NONBLOCK);
-	if (fd < 0)
-		return -errno;
-	ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
-	if (ret < 0) {
-		ret = -errno;
-		goto out;
-	}
-
-	memcpy(loop_file, lo64.lo_file_name, sizeof(lo64.lo_file_name));
-	loop_file[sizeof(lo64.lo_file_name)] = 0;
-
-out:
-	close(fd);
-
-	return ret;
-}
-
-/*
- * Takes a loop device path (e.g. /dev/loop0) and returns
- * the associated file (e.g. /images/my_btrfs.img)
- */
-static int resolve_loop_device(const char* loop_dev, char* loop_file,
-		int max_len)
-{
-	int ret;
-	FILE *f;
-	char fmt[20];
-	char p[PATH_MAX];
-	char real_loop_dev[PATH_MAX];
-
-	if (!realpath(loop_dev, real_loop_dev))
-		return -errno;
-	snprintf(p, PATH_MAX, "/sys/block/%s/loop/backing_file", strrchr(real_loop_dev, '/'));
-	if (!(f = fopen(p, "r"))) {
-		if (errno == ENOENT)
-			/*
-			 * It's possibly a partitioned loop device, which is
-			 * resolvable with loopdev API.
-			 */
-			return resolve_loop_device_with_loopdev(loop_dev, loop_file);
-		return -errno;
-	}
-
-	snprintf(fmt, 20, "%%%i[^\n]", max_len - 1);
-	ret = fscanf(f, fmt, loop_file);
-	fclose(f);
-	if (ret == EOF)
-		return -errno;
-
-	return 0;
-}
-
 /*
  * Checks whether a and b are identical or device
  * files associated with the same block device
  */
-static int is_same_blk_file(const char* a, const char* b)
+int is_same_blk_file(const char* a, const char* b)
 {
 	struct stat st_buf_a, st_buf_b;
 	char real_a[PATH_MAX];
@@ -224,55 +148,6 @@  static int is_same_blk_file(const char* a, const char* b)
 	return 0;
 }
 
-/*
- * Checks if a and b are identical or device files associated with the same
- * block device or if one file is a loop device that uses the other file.
- */
-int is_same_loop_file(const char *a, const char *b)
-{
-	char res_a[PATH_MAX];
-	char res_b[PATH_MAX];
-	const char* final_a = NULL;
-	const char* final_b = NULL;
-	int ret;
-
-	/* Resolve a if it is a loop device */
-	if ((ret = is_loop_device(a)) < 0) {
-		if (ret == -ENOENT)
-			return 0;
-		return ret;
-	} else if (ret) {
-		ret = resolve_loop_device(a, res_a, sizeof(res_a));
-		if (ret < 0) {
-			if (errno != EPERM)
-				return ret;
-		} else {
-			final_a = res_a;
-		}
-	} else {
-		final_a = a;
-	}
-
-	/* Resolve b if it is a loop device */
-	if ((ret = is_loop_device(b)) < 0) {
-		if (ret == -ENOENT)
-			return 0;
-		return ret;
-	} else if (ret) {
-		ret = resolve_loop_device(b, res_b, sizeof(res_b));
-		if (ret < 0) {
-			if (errno != EPERM)
-				return ret;
-		} else {
-			final_b = res_b;
-		}
-	} else {
-		final_b = b;
-	}
-
-	return is_same_blk_file(final_a, final_b);
-}
-
 /* Checks if a file exists and is a block or regular file*/
 int path_is_reg_or_block_device(const char *filename)
 {
diff --git a/common/path-utils.h b/common/path-utils.h
index 558fa21adfa1..6efcd81cf860 100644
--- a/common/path-utils.h
+++ b/common/path-utils.h
@@ -31,7 +31,7 @@  int path_is_a_mount_point(const char *file);
 int path_exists(const char *file);
 int path_is_reg_file(const char *path);
 int path_is_dir(const char *path);
-int is_same_loop_file(const char *a, const char *b);
+int is_same_blk_file(const char* a, const char* b);
 int path_is_reg_or_block_device(const char *filename);
 int path_is_in_dir(const char *parent, const char *path);
 char *path_basename(char *path);