#!/bin/sh -e # Enter a chroot # shellcheck disable=2004 ## SYNOPSIS: ## .Nm cpt-chroot ## .Op Fl m ## .Op Ar dir ## DESCRIPTION: ## .Nm ## is a wrapper script to chroot inside other root filesystems. It automatically ## mounts important filesystems in to the chroot directory, and unmounts them ## when the user exits the chroot and cleans up leftover host files. If the flag ## .Fl m ## is given, ## .Nm ## does not try to mount or unmount any filesystems. # We generate the parser into the script, because we don't want this script to # depend on cpt-lib. nomount='' REST='' parse() { OPTIND=$(($#+1)) while OPTARG= && [ $# -gt 0 ]; do case $1 in --?*=*) OPTARG=$1; shift eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'} ;; --no-*) unset OPTARG ;; -[mh]?*) OPTARG=$1; shift eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'} OPTARG= ;; esac case $1 in '-m') [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG='' nomount="$OPTARG" ;; '-h'|'--help') usage exit 0 ;; --) shift while [ $# -gt 0 ]; do REST="${REST} \"\${$(($OPTIND-$#))}\"" shift done break ;; [-]?*) set "unknown" "$1"; break ;; *) REST="${REST} \"\${$(($OPTIND-$#))}\"" esac shift done [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; } case $1 in unknown) set "Unrecognized option: $2" "$@" ;; noarg) set "Does not allow an argument: $2" "$@" ;; required) set "Requires an argument: $2" "$@" ;; pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;; notcmd) set "Not a command: $2" "$@" ;; *) set "Validation error ($1): $2" "$@" esac echo "$1" >&2 exit 1 } usage() { printf '%s\n' "usage: ${0##*/} [-m] [dir]" "" "Options:" \ " -m Don't mount or unmount directories" } parser_definition() { setup REST help:usage -- "usage: ${0##*/} [-m] [dir]" msg -- '' 'Options:' flag nomount -m -- "Don't mount or unmount directories" disp :usage -h --help hidden:1 } log() { printf '\033[32m->\033[m %s.\n' "$*" } die() { log "$*" >&2 exit 1 } clean() { log Unmounting /dev, /proc and /sys from chroot; { umount "$1/dev" ||: umount "$1/proc" ||: umount "$1/sys" ||: } log Cleaning leftover host files; { rm -f "$1/root/.ash_history" } } main() { parse "$@" && eval set -- "$REST" [ -d "$1" ] || die Given path does not exist [ "$(id -u)" = 0 ] || die Script needs to be run as root [ "$2" ] || { march=$(uname -m 2>/dev/null) ||: case "$march" in '') march=native ;; x86_64) march=x86-64 ;; i*86) march=i686 ;; esac } [ -z "$nomount" ] && { trap 'clean "$1"' EXIT INT log Mounting /dev, /proc and /sys from host; { mountpoint -q "$1/dev" || mount -o bind /dev "$1/dev" mountpoint -q "$1/proc" || mount -t proc proc "$1/proc" mountpoint -q "$1/sys" || mount -t sysfs sys "$1/sys" } log Copying /etc/resolv.conf from host; { [ -f "$1/etc/resolv.conf" ] || cp /etc/resolv.conf "$1/etc" } } log Entering chroot; { chroot "$1" /usr/bin/env -i \ HOME=/root \ TERM="$TERM" \ SHELL=/bin/sh \ USER=root \ CFLAGS="${CFLAGS:--march=$march -mtune=generic -pipe -Os}" \ CXXFLAGS="${CXXFLAGS:--march=$march -mtune=generic -pipe -Os}" \ MAKEFLAGS="${MAKFLAGS:--j$(nproc 2>/dev/null || echo 1)}" \ /bin/sh -l } } main "$@"