btrfs-progs: receive: fix btrfs_mount_root substring bug
The current mount detection code in btrfs receive is not quite perfect. For example, suppose /tmp is mounted as a tmpfs. In that case, btrfs receive /tmp2 will find /tmp as the longest mount that matches a prefix of /tmp2 and blow up because it is not a btrfs filesystem, even if /tmp2 is just a directory in / mounted as btrfs. Fix this by replacing the substring check with a dirname recursion to only check the directories in the path of the dir, rather than every substring. Add a new test for this case. Signed-off-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
c11874ae81
commit
feb8f56ba2
|
@ -29,6 +29,7 @@
|
|||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include "common/path-utils.h"
|
||||
|
||||
/*
|
||||
|
@ -374,6 +375,39 @@ int path_is_dir(const char *path)
|
|||
return !!S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if a path is recursively contained in parent. Assumes parent and path
|
||||
* are null terminated absolute paths.
|
||||
*
|
||||
* Returns:
|
||||
* 0 - path not contained in parent
|
||||
* 1 - path contained in parent
|
||||
* < 0 - error
|
||||
*
|
||||
* e.g. (/, /foo) -> 1
|
||||
* (/foo, /) -> 0
|
||||
* (/foo, /foo/bar/baz) -> 1
|
||||
*/
|
||||
int path_is_in_dir(const char *parent, const char *path)
|
||||
{
|
||||
char *tmp = strdup(path);
|
||||
char *curr_dir = tmp;
|
||||
int ret;
|
||||
|
||||
while (strcmp(parent, curr_dir) != 0) {
|
||||
if (strcmp(curr_dir, "/") == 0) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
curr_dir = dirname(curr_dir);
|
||||
}
|
||||
ret = 1;
|
||||
|
||||
out:
|
||||
free(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy a path argument from SRC to DEST and check the SRC length if it's at
|
||||
* most PATH_MAX and fits into DEST. DESTLEN is supposed to be exact size of
|
||||
|
|
|
@ -37,6 +37,7 @@ 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 path_is_reg_or_block_device(const char *filename);
|
||||
int path_is_in_dir(const char *parent, const char *path);
|
||||
|
||||
int test_issubvolname(const char *name);
|
||||
|
||||
|
|
|
@ -1559,9 +1559,8 @@ int find_mount_root(const char *path, char **mount_root)
|
|||
return -errno;
|
||||
|
||||
while ((ent = getmntent(mnttab))) {
|
||||
len = strlen(ent->mnt_dir);
|
||||
if (strncmp(ent->mnt_dir, path, len) == 0) {
|
||||
/* match found and use the latest match */
|
||||
if (path_is_in_dir(ent->mnt_dir, path)) {
|
||||
len = strlen(ent->mnt_dir);
|
||||
if (longest_matchlen <= len) {
|
||||
free(longest_match);
|
||||
longest_matchlen = len;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Test some scenarios around the mount point we do receive onto.
|
||||
# Should fail in a non-btrfs filesystem, but succeed if a non btrfs filesystem
|
||||
# is the longest mounted substring of the target, but not the actual containing
|
||||
# mount.
|
||||
#
|
||||
# This is a regression test for
|
||||
# "btrfs-progs: receive: fix btrfs_mount_root substring bug"
|
||||
|
||||
source "$TEST_TOP/common"
|
||||
|
||||
check_prereq mkfs.btrfs
|
||||
check_prereq btrfs
|
||||
|
||||
setup_root_helper
|
||||
prepare_test_dev
|
||||
|
||||
run_check_mkfs_test_dev
|
||||
run_check_mount_test_dev
|
||||
|
||||
cd "$TEST_MNT"
|
||||
run_check $SUDO_HELPER mkdir "foo" "foobar"
|
||||
run_check $SUDO_HELPER mount -t tmpfs tmpfs "foo"
|
||||
run_check $SUDO_HELPER mkdir "foo/bar"
|
||||
|
||||
run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "subvol"
|
||||
run_check $SUDO_HELPER "$TOP/btrfs" subvolume snapshot -r "subvol" "snap"
|
||||
run_check $SUDO_HELPER "$TOP/btrfs" send -f send.data "snap"
|
||||
run_mustfail "no receive on tmpfs" $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foo"
|
||||
run_mustfail "no receive on tmpfs" $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foo/bar"
|
||||
run_check $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foobar"
|
||||
run_check_umount_test_dev "foo"
|
||||
|
||||
cd ..
|
||||
run_check_umount_test_dev
|
Loading…
Reference in New Issue