#!/bin/bash
# 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.

# Avoid any encoding problems
export LANG=C

shopt -s extglob

# check if messages are to be printed using color
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 ]]; then
	# prefer terminal safe colored and bold text when tput is supported
	if tput setaf 0 &>/dev/null; then
		ALL_OFF="$(tput sgr0)"
		BOLD="$(tput bold)"
		BLUE="${BOLD}$(tput setaf 4)"
		GREEN="${BOLD}$(tput setaf 2)"
		RED="${BOLD}$(tput setaf 1)"
		YELLOW="${BOLD}$(tput setaf 3)"
	else
		ALL_OFF="\e[1;0m"
		BOLD="\e[1;1m"
		BLUE="${BOLD}\e[1;34m"
		GREEN="${BOLD}\e[1;32m"
		RED="${BOLD}\e[1;31m"
		YELLOW="${BOLD}\e[1;33m"
	fi
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW

plain() {
	local mesg=$1; shift
	printf "${BOLD}    ${mesg}${ALL_OFF}\n" "$@" >&2
}

msg() {
	local mesg=$1; shift
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

msg2() {
	local mesg=$1; shift
	printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

warning() {
	local mesg=$1; shift
	printf "${YELLOW}==> WARNING:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

error() {
	local mesg=$1; shift
	printf "${RED}==> ERROR:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

stat_busy() {
	local mesg=$1; shift
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}...${ALL_OFF}" >&2
}

stat_done() {
	printf "${BOLD}done${ALL_OFF}\n" >&2
}

setup_workdir() {
	[[ -z $WORKDIR ]] && WORKDIR=$(mktemp -d --tmpdir "${0##*/}.XXXXXXXXXX")
}

cleanup() {
	[[ -n $WORKDIR ]] && rm -rf "$WORKDIR"
	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
}

trap 'trap_abort' INT QUIT TERM HUP
trap 'trap_exit' EXIT

##
#  usage : in_array( $needle, $haystack )
# return : 0 - found
#          1 - not found
##
in_array() {
	local needle=$1; shift
	local item
	for item in "$@"; do
		[[ $item = $needle ]] && return 0 # Found
	done
	return 1 # Not Found
}

##
#  usage : get_full_version( [$pkgname] )
# return : full version spec, including epoch (if necessary), pkgver, pkgrel
##
get_full_version() {
	# set defaults if they weren't specified in buildfile
	pkgbase=${pkgbase:-${pkgname[0]}}
	epoch=${epoch:-0}
	if [[ -z $1 ]]; then
		if (( ! epoch )); then
			echo $pkgver-$pkgrel
		else
			echo $epoch:$pkgver-$pkgrel
		fi
	else
		for i in pkgver pkgrel epoch; do
			local indirect="${i}_override"
			eval $(declare -f package_$1 | sed -n "s/\(^[[:space:]]*$i=\)/${i}_override=/p")
			[[ -z ${!indirect} ]] && eval ${indirect}=\"${!i}\"
		done
		if (( ! $epoch_override )); then
			echo $pkgver_override-$pkgrel_override
		else
			echo $epoch_override:$pkgver_override-$pkgrel_override
		fi
	fi
}

##
#  usage : lock( $fd, $file, $message )
##
lock() {
	eval "exec $1>"'"$2"'
	if ! flock -n $1; then
		stat_busy "$3"
		flock $1
		stat_done
	fi
}

##
#  usage : slock( $fd, $file, $message )
##
slock() {
	eval "exec $1>"'"$2"'
	if ! flock -sn $1; then
		stat_busy "$3"
		flock -s $1
		stat_done
	fi
}

##
# usage: pkgver_equal( $pkgver1, $pkgver2 )
##
pkgver_equal() {
	local left right

	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 pkgparts name ver rel arch size 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"
			return 0
			;;
		*)
			error 'Multiple packages found:'
			printf '\t%s\n' "${results[@]}" >&2
			return 1
	esac
}

##
#  usage : check_root ("$0" "$@")
##
check_root() {
	(( EUID == 0 )) && return
	if type -P sudo >/dev/null; then
		exec sudo -- "$@"
	else
		exec su root -c "$(printf ' %q' "$@")"
	fi
}


CHROOT_VERSION='v3'

working_dir=''

usage() {
	echo "Usage: ${0##*/} [options] working-dir [systemd-nspawn arguments]"
	echo "A wrapper around systemd-nspawn. Provides support for pacman."
	echo
	echo ' options:'
	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 '    -h            This message'
	exit 1
}

orig_argv=("$@")

while getopts 'hC:M:c:' arg; do
	case "$arg" in
		C) pac_conf="$OPTARG" ;;
		M) makepkg_conf="$OPTARG" ;;
		c) cache_dir="$OPTARG" ;;
		h|?) usage ;;
		*) error "invalid argument '$arg'"; usage ;;
	esac
done
shift $(($OPTIND - 1))

(( $# < 1 )) && die 'You must specify a directory.'
check_root "$0" "${orig_argv[@]}"

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

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

if [[ -z $cache_dir ]]; then
	cache_dirs=($(pacman -v 2>&1 | grep '^Cache Dirs:' | sed 's/Cache Dirs:\s*//g'))
else
	cache_dirs=("$cache_dir")
fi

host_mirror=$(pacman -Sddp extra/devtools 2>/dev/null | sed -r 's#(.*/)extra/os/.*#\1$repo/os/$arch#')
[[ $host_mirror == *file://* ]] && host_mirror_path=$(echo "$host_mirror" | sed -r 's#file://(/.*)/\$repo/os/\$arch#\1#g')

# {{{ functions
build_mount_args() {
	declare -g mount_args=()

	if [[ -n $host_mirror_path ]]; then
		mount_args+=(--bind-ro="$host_mirror_path")
	fi

	mount_args+=(--bind="${cache_dirs[0]}")

	for cache_dir in ${cache_dirs[@]:1}; do
		mount_args+=(--bind-ro="$cache_dir")
	done
}

copy_hostconf () {
	cp -a /etc/pacman.d/gnupg "$working_dir/etc/pacman.d"
	echo "Server = $host_mirror" >"$working_dir/etc/pacman.d/mirrorlist"

	[[ -n $pac_conf ]] && cp $pac_conf "$working_dir/etc/pacman.conf"
	[[ -n $makepkg_conf ]] && cp $makepkg_conf "$working_dir/etc/makepkg.conf"

	sed -r "s|^#?\\s*CacheDir.+|CacheDir = $(echo -n ${cache_dirs[@]})|g" -i "$working_dir/etc/pacman.conf"
}
# }}}

umask 0022

# Sanity check
if [[ ! -f "$working_dir/.arch-chroot" ]]; then
	die "'%s' does not appear to be an Arch chroot." "$working_dir"
elif [[ $(cat "$working_dir/.arch-chroot") != $CHROOT_VERSION ]]; then
	die "chroot '%s' is not at version %s. Please rebuild." "$working_dir" "$CHROOT_VERSION"
fi

build_mount_args
copy_hostconf

eval $(grep '^CARCH=' "$working_dir/etc/makepkg.conf")

exec ${CARCH:+setarch "$CARCH"} systemd-nspawn -q \
	-D "$working_dir" \
	--register=no \
	"${mount_args[@]}" \
	"$@"
