#!/bin/bash
# License: GNU GPLv2
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# 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.

#!/hint/bash
# This may be included with or without `set -euE`

# License: Unspecified

[[ -z ${_INCLUDE_COMMON_SH:-} ]] || return 0
_INCLUDE_COMMON_SH="$(set +o|grep nounset)"

set +u +o posix
# shellcheck disable=1091
. /usr/share/makepkg/util.sh
$_INCLUDE_COMMON_SH

# Avoid any encoding problems
export LANG=C

shopt -s extglob

# check if messages are to be printed using color
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
}

_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
}

cleanup() {
	if [[ -n ${WORKDIR:-} ]] && $_setup_workdir; then
		rm -rf "$WORKDIR"
	fi
	exit "${1:-0}"
}

abort() {
	error 'Aborting...'
	cleanup 255
}

trap_abort() {
	trap - EXIT INT QUIT TERM HUP
	abort
}

trap_exit() {
	local r=$?
	trap - EXIT INT QUIT TERM HUP
	cleanup $r
}

die() {
	(( $# )) && error "$@"
	cleanup 255
}

##
#  usage : lock( $fd, $file, $message, [ $message_arguments... ] )
##
lock() {
	# Only reopen the FD if it wasn't handed to us
	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
}

##
#  usage : slock( $fd, $file, $message, [ $message_arguments... ] )
##
slock() {
	# Only reopen the FD if it wasn't handed to us
	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
}

##
#  usage : lock_close( $fd )
##
lock_close() {
	local fd=$1
	# https://github.com/koalaman/shellcheck/issues/862
	# shellcheck disable=2034
	exec {fd}>&-
}

##
# usage: pkgver_equal( $pkgver1, $pkgver2 )
##
pkgver_equal() {
	if [[ $1 = *-* && $2 = *-* ]]; then
		# if both versions have a pkgrel, then they must be an exact match
		[[ $1 = "$2" ]]
	else
		# otherwise, trim any pkgrel and compare the bare version.
		[[ ${1%%-*} = "${2%%-*}" ]]
	fi
}

##
#  usage: find_cached_package( $pkgname, $pkgver, $arch )
#
#    $pkgver can be supplied with or without a pkgrel appended.
#    If not supplied, any pkgrel will be matched.
##
find_cached_package() {
	local searchdirs=("$PWD" "$PKGDEST") results=()
	local targetname=$1 targetver=$2 targetarch=$3
	local dir pkg pkgbasename name ver rel arch r results

	for dir in "${searchdirs[@]}"; do
		[[ -d $dir ]] || continue

		for pkg in "$dir"/*.pkg.tar?(.?z); do
			[[ -f $pkg ]] || continue

			# avoid adding duplicates of the same inode
			for r in "${results[@]}"; do
				[[ $r -ef $pkg ]] && continue 2
			done

			# split apart package filename into parts
			pkgbasename=${pkg##*/}
			pkgbasename=${pkgbasename%.pkg.tar?(.?z)}

			arch=${pkgbasename##*-}
			pkgbasename=${pkgbasename%-"$arch"}

			rel=${pkgbasename##*-}
			pkgbasename=${pkgbasename%-"$rel"}

			ver=${pkgbasename##*-}
			name=${pkgbasename%-"$ver"}

			if [[ $targetname = "$name" && $targetarch = "$arch" ]] &&
					pkgver_equal "$targetver" "$ver-$rel"; then
				results+=("$pkg")
			fi
		done
	done

	case ${#results[*]} in
		0)
			return 1
			;;
		1)
			printf '%s\n' "${results[0]}"
			return 0
			;;
		*)
			error 'Multiple packages found:'
			printf '\t%s\n' "${results[@]}" >&2
			return 1
	esac
}

#!/hint/bash
# License: Unspecified
:

# shellcheck disable=2034
CHROOT_VERSION='v4'

##
#  usage : check_root $keepenv
##
orig_argv=("${BASH_SOURCE[0]}" "$@")
check_root() {
	local keepenv=$1

	(( EUID == 0 )) && return
	if type -P sudo >/dev/null; then
		exec sudo --preserve-env=$keepenv -- "${orig_argv[@]}"
	else
		exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
	fi
}

##
#  usage : is_btrfs( $path )
# return : whether $path is on a btrfs
##
is_btrfs() {
	[[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs ]]
}

##
#  usage : is_subvolume( $path )
# return : whether $path is a the root of a btrfs subvolume (including
#          the top-level subvolume).
##
is_subvolume() {
	[[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs && "$(stat -c %i "$1")" == 256 ]]
}

##
#  usage : subvolume_delete_recursive( $path )
#
#    Find all btrfs subvolumes under and including $path and delete them.
##
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
}


# umask might have been changed in /etc/profile
# ensure that sane default is set again
umask 0022

working_dir=''
umode=''

files=()
nspawn_args=()

usage() {
	echo "Usage: ${0##*/} [options] working-dir package-list..."
	echo ' options:'
	echo '    -U            Use pacman -U to install packages'
	echo '    -C <file>     Location of a pacman config file'
	echo '    -M <file>     Location of a makepkg config file'
	echo '    -c <dir>      Set pacman cache'
	echo '    -f <file>     Copy file from the host to the chroot'
	echo '    -s            Do not run setarch'
	echo '    -h            This message'
	exit 1
}

while getopts 'hUC:M:c:f:s' arg; do
	case "$arg" in
		U) umode=U ;;
		C) pac_conf="$OPTARG" ;;
		M) makepkg_conf="$OPTARG" ;;
		c) cache_dirs+=("$OPTARG") ;;
		f) files+=("$OPTARG") ;;
		s) nosetarch=1 ;;
		h|?) usage ;;
		*) error "invalid argument '%s'" "$arg"; usage ;;
	esac
	if [[ $arg != U ]]; then
		nspawn_args+=("-$arg")
		[[ -v OPTARG ]] && nspawn_args+=("$OPTARG")
	fi
done
shift $((OPTIND - 1))

(( $# < 2 )) && die 'You must specify a directory and one or more packages.'

check_root

working_dir="$(readlink -f "$1")"
shift 1

[[ -z $working_dir ]] && die 'Please specify a working directory.'

pacconf_cmd=$(command -v pacman-conf || command -v pacconf)

if (( ${#cache_dirs[@]} == 0 )); then
	mapfile -t cache_dirs < <($pacconf_cmd CacheDir)
fi

umask 0022

[[ -e $working_dir ]] && die "Working directory '%s' already exists" "$working_dir"

mkdir -p "$working_dir"

lock 9 "${working_dir}.lock" "Locking chroot"

if is_btrfs "$working_dir"; then
	rmdir "$working_dir"
	if ! btrfs subvolume create "$working_dir"; then
		die "Couldn't create subvolume for '%s'" "$working_dir"
	fi
	chmod 0755 "$working_dir"
fi

for file in "${files[@]}"; do
	mkdir -p "$(dirname "$working_dir$file")"
	cp "$file" "$working_dir$file"
done

pacstrap -${umode}Mcd ${pac_conf:+-C "$pac_conf"} "$working_dir" \
	"${cache_dirs[@]/#/--cachedir=}" "$@" || die 'Failed to install all packages'

printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$working_dir/etc/locale.gen"
echo 'LANG=en_US.UTF-8' > "$working_dir/etc/locale.conf"
echo "$CHROOT_VERSION" > "$working_dir/.arch-chroot"

systemd-machine-id-setup --root="$working_dir"

exec arch-nspawn \
	"${nspawn_args[@]}" \
	"$working_dir" locale-gen
