diff --git a/bin/groot b/bin/groot index 4033705..b2cc2ab 100755 --- a/bin/groot +++ b/bin/groot @@ -22,7 +22,8 @@ TOUCH=touch CUT=cut SORT=sort UNIQ=uniq -GREP=grep +CAT=cat +READLINK=readlink MOUNTS_FILE=/proc/self/mounts NOT_EXISTING_FILE=103 @@ -38,11 +39,18 @@ function chroot_teardown() { # Remove all mounts starting from the most nested ones. # Suffix the CHROOTDIR with / to avoid umounting directories not belonging # to CHROOTDIR. - for mp in $($GREP "${CHROOTDIR%/}/" $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) + local normalized_chrootdir="$($READLINK -f ${CHROOTDIR})/" + local final_res=0 + for mp in $($CAT $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) do - $UMOUNT $mp + if [[ $mp =~ ^${normalized_chrootdir}.* ]] + then + $UMOUNT $mp || final_res=$? + fi done $UMOUNT ${CHROOTDIR%/} + + return $final_res } function chroot_maybe_add_mount() { @@ -79,10 +87,32 @@ function chroot_setup() { fi create_node "${host_path}" "${CHROOTDIR}${guest_path}" - $MOUNT --rbind "${host_path}" "${CHROOTDIR}${guest_path}" + mount_directory "${host_path}" "${guest_path}" done } +function mount_directory() { + local host_path=$($READLINK -f "$1") + local guest_path="$2" + + if ! $OPT_AVOID_BIND + then + $MOUNT $OPT_BIND "${host_path}" "${CHROOTDIR}${guest_path}" + return 0 + fi + + case "$host_path" in + /proc) $MOUNT proc "${CHROOTDIR}${guest_path}" -t proc ;; + /sys) $MOUNT sys "${CHROOTDIR}${guest_path}" -t sysfs ;; + /dev) $MOUNT udev "${CHROOTDIR}${guest_path}" -t devtmpfs; $MOUNT devpts "${guest_path}/pts" -t devpts; $MOUNT shm "${guest_path}/shm" -t tmpfs ;; + /run) $MOUNT run "${CHROOTDIR}${guest_path}" -t tmpfs ;; + /tmp) $MOUNT tmp "${CHROOTDIR}${guest_path}" -t tmpfs ;; + *) $MOUNT $OPT_BIND "${host_path}" "${CHROOTDIR}${guest_path}" ;; + esac + + return 0 +} + function create_node() { local src="$1" local dst="$2" @@ -118,9 +148,19 @@ Options: path in the guest rootfs but users can specify any other location with the syntax: -b :. This option can be invoked multiple times and the paths specified must be absolutes. + -n, --no-umount Do not umount after chroot session finished. + -r, --recursive + Use rbind instead of bind. + + -i, --avoid-bind + Attempt to avoid mount --bind for common directories and use + proper mount fstype instead. Detected directories with + corresponding fstype are: /proc (proc), /sys (sysfs), + /dev (devtmpfs), /tmp (tmpfs), /run (tmpfs). + -h, --help Print this help message -V, --version Show the $NAME version @@ -135,16 +175,20 @@ version() { } function parse_arguments() { - OPT_BIND=false BINDINGS=() OPT_NO_UMOUNT=false + OPT_RECURSIVE=false + OPT_BIND="--bind" + OPT_AVOID_BIND=false OPT_HELP=false OPT_VERSION=false for opt in "$@" do case "$1" in - -b|--bind) OPT_BIND=true ; shift ; BINDINGS+=("$1") ; shift ;; + -b|--bind) shift ; BINDINGS+=("$1") ; shift ;; -n|--no-umount) OPT_NO_UMOUNT=true ; shift ;; + -r|--recursive) OPT_BIND="--rbind" ; shift ;; + -i|--avoid-bind) OPT_AVOID_BIND=true ; shift ;; -h|--help) OPT_HELP=true ; shift ;; -V|--version) OPT_VERSION=true ; shift ;; -*) die "Invalid option $1" ;; diff --git a/lib/core/build.sh b/lib/core/build.sh index 497e928..794298e 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -73,7 +73,7 @@ function build_image_env(){ sudo bash -c "echo LANG=\"en_US.UTF-8\" >> ${maindir}/root/etc/locale.conf" info "Setting up the pacman keyring (this might take a while!)..." - sudo ${maindir}/root/opt/junest/bin/groot ${maindir}/root bash -c \ + sudo ${maindir}/root/opt/junest/bin/groot -b /dev ${maindir}/root bash -c \ "pacman-key --init; pacman-key --populate archlinux; [ -e /etc/pacman.d/gnupg/S.gpg-agent ] && gpg-connect-agent -S /etc/pacman.d/gnupg/S.gpg-agent killagent /bye" sudo rm ${maindir}/root/var/cache/pacman/pkg/* diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index 9f4596f..f472a4c 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -57,9 +57,9 @@ function _run_env_with_namespace(){ # will terminate too with its own mounted directories. if [[ "$1" != "" ]] then - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT -n $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT --no-umount --recursive $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" else - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT -n $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT --no-umount --recursive $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" fi } diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh index c7b5f36..784452d 100755 --- a/tests/unit-tests/test-groot.sh +++ b/tests/unit-tests/test-groot.sh @@ -89,14 +89,14 @@ function test_groot_with_bind(){ assertCommandSuccess main -b /tmp chrootdir [[ -d chrootdir/tmp ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_file(){ touch file_src assertCommandSuccess main -b ${PWD}/file_src:/file_src chrootdir [[ -f chrootdir/file_src ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_not_existing_node(){ assertCommandFailOnStatus $NOT_EXISTING_FILE main -b ${PWD}/file_src:/file_src chrootdir @@ -111,7 +111,7 @@ function test_groot_with_bind_guest_host(){ assertCommandSuccess main -b /tmp:/home/tmp chrootdir [[ -d chrootdir/home/tmp ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_multiple_bind(){ assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir @@ -119,7 +119,7 @@ function test_groot_with_multiple_bind(){ assertEquals 0 $? [[ -d chrootdir/dev ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nmount(--bind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_command(){ assertCommandSuccess main chrootdir ls -la -h @@ -131,7 +131,7 @@ function test_groot_with_bind_and_command(){ assertEquals 0 $? [[ -d chrootdir/dev ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nmount(--bind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" } function test_groot_with_bind_no_umount(){ assertCommandSuccess main -n chrootdir @@ -147,6 +147,23 @@ umount(/home/mychroot/dev/shm) umount(/home/mychroot/dev) umount(/home/mychroot)")" "$(cat $STDOUTF)" } + +function test_groot_with_chroot_teardown_umount_failure(){ + function umount() { + echo "umount($@)" + [[ "$1" == "/home/mychroot/dev/shm" ]] && return 128 + return 0 + } + UMOUNT=umount + echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts + MOUNTS_FILE=./mounts + CHROOTDIR=/home/mychroot assertCommandFailOnStatus 128 chroot_teardown + assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) +umount(/home/mychroot/proc) +umount(/home/mychroot/dev/shm) +umount(/home/mychroot/dev) +umount(/home/mychroot)")" "$(cat $STDOUTF)" +} function test_groot_with_chroot_teardown_with_trailing_slash(){ echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts MOUNTS_FILE=./mounts @@ -158,4 +175,56 @@ umount(/home/mychroot/dev) umount(/home/mychroot)")" "$(cat $STDOUTF)" } +function test_groot_with_rbind(){ + assertCommandSuccess main -r -b /tmp chrootdir + [[ -d chrootdir/tmp ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_proc(){ + assertCommandSuccess main -i -b /proc chrootdir + [[ -d chrootdir/proc ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(proc chrootdir/proc -t proc)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_dev(){ + assertCommandSuccess main -i -b /dev chrootdir + [[ -d chrootdir/dev ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(udev chrootdir/dev -t devtmpfs)\nmount(devpts /dev/pts -t devpts)\nmount(shm /dev/shm -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_sys(){ + assertCommandSuccess main -i -b /sys chrootdir + [[ -d chrootdir/sys ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(sys chrootdir/sys -t sysfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_run(){ + assertCommandSuccess main -i -b /run chrootdir + [[ -d chrootdir/run ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(run chrootdir/run -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_tmp(){ + assertCommandSuccess main -i -b /tmp chrootdir + [[ -d chrootdir/tmp ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(tmp chrootdir/tmp -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + +function test_groot_with_avoid_bind_combined(){ + assertCommandSuccess main -i -b /tmp -b /usr chrootdir + cat $STDERRF + [[ -d chrootdir/tmp ]] + assertEquals 0 $? + [[ -d chrootdir/usr ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(tmp chrootdir/tmp -t tmpfs)\nmount(--bind /usr chrootdir/usr)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} + source $(dirname $0)/../utils/shunit2 diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh index 9cc60a7..811cfef 100755 --- a/tests/unit-tests/test-namespace.sh +++ b/tests/unit-tests/test-namespace.sh @@ -80,7 +80,7 @@ function test_is_user_namespace_enabled_with_config(){ function test_run_env_with_namespace() { assertCommandSuccess run_env_with_namespace "" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -88,7 +88,7 @@ function test_run_env_with_namespace() { function test_run_env_with_namespace_with_bindings() { assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -96,7 +96,7 @@ function test_run_env_with_namespace_with_bindings() { function test_run_env_with_namespace_with_command() { assertCommandSuccess run_env_with_namespace "" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -104,7 +104,7 @@ function test_run_env_with_namespace_with_command() { function test_run_env_with_namespace_with_bindings_and_command() { assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files