269 lines
5.9 KiB
Bash
Executable file
269 lines
5.9 KiB
Bash
Executable file
#!/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}" "$@"
|