diff --git a/README.md b/README.md index bb99ccc..1181ea1 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ You can build a new JuNest image from scratch by running the following command: junest -b [-n] The script will create a directory containing all the essentials -files in order to make JuNest working properly (such as pacman, yaourt, arch-chroot and proot). +files in order to make JuNest working properly (such as pacman, yaourt and proot). The option **-n** will skip the final validation tests if they are not needed. Remember that the script to build the image must run in an Arch Linux OS with arch-install-scripts, package-query, git and the base-devel packages installed. @@ -132,18 +132,20 @@ Internals There are two main chroot jail used in JuNest. The main one is [proot](https://wiki.archlinux.org/index.php/Proot) which allows unprivileged users to execute programs inside a sandbox and +jchroot, a small and portable version of [arch-chroot](https://wiki.archlinux.org/index.php/Chroot) which is an enhanced chroot for privileged users that mounts the primary directories (i.e. /proc, /sys, /dev and /run) before executing any programs inside the sandbox. ##Automatic fallback to classic chroot## -Since the [arch-chroot](https://wiki.archlinux.org/index.php/Chroot) may not work -on some distros, JuNest automatically tries to fallback to the classic chroot. +If jchroot fails for some reasons in the host system (i.e. it is not able to +mount one of the directories), +JuNest automatically tries to fallback to the classic chroot. ##Automatic fallback for all the dependent host OS executables## JuNest attempt first to run the executables in the host OS located in different -positions (/usr/bin, /bin and /sbin). +positions (/usr/bin, /bin, /usr/sbin and /sbin). As a fallback it tries to run the same executable if it is available in the JuNest image. diff --git a/bin/jchroot b/bin/jchroot new file mode 100755 index 0000000..2ed810f --- /dev/null +++ b/bin/jchroot @@ -0,0 +1,112 @@ +#!/bin/bash +# +# Copyright (c) 2012-2015 +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU Library General Public License as published +# by the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# This script is the simplified and portable version of arch-chroot +# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot) + +set -e + +################################ IMPORTS ################################## +source "$(dirname $0)/../lib/util.sh" + +################################ MAIN FUNCTIONS ########################### + +chroot_add_mount() { + mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") +} + +chroot_maybe_add_mount() { + local cond=$1; shift + if eval "$cond"; then + chroot_add_mount "$@" + fi +} + +chroot_setup() { + CHROOT_ACTIVE_MOUNTS=() + [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' + trap 'chroot_teardown' EXIT + + chroot_maybe_add_mount "! mountpoint -q '$1'" "$1" "$1" --bind && + chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && + chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro && + chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid && + chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && + chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && + chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 && + chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,atime,nodev,nosuid && + mkdir -p "$1/$HOME" && + chroot_add_mount $HOME "$1/$HOME" --bind + + mkdir -p "$1/run/lock" +} + +chroot_teardown() { + umount "${CHROOT_ACTIVE_MOUNTS[@]}" + unset CHROOT_ACTIVE_MOUNTS +} + +usage() { + cat <. # -source "$(dirname $0)/../lib/core.sh" +JUNEST_BASE="$(dirname $0)/.." + +source "${JUNEST_BASE}/lib/core.sh" ################################### ### General functions ### diff --git a/lib/core.sh b/lib/core.sh index 3d2428f..50fce21 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -46,6 +46,7 @@ then fi [ -z ${JUNEST_HOME} ] && JUNEST_HOME=~/.${CMD} +[ -z ${JUNEST_BASE} ] && JUNEST_BASE=${JUNEST_HOME}/opt/junest if [ -z ${JUNEST_TEMPDIR} ] || [ ! -d ${JUNEST_TEMPDIR} ] then JUNEST_TEMPDIR=/tmp @@ -86,14 +87,14 @@ ID="id -u" # List of executables that are run in the host OS: PROOT_COMPAT="${JUNEST_HOME}/opt/proot/proot-${ARCH}" -CHROOT=${JUNEST_HOME}/usr/bin/arch-chroot -CLASSIC_CHROOT=${JUNEST_HOME}/usr/bin/chroot +CHROOT=${JUNEST_BASE}/bin/jchroot +CLASSIC_CHROOT="chroot" WGET="wget --no-check-certificate" CURL="curl -L -J -O -k" TAR=tar CHOWN="chown" -PATH=/usr/bin:/bin:/sbin:$PATH +PATH=/usr/bin:/bin:/usr/sbin:/sbin:$PATH LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" # The following functions attempt first to run the executable in the host OS. @@ -120,6 +121,10 @@ function download_cmd(){ $WGET $@ || $CURL $@ } +function chroot_cmd(){ + $CHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/chroot "$@" +} + ################################# MAIN FUNCTIONS ############################## function is_env_installed(){ @@ -182,32 +187,20 @@ function setup_env_from_file(){ builtin cd $ORIGIN_WD } - function run_env_as_root(){ local uid=$UID [ -z $SUDO_UID ] || uid=$SUDO_UID:$SUDO_GID local main_cmd="${SH[@]}" [ "$1" != "" ] && main_cmd="$(insert_quotes_on_spaces "$@")" - local cmd="mkdir -p ${JUNEST_HOME}/${HOME} && mkdir -p /run/lock && ${main_cmd}" trap - QUIT EXIT ABRT KILL TERM INT trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME}; rm_cmd -r ${JUNEST_HOME}/etc/mtab" EXIT QUIT ABRT KILL TERM INT [ ! -e ${JUNEST_HOME}/etc/mtab ] && ln_cmd -s /proc/self/mounts ${JUNEST_HOME}/etc/mtab - if ${CHROOT} $JUNEST_HOME ${TRUE} 1> /dev/null - then - JUNEST_ENV=1 ${CHROOT} $JUNEST_HOME "${SH[@]}" "-c" "${cmd}" - local ret=$? - elif ${CLASSIC_CHROOT} $JUNEST_HOME ${TRUE} 1> /dev/null - then - warn "Warning: The executable arch-chroot does not work, falling back to classic chroot" - JUNEST_ENV=1 ${CLASSIC_CHROOT} $JUNEST_HOME "${SH[@]}" "-c" "${cmd}" - local ret=$? - else - die "Error: Chroot does not work" - fi + JUNEST_ENV=1 chroot_cmd "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}" + local ret=$? # The ownership of the files is assigned to the real user [ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME} @@ -319,7 +312,7 @@ function build_image_env(){ # The archlinux-keyring and libunistring are due to missing dependencies declaration in ARM archlinux # All the essential executables (ln, mkdir, chown, etc) are in coreutils # yaourt requires sed - sudo pacstrap -G -M -d ${maindir}/root pacman arch-install-scripts coreutils binutils libunistring archlinux-keyring sed + sudo pacstrap -G -M -d ${maindir}/root pacman coreutils binutils libunistring archlinux-keyring sed sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist" info "Generating the locales..." @@ -394,7 +387,6 @@ function validate_image(){ JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r pacman -Qi pacman 1> /dev/null JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r yaourt -V 1> /dev/null JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r /opt/proot/proot-$ARCH --help 1> /dev/null - JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r arch-chroot --help 1> /dev/null local repo_package=sysstat info "Installing ${repo_package} package from official repo using proot..." diff --git a/tests/test_cli.sh b/tests/test_cli.sh index a9e0a13..bcf2f0c 100755 --- a/tests/test_cli.sh +++ b/tests/test_cli.sh @@ -1,5 +1,5 @@ #!/bin/bash -source $(dirname $0)/../bin/* -h &> /dev/null +source $(dirname $0)/../bin/junest -h &> /dev/null # Disable the exiterr set +e diff --git a/tests/test_core.sh b/tests/test_core.sh index a9759d9..7c3e45f 100755 --- a/tests/test_core.sh +++ b/tests/test_core.sh @@ -16,7 +16,8 @@ function install_mini_env(){ function setUp(){ cd $CURRPWD JUNEST_HOME=$(TMPDIR=/tmp mktemp -d -t envhome.XXXXXXXXXX) - source "$(dirname $0)/../lib/core.sh" + JUNEST_BASE="$CURRPWD/$(dirname $0)/.." + source "${JUNEST_BASE}/lib/core.sh" ORIGIN_WD=$(TMPDIR=/tmp mktemp -d -t envowd.XXXXXXXXXX) cd $ORIGIN_WD JUNEST_TEMPDIR=$(TMPDIR=/tmp mktemp -d -t envtemp.XXXXXXXXXX) @@ -217,6 +218,23 @@ function test_run_env_as_classic_root(){ assertEquals 0 $? } +function test_run_env_as_junest_root(){ + [ $SKIP_ROOT_TESTS -eq 1 ] && return + + install_mini_env + CHROOT="sudo unknowncommand" + CLASSIC_CHROOT="sudo unknowncommand" + LD_EXEC="sudo $LD_EXEC" + CHOWN="sudo $CHOWN" + + local output=$(run_env_as_root pwd 2> /dev/null) + assertEquals "/" "$output" + run_env_as_root [ -e /run/lock ] 2> /dev/null + assertEquals 0 $? + run_env_as_root [ -e $HOME ] 2> /dev/null + assertEquals 0 $? +} + function test_run_env_as_user(){ install_mini_env local output=$(run_env_as_user "-k 3.10" "/usr/bin/mkdir" "-v" "/newdir2" | awk -F: '{print $1}')