diff --git a/bin/groot b/bin/groot deleted file mode 100755 index cb4952f..0000000 --- a/bin/groot +++ /dev/null @@ -1,242 +0,0 @@ -#!/bin/bash -# -# This script is the simplified and portable version of arch-chroot -# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot) -# - -set -e - -# JUNEST_BASE can be overridden for testing purposes. -# There is no need for doing it for normal usage. -JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/..)}" -NAME='GRoot' -CMD='groot' -DESCRIPTION="I am $NAME!" -CHROOTCMD=${CHROOTCMD:-chroot} -SHELL="/bin/sh" -MOUNT=mount -UMOUNT=umount -MKDIR=mkdir -TOUCH=touch -CUT=cut -SORT=sort -UNIQ=uniq -CAT=cat -READLINK=readlink -MOUNTS_FILE=/proc/self/mounts - -NOT_EXISTING_FILE=103 -NOT_ABSOLUTE_PATH=111 -NO_ROOT_PRIVILEGES=110 - -source "${JUNEST_BASE}/lib/utils/utils.sh" - - -################################ MAIN FUNCTIONS ########################### - -function is_mountpoint() { - local mountpoint="$1" - for mp in $($CAT $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) - do - [[ $mp == $mountpoint ]] && return 0 - done - return 1 -} - -function chroot_teardown() { - # Remove all mounts starting from the most nested ones. - # Suffix the CHROOTDIR with / to avoid umounting directories not belonging - # to CHROOTDIR. - local normalized_chrootdir="$($READLINK -f ${CHROOTDIR})/" - local final_res=0 - for mp in $($CAT $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) - do - if [[ $mp =~ ^${normalized_chrootdir}.* ]] && is_mountpoint "$mp" - then - $UMOUNT $mp || final_res=$? - fi - done - $UMOUNT ${CHROOTDIR%/} - - return $final_res -} - -function chroot_maybe_add_mount() { - local cond=$1 - shift - if eval "$cond"; then - $MOUNT "$@" - return - fi - return 1 -} - -function chroot_setup() { - $OPT_NO_UMOUNT || check_and_trap 'chroot_teardown' QUIT EXIT ABRT KILL TERM INT - - if ! chroot_maybe_add_mount "! is_mountpoint '$CHROOTDIR'" --bind "$CHROOTDIR" "$CHROOTDIR" - then - warn "Failed mount of directories. $CHROOTDIR is already a mountpoint. Skipping it..." - return 0 - fi - - local re='(.*):(.*)' - for binds in ${BINDINGS[@]} - do - local host_path="" - local guest_path="" - if [[ $binds =~ $re ]] - then - local host_path="${BASH_REMATCH[1]}" - local guest_path="${BASH_REMATCH[2]}" - else - local host_path="$binds" - local guest_path="$binds" - fi - - create_node "${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" - if [[ ! -e $src ]] - then - die_on_status $NOT_EXISTING_FILE "${src} does not exist." - elif [[ $src != /* ]] - then - die_on_status $NOT_ABSOLUTE_PATH "${src} is not an absolute path." - elif [[ -f $src ]] - then - $TOUCH "$dst" - elif [[ -d $src ]] - then - $MKDIR -p "$dst" - fi -} - -function usage() { - cat < [command]] - -Options: - -b, --bind - Make the content of accessible in the guest rootfs. - - This option makes any file or directory of the host rootfs - accessible in the confined environment just as if it were part of - the guest rootfs. By default the host path is bound to the same - 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 - -If 'command' is unspecified, $CMD will launch $SHELL. - -EOF -} - -version() { - echo -e "$NAME $(cat $JUNEST_BASE/VERSION)" -} - -function parse_arguments() { - 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) 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" ;; - *) break ;; - esac - done - - if [[ ! -z $1 ]] - then - CHROOTDIR="$1" - shift - fi - ARGS=() - for arg in "$@" - do - ARGS+=("$arg") - done -} - -function is_user_root() { - (( EUID == 0 )) -} - -function execute_operation() { - $OPT_HELP && usage && return - $OPT_VERSION && version && return - - is_user_root || die_on_status $NO_ROOT_PRIVILEGES 'This script must be run with root privileges' - - [[ -d $CHROOTDIR ]] || die_on_status $NOT_EXISTING_FILE "Can't create chroot on non-directory $CHROOTDIR" - - chroot_setup "$CHROOTDIR" || die "Failed to setup chroot $CHROOTDIR" - - $CHROOTCMD "$CHROOTDIR" "${ARGS[@]}" -} - - -function main() { - parse_arguments "$@" - execute_operation -} - -main "$@" diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh deleted file mode 100755 index 5fb2580..0000000 --- a/tests/unit-tests/test-groot.sh +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/bash -source "$(dirname $0)/../utils/utils.sh" - -JUNEST_BASE="$(readlink -f $(dirname $(readlink -f "$0"))/../..)" - -# Disable the exiterr -set +e - -function oneTimeSetUp(){ - setUpUnitTests -} - -function setUp(){ - # Attempt to source the files under test to revert variable overrides - source $JUNEST_BASE/bin/groot -h &> /dev/null - set +e - - cwdSetUp - mkdir -p chrootdir - - init_mocks -} - -function tearDown(){ - cwdTearDown -} - -## Mock functions ## -function init_mocks() { - function usage(){ - echo "usage" - } - function is_user_root() { - return 0 - } - function chroot() { - echo "chroot($@)" - } - function mountpoint() { - echo "mountpoint($@)" - # As default suppose the mountpoint does not exist - return 1 - } - function mountpoint_mock() { - echo "mountpoint($@)" - } - function mount() { - echo "mount($@)" - } - function umount() { - echo "umount($@)" - } - function check_and_trap() { - echo "check_and_trap($@)" - } - - # As default suppose the mountpoint "chrootdir" does not exist - echo -e "1 /home/mychroot/dev\n" > ./mounts - MOUNTS_FILE=./mounts -} - -function test_help(){ - assertCommandSuccess main -h - assertEquals "usage" "$(cat $STDOUTF)" - assertCommandSuccess main --help - assertEquals "usage" "$(cat $STDOUTF)" -} -function test_version(){ - assertCommandSuccess main -V - assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" - assertCommandSuccess main --version - assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" -} -function test_groot_no_root(){ - is_user_root() { - return 1 - } - assertCommandFailOnStatus $NO_ROOT_PRIVILEGES main -} -function test_groot_no_directory(){ - assertCommandFailOnStatus $NOT_EXISTING_FILE main no-directory -} -function test_groot_mountpoint_exist(){ - echo -e "1 /home/mychroot/dev\n1 chrootdir\n" > ./mounts - MOUNTS_FILE=./mounts - assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_mountpoint_does_not_exist(){ - assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -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)\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)\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 - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_not_absolute_path_node(){ - touch file_src - assertCommandFailOnStatus $NOT_ABSOLUTE_PATH main -b file_src:/file_src chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" -} -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)\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 - [[ -d chrootdir/home/tmp ]] - assertEquals 0 $? - [[ -d chrootdir/dev ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\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 - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_and_command(){ - assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir ls -la -h - [[ -d chrootdir/home/tmp ]] - assertEquals 0 $? - [[ -d chrootdir/dev ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\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 - assertEquals "$(echo -e "mount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_chroot_teardown(){ - 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 assertCommandSuccess 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_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 - CHROOTDIR=/home/mychroot assertCommandSuccess 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_mountpoint_failure(){ - is_mountpoint() { - [[ $1 == "/home/mychroot/dev/shm" ]] && return 128 - return 0 - } - 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 assertCommandSuccess chroot_teardown - assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) -umount(/home/mychroot/proc) -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)\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)\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)\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)\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)\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)\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)\nmount(--bind chrootdir chrootdir)\nmount(tmp chrootdir/tmp -t tmpfs)\nmount(--bind /usr chrootdir/usr)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -source $(dirname $0)/../utils/shunit2