diff mbox series

[v4,2/3] btrfs-progs: inspect: Fix logical-resolve file path lookup

Message ID 20201127193035.19171-3-marcos@mpdesouza.com (mailing list archive)
State New, archived
Headers show
Series btrfs-progs: Fix logical-resolve | expand

Commit Message

Marcos Paulo de Souza Nov. 27, 2020, 7:30 p.m. UTC
From: Marcos Paulo de Souza <mpdesouza@suse.com>

[BUG]
logical-resolve is currently broken on systems that have a child
subvolume being mounted without access to the parent subvolume.
This is the default for SLE/openSUSE installations. openSUSE has the
subvolume '@' as the parent of all other subvolumes like /boot, /home.
The subvolume '@' is never mounted and accessed, but only it's childs:

mount | grep btrfs
/dev/sda2 on / type btrfs (rw,relatime,ssd,space_cache,subvolid=267,subvol=/@/.snapshots/1/snapshot)
/dev/sda2 on /opt type btrfs (rw,relatime,ssd,space_cache,subvolid=262,subvol=/@/opt)
/dev/sda2 on /boot/grub2/i386-pc type btrfs (rw,relatime,ssd,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc)

logical-resolve command calls btrfs_list_path_for_root, that returns the
subvolume full-path that corresponds to the tree id of the logical
address. As the name implies, the 'full-path' returns the subvolume full
path, starting from '@'. Later on, btrfs_open_dir is called using the path
returned, but it fails to resolve it since it contains the '@' and this
subvolume cannot be accessed.

The same problem can be triggered to any user that calls for
logical-resolve on a child subvolume that has the parent subvolume
not accessible.

Another problem in the current approach is that it believes that a
subvolume will be mounted in a directory with the same name e.g /@/boot
being mounted in /boot. When this is not true, the code also fails,
since it uses the subvolume name as the path.

[FIX]
Extent the find_mount_root function by allowing it to check for mnt_opts
member of mntent struct. Using this new approach we can change
logical-resolve command to search for subvolid=XXX,subvol=YYY returning
the correct path accessible to the user. Using this approach we can solve
the problems stated above by not trusting the subvolume name being the
mountpoint, and not executing the lookup based only in the subvolume
tree.

Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
---
 cmds/inspect.c | 44 ++++++++++++++++++++++++++++++++++----------
 common/utils.c | 29 +++++++++++++++++++++++------
 common/utils.h |  5 ++++-
 3 files changed, 61 insertions(+), 17 deletions(-)

Comments

Qu Wenruo Dec. 4, 2020, 12:08 a.m. UTC | #1
On 2020/11/28 上午3:30, Marcos Paulo de Souza wrote:
> From: Marcos Paulo de Souza <mpdesouza@suse.com>
> 
> [BUG]
> logical-resolve is currently broken on systems that have a child
> subvolume being mounted without access to the parent subvolume.
> This is the default for SLE/openSUSE installations. openSUSE has the
> subvolume '@' as the parent of all other subvolumes like /boot, /home.
> The subvolume '@' is never mounted and accessed, but only it's childs:
> 
> mount | grep btrfs
> /dev/sda2 on / type btrfs (rw,relatime,ssd,space_cache,subvolid=267,subvol=/@/.snapshots/1/snapshot)
> /dev/sda2 on /opt type btrfs (rw,relatime,ssd,space_cache,subvolid=262,subvol=/@/opt)
> /dev/sda2 on /boot/grub2/i386-pc type btrfs (rw,relatime,ssd,space_cache,subvolid=265,subvol=/@/boot/grub2/i386-pc)
> 
> logical-resolve command calls btrfs_list_path_for_root, that returns the
> subvolume full-path that corresponds to the tree id of the logical
> address. As the name implies, the 'full-path' returns the subvolume full
> path, starting from '@'. Later on, btrfs_open_dir is called using the path
> returned, but it fails to resolve it since it contains the '@' and this
> subvolume cannot be accessed.
> 
> The same problem can be triggered to any user that calls for
> logical-resolve on a child subvolume that has the parent subvolume
> not accessible.
> 
> Another problem in the current approach is that it believes that a
> subvolume will be mounted in a directory with the same name e.g /@/boot
> being mounted in /boot. When this is not true, the code also fails,
> since it uses the subvolume name as the path.
> 
> [FIX]
> Extent the find_mount_root function by allowing it to check for mnt_opts
> member of mntent struct. Using this new approach we can change
> logical-resolve command to search for subvolid=XXX,subvol=YYY returning
> the correct path accessible to the user. Using this approach we can solve
> the problems stated above by not trusting the subvolume name being the
> mountpoint, and not executing the lookup based only in the subvolume
> tree.
> 
> Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>


With the extra prompt for subvolume can't be accessed, it looks good to
me now.

Reviewed-by: Qu Wenruo <wqu@suse.com>

Thanks,
Qu
> ---
>  cmds/inspect.c | 44 ++++++++++++++++++++++++++++++++++----------
>  common/utils.c | 29 +++++++++++++++++++++++------
>  common/utils.h |  5 ++++-
>  3 files changed, 61 insertions(+), 17 deletions(-)
> 
> diff --git a/cmds/inspect.c b/cmds/inspect.c
> index 2530b904..cfa2f708 100644
> --- a/cmds/inspect.c
> +++ b/cmds/inspect.c
> @@ -236,6 +236,7 @@ static int cmd_inspect_logical_resolve(const struct cmd_struct *cmd,
>  		DIR *dirs = NULL;
>  
>  		if (getpath) {
> +			char mount_path[PATH_MAX];
>  			name = btrfs_list_path_for_root(fd, root);
>  			if (IS_ERR(name)) {
>  				ret = PTR_ERR(name);
> @@ -244,23 +245,46 @@ static int cmd_inspect_logical_resolve(const struct cmd_struct *cmd,
>  			if (!name) {
>  				path_ptr[-1] = '\0';
>  				path_fd = fd;
> +
> +				strncpy(mount_path, full_path, PATH_MAX);
>  			} else {
> -				path_ptr[-1] = '/';
> -				ret = snprintf(path_ptr, bytes_left, "%s",
> -						name);
> -				free(name);
> -				if (ret >= bytes_left) {
> -					error("path buffer too small: %d bytes",
> -							bytes_left - ret);
> -					goto out;
> +				char *mounted = NULL;
> +				char volid_str[PATH_MAX];
> +
> +				/*
> +				 * btrfs_list_path_for_root returns the full
> +				 * path to the subvolume pointed by root, but the
> +				 * subvolume can be mounted in a directory name
> +				 * different from the subvolume name. In this
> +				 * case we need to find the correct mountpoint
> +				 * using same subvol path and subvol id found
> +				 * before.
> +				 */
> +				snprintf(volid_str, PATH_MAX, "subvolid=%llu,subvol=/%s",
> +						root, name);
> +
> +				ret = find_mount_root(full_path, volid_str,
> +						BTRFS_FIND_ROOT_OPTS, &mounted);
> +
> +				if (ret == -ENOENT) {
> +					printf("inode %llu subvol %s could not be accessed: not mounted\n",
> +							inum, name);
> +					continue;
>  				}
> -				path_fd = btrfs_open_dir(full_path, &dirs, 1);
> +
> +				if (ret < 0)
> +					goto out;
> +
> +				strncpy(mount_path, mounted, PATH_MAX);
> +				free(mounted);
> +
> +				path_fd = btrfs_open_dir(mount_path, &dirs, 1);
>  				if (path_fd < 0) {
>  					ret = -ENOENT;
>  					goto out;
>  				}
>  			}
> -			ret = __ino_to_path_fd(inum, path_fd, full_path);
> +			ret = __ino_to_path_fd(inum, path_fd, mount_path);
>  			if (path_fd != fd)
>  				close_file_or_dir(path_fd, dirs);
>  		} else {
> diff --git a/common/utils.c b/common/utils.c
> index 1c264455..1562ac52 100644
> --- a/common/utils.c
> +++ b/common/utils.c
> @@ -1259,9 +1259,6 @@ int find_mount_root(const char *path, const char *data, u8 flag, char **mount_ro
>  	int longest_matchlen = 0;
>  	char *longest_match = NULL;
>  	char *cmp_field = NULL;
> -	bool found;
> -
> -	BUG_ON(flag != BTRFS_FIND_ROOT_PATH);
>  
>  	fd = open(path, O_RDONLY | O_NOATIME);
>  	if (fd < 0)
> @@ -1273,12 +1270,32 @@ int find_mount_root(const char *path, const char *data, u8 flag, char **mount_ro
>  		return -errno;
>  
>  	while ((ent = getmntent(mnttab))) {
> -		cmp_field = ent->mnt_dir;
> +		bool found = false;
>  
> -		len = strlen(cmp_field);
> +		/* BTRFS_FIND_ROOT_PATH is the default behavior */
> +		if (flag == BTRFS_FIND_ROOT_OPTS)
> +			cmp_field = ent->mnt_opts;
> +		else
> +			cmp_field = ent->mnt_dir;
>  
> -		found = strncmp(cmp_field, data, len) == 0;
> +		len = strlen(cmp_field);
>  
> +		if (flag == BTRFS_FIND_ROOT_OPTS) {
> +			size_t dlen = strlen(data);
> +			char *tmp_str = strstr(cmp_field, data);
> +			/*
> +			 * Make sure that we are dealing with the wanted string,
> +			 * since strstr returns the start of the string found.
> +			 * Compare the end string position from data with the
> +			 * mount point found, and make sure that we have an
> +			 * option separator or string end.
> +			 */
> +			if (tmp_str)
> +				found = tmp_str[dlen] == ',' ||
> +					tmp_str[dlen] == 0;
> +		} else {
> +			found = strncmp(cmp_field, data, len) == 0;
> +		}
>  		if (found) {
>  			/* match found and use the latest match */
>  			if (longest_matchlen <= len) {
> diff --git a/common/utils.h b/common/utils.h
> index 449e1d3e..b5d256c6 100644
> --- a/common/utils.h
> +++ b/common/utils.h
> @@ -54,7 +54,10 @@
>  
>  enum btrfs_find_root_flags {
>  	/* check mnt_dir of mntent */
> -	BTRFS_FIND_ROOT_PATH = 0
> +	BTRFS_FIND_ROOT_PATH = 0,
> +
> +	/* check mnt_opts of mntent */
> +	BTRFS_FIND_ROOT_OPTS
>  };
>  
>  void units_set_mode(unsigned *units, unsigned mode);
>
diff mbox series

Patch

diff --git a/cmds/inspect.c b/cmds/inspect.c
index 2530b904..cfa2f708 100644
--- a/cmds/inspect.c
+++ b/cmds/inspect.c
@@ -236,6 +236,7 @@  static int cmd_inspect_logical_resolve(const struct cmd_struct *cmd,
 		DIR *dirs = NULL;
 
 		if (getpath) {
+			char mount_path[PATH_MAX];
 			name = btrfs_list_path_for_root(fd, root);
 			if (IS_ERR(name)) {
 				ret = PTR_ERR(name);
@@ -244,23 +245,46 @@  static int cmd_inspect_logical_resolve(const struct cmd_struct *cmd,
 			if (!name) {
 				path_ptr[-1] = '\0';
 				path_fd = fd;
+
+				strncpy(mount_path, full_path, PATH_MAX);
 			} else {
-				path_ptr[-1] = '/';
-				ret = snprintf(path_ptr, bytes_left, "%s",
-						name);
-				free(name);
-				if (ret >= bytes_left) {
-					error("path buffer too small: %d bytes",
-							bytes_left - ret);
-					goto out;
+				char *mounted = NULL;
+				char volid_str[PATH_MAX];
+
+				/*
+				 * btrfs_list_path_for_root returns the full
+				 * path to the subvolume pointed by root, but the
+				 * subvolume can be mounted in a directory name
+				 * different from the subvolume name. In this
+				 * case we need to find the correct mountpoint
+				 * using same subvol path and subvol id found
+				 * before.
+				 */
+				snprintf(volid_str, PATH_MAX, "subvolid=%llu,subvol=/%s",
+						root, name);
+
+				ret = find_mount_root(full_path, volid_str,
+						BTRFS_FIND_ROOT_OPTS, &mounted);
+
+				if (ret == -ENOENT) {
+					printf("inode %llu subvol %s could not be accessed: not mounted\n",
+							inum, name);
+					continue;
 				}
-				path_fd = btrfs_open_dir(full_path, &dirs, 1);
+
+				if (ret < 0)
+					goto out;
+
+				strncpy(mount_path, mounted, PATH_MAX);
+				free(mounted);
+
+				path_fd = btrfs_open_dir(mount_path, &dirs, 1);
 				if (path_fd < 0) {
 					ret = -ENOENT;
 					goto out;
 				}
 			}
-			ret = __ino_to_path_fd(inum, path_fd, full_path);
+			ret = __ino_to_path_fd(inum, path_fd, mount_path);
 			if (path_fd != fd)
 				close_file_or_dir(path_fd, dirs);
 		} else {
diff --git a/common/utils.c b/common/utils.c
index 1c264455..1562ac52 100644
--- a/common/utils.c
+++ b/common/utils.c
@@ -1259,9 +1259,6 @@  int find_mount_root(const char *path, const char *data, u8 flag, char **mount_ro
 	int longest_matchlen = 0;
 	char *longest_match = NULL;
 	char *cmp_field = NULL;
-	bool found;
-
-	BUG_ON(flag != BTRFS_FIND_ROOT_PATH);
 
 	fd = open(path, O_RDONLY | O_NOATIME);
 	if (fd < 0)
@@ -1273,12 +1270,32 @@  int find_mount_root(const char *path, const char *data, u8 flag, char **mount_ro
 		return -errno;
 
 	while ((ent = getmntent(mnttab))) {
-		cmp_field = ent->mnt_dir;
+		bool found = false;
 
-		len = strlen(cmp_field);
+		/* BTRFS_FIND_ROOT_PATH is the default behavior */
+		if (flag == BTRFS_FIND_ROOT_OPTS)
+			cmp_field = ent->mnt_opts;
+		else
+			cmp_field = ent->mnt_dir;
 
-		found = strncmp(cmp_field, data, len) == 0;
+		len = strlen(cmp_field);
 
+		if (flag == BTRFS_FIND_ROOT_OPTS) {
+			size_t dlen = strlen(data);
+			char *tmp_str = strstr(cmp_field, data);
+			/*
+			 * Make sure that we are dealing with the wanted string,
+			 * since strstr returns the start of the string found.
+			 * Compare the end string position from data with the
+			 * mount point found, and make sure that we have an
+			 * option separator or string end.
+			 */
+			if (tmp_str)
+				found = tmp_str[dlen] == ',' ||
+					tmp_str[dlen] == 0;
+		} else {
+			found = strncmp(cmp_field, data, len) == 0;
+		}
 		if (found) {
 			/* match found and use the latest match */
 			if (longest_matchlen <= len) {
diff --git a/common/utils.h b/common/utils.h
index 449e1d3e..b5d256c6 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -54,7 +54,10 @@ 
 
 enum btrfs_find_root_flags {
 	/* check mnt_dir of mntent */
-	BTRFS_FIND_ROOT_PATH = 0
+	BTRFS_FIND_ROOT_PATH = 0,
+
+	/* check mnt_opts of mntent */
+	BTRFS_FIND_ROOT_OPTS
 };
 
 void units_set_mode(unsigned *units, unsigned mode);