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}')