#!/bin/bash
# 2008, 2010 Etersoft www.etersoft.ru
# Author: Vitaly Lipatov <lav@etersoft.ru>
# Public domain

load_mod rpm

set_hasherdir()
{
	HSH=/usr/bin/hsh
	if [ ! -x "$HSH" ] ; then
		fatal "Please install hasher package before use it"
	fi
	# Reuse the parent's directory even if loading userconfig reset HASHER_SLOT.
	if hasher_slot_inherited ; then
		HASHERDIR=$HASHER_LOCK_DIR
		return
	fi

	#if [ -n "$HASHERDIR" ] ; then
	#	fatal "Possible HASHERBASEDIR is not set correctly. Please replace HASHERDIR with HASHERBASEDIR in the config file"
	#fi
	HASHERDIR=$HASHERBASEDIR$MENVARG
	case "$BUILDARCH" in
		x86_64)  HASHERDIR="$HASHERDIR-64" ;;
		aarch64) HASHERDIR="$HASHERDIR-aarch64" ;;
	esac
	# Directory isolation only: hasher-priv selects its own free user pair.
	if [ "${HASHER_AUTO_SLOTS:-no}" != yes ] && [ -n "${HASHER_SLOT:-}" ] ; then
		fatal "Enable HASHER_AUTO_SLOTS=yes in configuration before selecting a slot"
	fi
	case "${HASHER_SLOT:-}" in
		*[!a-zA-Z0-9_-]*) fatal "Invalid HASHER_SLOT: use letters, digits, '_' or '-'" ;;
	esac
	[ -z "${HASHER_SLOT:-}" ] || HASHERDIR="$HASHERDIR-slot-$HASHER_SLOT"
}

hasher_slot_inherited()
{
	[ -n "${HASHER_LOCK_DIR:-}" ] &&
		[ "$HASHER_LOCK_DIR.ebu-lock" -ef /proc/self/fd/9 ]
}

# Hold fd 9 until the build and its installation helpers have all exited.
# The lock lives outside the chroot directory so cleanup cannot remove it.
acquire_hasher_slot()
{
	set_hasherdir
	hasher_slot_inherited && return
	command -v flock >/dev/null 2>&1 || fatal "Please install flock before using hasher"
	local base=$HASHERDIR slot=0 lock_status waited=
	mkdir -p "$(dirname "$base")" || fatal "Cannot create hasher parent directory"
	while : ; do
		exec 9>"$HASHERDIR.ebu-lock" || fatal "Cannot open hasher directory lock"
		# Explicit slots and operations on existing results must stay in place.
		if [ "${HASHER_AUTO_SLOTS:-no}" != yes ] ||
			[ -n "${HASHER_SLOT:-}" ] || [ "${1:-}" = fixed ] ; then
			flock 9 || fatal "Cannot lock hasher directory"
			break
		fi
		if flock -n 9 ; then
			break
		else
			lock_status=$?
		fi
		[ "$lock_status" = 1 ] || fatal "Cannot lock hasher directory"
		slot=$((slot + 1))
		# HASHER_MAX_SLOTS bounds parallel builds (the base dir counts as one): wait for a free one
		if [ -n "${HASHER_MAX_SLOTS:-}" ] && [ "$slot" -ge "$HASHER_MAX_SLOTS" ] ; then
			[ "$slot" -eq "$HASHER_MAX_SLOTS" ] && [ -z "$waited" ] && info "All $HASHER_MAX_SLOTS hasher slots are busy, waiting..."
			waited=1
			slot=0
			sleep 10
		fi
		[ "$slot" = 0 ] && HASHERDIR=$base || HASHERDIR=$base-slot-$slot
	done
	export HASHER_LOCK_DIR=$HASHERDIR
}


# hsh-mkchroot can fail at chgrp before creating .out or installing helpers.
# Preserve only that exact empty scaffold; never alter populated/unknown roots.
recover_incomplete_hasher_root()
(
	local root="$HASHERDIR/chroot" entry backup
	[ -d "$root" ] && [ ! -L "$root" ] && [ -O "$root" ] || return 0
	enable -f /usr/lib/bash/lockf lockf || fatal "Cannot load hasher directory lock"
	builtin lockf "$HASHERDIR" || fatal "Cannot lock hasher directory for recovery"
	# Recheck under hasher's native lock, also respected by direct hsh users.
	[ -d "$root" ] && [ ! -L "$root" ] && [ -O "$root" ] || return 0
	shopt -s dotglob nullglob
	local entries=("$root"/*)
	[ "${#entries[@]}" = 3 ] || return 0
	for entry in "${entries[@]}" ; do
		case "${entry##*/}" in .host|.in|dev) ;; *) return 0 ;; esac
		[ -d "$entry" ] && [ ! -L "$entry" ] && [ -O "$entry" ] || return 0
		local contents=("$entry"/*)
		[ "${#contents[@]}" = 0 ] || return 0
	done
	backup=$(mktemp -d "$HASHERDIR/chroot-incomplete.XXXXXX") || fatal "Cannot create recovery directory"
	mv -- "$root" "$backup/chroot" || fatal "Cannot preserve incomplete hasher root"
	warning "Preserved incomplete hasher root in $backup/chroot; initializing a new one" ||:
	return 0
)

# Cleanup a hasher work directory: remove chroot and aptbox (hsh --cleanup-only),
# keep the directory with its repo and cache. A directory in use (the build holds
# LOCK, by default DIR without -test plus .ebu-lock) is skipped, never waited for.
cleanup_hasher_dir()
(
	dir="$1"
	lock="${2:-${1%-test}.ebu-lock}"
	[ -d "$dir/chroot" ] || [ -d "$dir/aptbox" ] || [ -d "$dir/img-workdir" ] || return 0
	if [ -e "$lock" ] ; then
		exec 8<"$lock" || return 1
		if ! flock -n 8 ; then
			info "Skip $dir: it is in use"
			return 0
		fi
	fi
	echog "Cleanup hasher in $dir ..."
	docmd $HSH --cleanup-only --no-wait-lock "$dir"
)

check_locking()
{
	test -f "$HASHERDIR/pid" || return
	local pid="$(cat "$HASHERDIR/pid")"
	if [ "$pid" -gt 0 ] ; then
		if kill -0 -- "$pid" 2>/dev/null; then
			warning "hasher dir $HASHERDIR is already locked by pid $pid"
			ps hp "$pid" >&2
		# disable due always reported
		#else
		#	warning "hasher dir $HASHERDIR is locked by stale pid $pid"
		fi
	fi

#	echo -n "Wait for unlocking"
#	while test -f $HASHERDIR/lockdir/lockfile
#	do
#		echo -n "."
#		sleep $(($RANDOM%5+3))
#	done

}

# workaround about hard coded path to sources.list in apt.conf
print_tmp_aptconf()
{
	test -r "$1" || fatal "$1 is missed"
	# Note: /etc/apt and /etc/eterbuild/apt is supported
	cat "$1" | \
		sed -e "s|\"[^\"]*/sources\\.list[^\"]*\"|\"$OURSOURCES\"|g"
}

# printout sources.list with filtering from apt.conf
# workaround about hard coded arch in sources.list
print_tmp_sourceslist()
{
	local SLIST
	test -r "$1" || fatal "$1 is missed"
	# TODO add comments support
	# Note: /etc/apt and /etc/eterbuild/apt is supported
	if rhas "$1" "^/etc" ; then
		SLIST=`cat "$1" | \
			grep "/sources.list" | tail -n1 | sed -e 's|.*"\(.*\)".*|\1|g'`
	else
		SLIST=`cat "$1" | \
			sed -e "s|\"/etc/.*apt/sources.list|\"$ETERBUILDETC/apt/sources.list|g" | \
			grep "/sources.list" | tail -n1 | sed -e 's|.*"\(.*\)".*|\1|g'`
	fi
	[ -n "$SLIST" ] || SLIST="/etc/apt/sources.list"
	test -r "$SLIST" || fatal "'$SLIST' - sources.list path from $1 is missed"
	case "$BUILDARCH" in
		x86_64)
			cat "$SLIST" | sed -e "s|\([ /]\)i[56]86|\1$BUILDARCH|g"
			;;
		aarch64)
			# Replace i586/i686/x86_64 with aarch64, drop x86_64-i586 biarch suffix
			cat "$SLIST" | sed -e "s|\([ /]\)x86_64-i586|\1$BUILDARCH|g" -e "s|\([ /]\)i[56]86|\1$BUILDARCH|g" -e "s|\([ /]\)x86_64|\1$BUILDARCH|g"
			;;
		*)
			# Note: drop biarch 32bit on real 32bit
			cat "$SLIST" | sed -e "s|x86_64\([ /]\)|$BUILDARCH\1|g" -e "s|WINE/i586|WINE|" -e "s|WINE-Local/i586|WINE-Local|" -e "s|WINE-Network/i586|WINE-Network|" -e "s| i586/| |"  | grep -v "x86_32" | grep -v "x86_64-i586"
			;;
	esac
}

prepare_aptconfig()
{
	# Concurrent builds may use different architectures or extra repositories.
	OURSOURCES=$(make_temp_file sources.list) || fatal "Can't create sources.list"
	OURAPTCONF=$(make_temp_file apt.conf) || fatal "Can't create apt.conf"
	export OURSOURCES OURAPTCONF
	print_tmp_sourceslist "$APTCONF" >"$OURSOURCES" || fatal "Can't create sources.list"
	if [ -n "$ETERBUILD_APTREPO" ] ; then
		cat >>$OURSOURCES <<EOF

# Apply repo(s) from external ETERBUILD_APTREPO variable
$ETERBUILD_APTREPO
EOF
	fi
	print_tmp_aptconf "$APTCONF" >"$OURAPTCONF" || fatal "Can't create apt.conf"
	[ -n "$QUIETMODE" ] && return
	echo
	echog "Apt conf $APTCONF:"
	cat $APTCONF
	echo
	echog "Apt sources from $APTCONF:"
	cat $OURSOURCES | grep -v "^#" | grep -v "^\$" | grep rpm | sed -e "s|rpm|    rpm|"
	echo
}
