#!/bin/bash shopt -s extglob #!/hint/bash #{{{ message #set +u +o posix # shellcheck disable=1091 . /usr/share/makepkg/util.sh export LANG=C shopt -s extglob if [[ -t 2 && "$TERM" != dumb ]]; then colorize else # shellcheck disable=2034 declare -gr ALL_OFF='' BOLD='' BLUE='' GREEN='' RED='' YELLOW='' fi stat_busy() { local mesg=$1; shift # shellcheck disable=2059 printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}...${ALL_OFF}" "$@" >&2 } stat_done() { # shellcheck disable=2059 printf "${BOLD}done${ALL_OFF}\n" >&2 } lock_close() { local fd=$1 exec {fd}>&- } lock() { if ! [[ "/dev/fd/$1" -ef "$2" ]]; then mkdir -p -- "$(dirname -- "$2")" eval "exec $1>"'"$2"' fi if ! flock -n "$1"; then stat_busy "$3" flock "$1" stat_done fi } slock() { if ! [[ "/dev/fd/$1" -ef "$2" ]]; then mkdir -p -- "$(dirname -- "$2")" eval "exec $1>"'"$2"' fi if ! flock -sn "$1"; then stat_busy "$3" flock -s "$1" stat_done fi } _setup_workdir=false setup_workdir() { [[ -z ${WORKDIR:-} ]] && WORKDIR=$(mktemp -d --tmpdir "${0##*/}.XXXXXXXXXX") _setup_workdir=true trap 'trap_abort' INT QUIT TERM HUP trap 'trap_exit' EXIT } trap_abort() { trap - EXIT INT QUIT TERM HUP abort } trap_exit() { local r=$? trap - EXIT INT QUIT TERM HUP cleanup $r } cleanup() { if [[ -n ${WORKDIR:-} ]] && $_setup_workdir; then rm -rf "$WORKDIR" fi exit "${1:-0}" } abort() { error 'Aborting...' cleanup 255 } die() { (( $# )) && error "$@" cleanup 255 } #}}} #!/hint/bash #{{{ chroot orig_argv=("$0" "$@") check_root() { local keepenv="$1" (( EUID == 0 )) && return if type -P sudo >/dev/null; then # shellcheck disable=2154 exec sudo --preserve-env="$keepenv" -- "${orig_argv[@]}" else # shellcheck disable=2154 exec su root -c "$(printf ' %q' "${orig_argv[@]}")" fi } is_btrfs() { [[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs ]] } is_subvolume() { [[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs && "$(stat -c %i "$1")" == 256 ]] } # is_same_fs() { # [[ "$(stat -c %d "$1")" == "$(stat -c %d "$2")" ]] # } subvolume_delete_recursive() { local subvol is_subvolume "$1" || return 0 while IFS= read -d $'\0' -r subvol; do if ! subvolume_delete_recursive "$subvol"; then return 1 fi done < <(find "$1" -mindepth 1 -xdev -depth -inum 256 -print0) if ! btrfs subvolume delete "$1" &>/dev/null; then error "Unable to delete subvolume %s" "$subvol" return 1 fi return 0 } # }}} #!/hint/bash #{{{ mount ignore_error() { "$@" 2>/dev/null return 0 } trap_setup(){ [[ $(trap -p EXIT) ]] && die 'Error! Attempting to overwrite existing EXIT trap' trap "$1" EXIT } chroot_mount() { # msg2 "mount: [%s]" "$2" mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") } chroot_add_resolv_conf() { local chrootdir=$1 resolv_conf=$1/etc/resolv.conf [[ -e /etc/resolv.conf ]] || return 0 # Handle resolv.conf as a symlink to somewhere else. if [[ -L $chrootdir/etc/resolv.conf ]]; then # readlink(1) should always give us *something* since we know at this point # it's a symlink. For simplicity, ignore the case of nested symlinks. resolv_conf=$(readlink "$chrootdir/etc/resolv.conf") if [[ $resolv_conf = /* ]]; then resolv_conf=$chrootdir$resolv_conf else resolv_conf=$chrootdir/etc/$resolv_conf fi # ensure file exists to bind mount over if [[ ! -f $resolv_conf ]]; then install -Dm644 /dev/null "$resolv_conf" || return 1 fi elif [[ ! -e $chrootdir/etc/resolv.conf ]]; then # The chroot might not have a resolv.conf. return 0 fi chroot_mount /etc/resolv.conf "$resolv_conf" --bind } chroot_mount_conditional() { local cond=$1; shift if eval "$cond"; then chroot_mount "$@" fi } chroot_setup(){ local mnt="$1" os="$2" args='-t tmpfs -o nosuid,nodev,mode=0755' $os && args='--bind' chroot_mount_conditional "! mountpoint -q '$mnt'" "$mnt" "$mnt" --bind && chroot_mount proc "$mnt/proc" -t proc -o nosuid,noexec,nodev && chroot_mount sys "$mnt/sys" -t sysfs -o nosuid,noexec,nodev,ro && ignore_error chroot_mount_conditional "[[ -d '$mnt/sys/firmware/efi/efivars' ]]" \ efivarfs "$mnt/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev && chroot_mount udev "$mnt/dev" -t devtmpfs -o mode=0755,nosuid && chroot_mount devpts "$mnt/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && chroot_mount shm "$mnt/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && chroot_mount /run "$mnt/run" ${args} && chroot_mount tmp "$mnt/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid } chroot_api_mount() { CHROOT_ACTIVE_MOUNTS=() trap_setup chroot_api_umount chroot_setup "$1" false } chroot_api_umount() { if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then # msg2 "umount: [%s]" "${CHROOT_ACTIVE_MOUNTS[@]}" umount "${CHROOT_ACTIVE_MOUNTS[@]}" fi unset CHROOT_ACTIVE_MOUNTS } #}}} usage() { printf 'usage: %s chroot-dir [command]\n' "${0##*/}" printf ' -h Print this help message\n' printf '\n' printf " If 'command' is unspecified, %s will launch /bin/sh.\n" "${0##*/}" printf '\n' printf '\n' exit "$1" } opts=':h' while getopts ${opts} arg; do case "${arg}" in h|?) usage 0 ;; esac done shift $(( OPTIND - 1 )) check_root chrootdir=$1 shift [[ -d ${chrootdir} ]] || die "Can't create chroot on non-directory %s" "${chrootdir}" chroot_api_mount "${chrootdir}" || die "failed to setup API filesystems in chroot %s" "${chrootdir}" chroot_add_resolv_conf "${chrootdir}" SHELL=/bin/sh unshare --fork --pid chroot "${chrootdir}" "$@"