#!/bin/sh -ef
#
# $Id: functions.in,v 1.32 2005/10/09 15:30:46 ldv Exp $
# Copyright (C) 2003-2005  Dmitry V. Levin <ldv@altlinux.org>
# 
# This file defines functions used by hasher scripts.
#
# This file 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; either version 2 of the License, or
# (at your option) any later version.
# 
# 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.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

unset CDPATH ||:
PROG="${0##*/}"

Info()
{
	printf %s\\n "${0##*/}: $*" >&2
}

Fatal()
{
	printf %s\\n "${0##*/}: $*" >&2
	exit 1
}

quiet=
verbose=
Verbose()
{
	[ -n "$verbose" ] || return 0
	printf %s\\n "${0##*/}: $*" >&2
}

print_version()
{
	local prog
	prog="$1" && shift
	cat <<EOF
$prog version 1.0.23
Written by Dmitry V. Levin <ldv@altlinux.org>

Copyright (C) 2003-2005  Dmitry V. Levin <ldv@altlinux.org>
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
	exit
}

show_usage()
{
	[ -z "$*" ] || Info "$*"
	echo "Try \`$PROG --help' for more information." >&2
	exit 1
}

# safe umask.
umask 022

# save current work directory.
saved_cwd="$(/bin/pwd)"

# hasher directory.
hasher_dir=/usr/share/hasher

# hasher-priv directory.
def_hasher_priv_dir=/usr/libexec/hasher-priv
hasher_priv_dir=

# subconfig identifier.
number=

# target architecture.
def_target="$(uname -m)"
target=

# repo directory.
def_repo=repo
repo=

# RPM --excludedocs
exclude_docs=

# various reasonable work limits
wlimit_time_short=60
wlimit_time_long=600
wlimit_bytes_out=65536

# allow use of built packages or not
no_stuff=

# variables used by hasher-priv helper
mountpoints=
use_pty=

# default sisyphus_check config
no_sisyphus_check=
no_sisyphus_check_in=
no_sisyphus_check_out=

[ -n "$prog_apt_get" ] || prog_apt_get="apt-get"
[ -n "$prog_apt_cache" ] || prog_apt_cache="apt-cache"
[ -n "$prog_apt_config" ] || prog_apt_config="apt-config"
[ -n "$prog_genbasedir" ] || prog_genbasedir="genbasedir"
export prog_apt_get prog_apt_cache prog_apt_config prog_genbasedir

# whether to save fakeroot state
save_fakeroot=

# source user config if any
hasher_config="$HOME/.hasher/config"
if [ -s "$hasher_config" ]; then
	. "$hasher_config"
fi

# working directory.
workdir=

# APT workdir, $workdir/aptbox
aptbox=

# chroot workdir, $workdir/chroot
chroot=

# program to execute while entering chroot, $chroot/.host/entry
entry=

wlimit_time_elapsed=
wlimit_time_idle=
wlimit_bytes_written=

opt_check_read()
{
	local value
	value="$(readlink -ev "$2")" &&
		[ -r "$value" ] ||
		Fatal "$1: $2: file not available."
	printf %s "$value"
}

opt_check_dir()
{
	local value
	value="$(readlink -ev "$2")" &&
		[ -d "$value" -a -x "$value" ] ||
		Fatal "$1: $2: directory not available."
	printf %s "$value"
}

opt_check_number()
{
	[ -z "$(printf %s "$2" |tr -d [0-9])" ] &&
		[ "$2" -gt 0 ] 2>/dev/null ||
		Fatal "$1: $2: invalid number."
	printf %s "$2"
}

parse_common_options()
{
	local prog
	prog="$1" && shift

	case "$1" in
		-h|--help) show_help
			;;
		-q|--quiet) quiet=-q
			;;
		-v|--verbose) verbose=-v
			;;
		-V|--version) print_version "$prog"
			;;
		*) Fatal "Unrecognized option: $1"
			;;
	esac
}

set_apt_vars()
{
	[ -n "$apt_prefix" ] || return 0

	[ -d "$apt_prefix" ] ||
		Fatal "$apt_prefix: directory not available."

	if printf %s "$apt_prefix" |grep -qs '[`"$\]'; then
		Fatal "$apt_prefix: illegal symbols in apt prefix."
	fi

	local apt_libdir="$apt_prefix/lib"
	[ -d "$apt_libdir" ] ||
		Fatal "$apt_libdir: directory not available."

	prog_apt_get="$apt_prefix/bin/apt-get"
	[ -x "$prog_apt_get" ] ||
		Fatal "$prog_apt_get: program not available."

	prog_apt_cache="$apt_prefix/bin/apt-cache"
	[ -x "$prog_apt_cache" ] ||
		Fatal "$prog_apt_cache: program not available."

	prog_apt_config="$apt_prefix/bin/apt-config"
	[ -x "$prog_apt_config" ] ||
		Fatal "$prog_apt_config: program not available."

	prog_genbasedir="$apt_prefix/bin/genbasedir"
	[ -x "$prog_genbasedir" ] ||
		Fatal "$prog_genbasedir: program not available."

	if [ -z "$LD_LIBRARY_PATH" ]; then
		LD_LIBRARY_PATH="$apt_libdir"
	else
		LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$apt_libdir"
	fi

	if printf %s "$LD_LIBRARY_PATH" |grep -qs '[`"$\]'; then
		Fatal "$LD_LIBRARY_PATH: illegal symbols in LD_LIBRARY_PATH."
	fi

	export apt_prefix LD_LIBRARY_PATH
}

APT_CONFIG=
set_workdir()
{
	workdir="$1"
	cd "$workdir" || return 1
	workdir="$(/bin/pwd)" || return 1

	[ -n "$(printf %s "$workdir" |tr -d /.)" ] ||
		Fatal "$workdir: illegal working directory."
	if printf %s "$workdir" |grep -qs '[`"$\]'; then
		Fatal "$workdir: illegal symbols in pathname."
	fi
	[ -w . ] || Fatal "unwritable working directory."

	Verbose "changed working directory to \`$workdir'"
	aptbox="$workdir/aptbox"
	chroot="$workdir/chroot"
	entry="$chroot/.host/entry"
	export APT_CONFIG="$aptbox/etc/apt/apt.conf"
}

# assumed: defined APT_CONFIG, prog_apt_config
get_apt_config()
{
	local get_eval get_name get_value
	get_name="$1"
	shift || return 1
	get_value="$1"
	shift || return 1

	get_eval="$("$prog_apt_config" shell "$get_value" "$get_name")" || return 1
	eval "$get_eval"
}

# assumes: working get_apt_config()
read_apt_config()
{
	local name orig_name res_value dir_name value dir_value
	orig_name="$1"
	shift || return 1
	res_value="$1"
	shift || return 1

	name="$orig_name"
	get_apt_config "$name" value
	[ -n "$value" ] || Fatal "apt-config: undefined: $name"

	while [ -n "${value##/*}" ]; do
		dir_name="${name%::*}"
		[ "$dir_name" != "$name" ] || break
		get_apt_config "$dir_name" dir_value
		[ -n "$dir_value" ] || Fatal "apt-config: undefined: $dir_name"
		name="$dir_name"
		value="$dir_value$value"
	done

	[ -z "${value##/*}" ] &&
		[ -s "$value" -a -r "$value" ] ||
		Fatal "apt-config: broken $orig_name: $value"

	eval "$res_value=$(quote_arg "$value")"
}
	
hash=
rpm_verbose=

# set hash and rpm_verbose variables.
check_tty()
{
	[ -n "$verbose" ] && tty -s <&1 &&
		hash=-h ||
		hash=

	rpm_verbose="$verbose"
	if [ -z "$rpm_verbose" ]; then
		if [ -z "$quiet" ]; then
			rpm_verbose=-v
		fi
	fi
}

check_helpers()
{
	[ -d "${hasher_priv_dir:-$def_hasher_priv_dir}" ] ||
		Fatal "cannot access hasher-priv helper directory."

	getugid1="${hasher_priv_dir:-$def_hasher_priv_dir}/getugid1.sh"
	[ -x "$getugid1" ] ||
		Fatal "cannot access getugid1 helper."

	getugid2="${hasher_priv_dir:-$def_hasher_priv_dir}/getugid2.sh"
	[ -x "$getugid2" ] ||
		Fatal "cannot access getugid2 helper."

	chrootuid1="${hasher_priv_dir:-$def_hasher_priv_dir}/chrootuid1.sh"
	[ -x "$chrootuid1" ] ||
		Fatal "cannot access chrootuid1 helper."

	chrootuid2="${hasher_priv_dir:-$def_hasher_priv_dir}/chrootuid2.sh"
	[ -x "$chrootuid2" ] ||
		Fatal "cannot access chrootuid2 helper."

	makedev="${hasher_priv_dir:-$def_hasher_priv_dir}/makedev.sh"
	[ -x "$makedev" ] ||
		Fatal "cannot access makedev helper."
}

# assumed: cwd == workdir
purge_chroot_in()
{
	find chroot/.in/ -mindepth 1 -delete
}

# assumed: cwd == workdir
purge_chroot_out()
{
	find chroot/.out/ -mindepth 1 -delete
}

# assumed: cwd == workdir
copy_chroot_incoming()
{
	purge_chroot_in
	install -p -m644 $verbose -- "$@" chroot/.in/ ||
		Fatal "failed to copy files."
}

# assumed: cwd == workdir
make_repo()
{
	mkdir -p $verbose -- ${repo:-$def_repo}/{{SRPMS,${target:-$def_target}/RPMS}.hasher,${target:-$def_target}/base}
}

# assumed: cwd == workdir, defined prog_genbasedir
update_repo()
{
	[ -z "$no_stuff" ] || return 0
	"$prog_genbasedir" --topdir=${repo:-$def_repo} --no-oldhashfile --bz2only --mapi ${target:-$def_target} hasher &&
		Verbose "updated hasher repository indices." ||
		Fatal "failed to update hasher repository indices."
}

# assumed: defined variables: hash
update_RPM_database()
{
	rpmi -i $verbose $hash --dbpath "$aptbox/var/lib/rpm" $exclude_docs --ignoresize --noorder --noscripts --notriggers --justdb "$@"
		Verbose "RPM database updated." ||
		Fatal "RPM database update failed."
}

# execute as pseudoroot.
chrootuid1()
{
	[ $# -gt 0 ] || set -- /.host/entry
	local rc=0
	"$chrootuid1" ${number:+-$number} "$chroot" "$@" || rc=$?
	wlimit_time_elapsed= wlimit_time_idle= wlimit_bytes_written=
	return $rc
}

# execute as builder.
chrootuid2()
{
	[ $# -gt 0 ] || set -- /.host/entry
	local rc=0
	"$chrootuid2" ${number:+-$number} "$chroot" "$@" || rc=$?
	wlimit_time_elapsed= wlimit_time_idle= wlimit_bytes_written=
	return $rc
}

create_entry_header()
{
	cat >"$entry" <<__EOF__
#!/bin/sh -e
TMPDIR="\$HOME/tmp"
export TMPDIR
cd /.in
__EOF__
	chmod 755 "$entry"
}

create_entry_fakeroot_header()
{
	cat >"$entry" <<__EOF__
#!/bin/sh -e
if [ -z "\$FAKEROOTKEY" -a "\$USER" = root -a -x /usr/bin/fakeroot ]; then
	exec /usr/bin/fakeroot${save_fakeroot:+ -i /.fakedata -s /.fakedata} "\$0" "\$@"
fi
cd /.in
__EOF__
	chmod 755 "$entry"
}

hasher_exit_handler()
{
	local rc=$?
	trap - EXIT
	rm -rf $verbose -- "$workdir/lockdir"
	exit $rc
}

lock_workdir()
{
	local lockdir="$workdir/lockdir"
	local pidfile="$lockdir/pid"
	if ! mkdir -m700 $verbose -- "$lockdir"; then
		if [ -s "$pidfile" ]; then
			local pid
			pid="$(head -c32 "$pidfile")"
			if kill -0 -- "$pid" 2>/dev/null; then
				Info "Working directory \`$workdir' is locked, pid=$pid."
				ps hp "$pid" 2>/dev/null
			else
				Info "Working directory \`$workdir' is locked, stale pid=$pid."
			fi
		fi
		Fatal 'Unable to lock working directory.'
	fi

	trap hasher_exit_handler HUP PIPE INT QUIT TERM EXIT

	echo $$ >"$pidfile"
}

# assumed: defined APT_CONFIG, prog_apt_get
print_uris()
{
	[ -n "$APT_CONFIG" ] || Fatal "APT_CONFIG undefined."
	local out
	if ! out="$("$prog_apt_get" -q -y --print-uris install -- "$@" 2>&1)"; then
		printf %s\\n "$out" >&2
		Fatal "failed to calculate package file list."
	fi

	local pattern0="'\\([a-z]\\+\\):\\([^']\\+\\)' .*"
	local pattern1="'\\(file\\|copy\\):\\([^']\\+\\)' .*"

	local all_uris
	if ! all_uris="$(printf %s "$out" |sed -ne "s/^$pattern0/\\2/pg")"; then
		printf %s\\n "$out" >&2
		Fatal "failed to filter package file list."
	fi
	local uris
	if ! uris="$(printf %s "$out" |sed -ne "s/^$pattern1/\\2/pg")"; then
		printf %s\\n "$out" >&2
		Fatal "failed to filter package file list."
	fi

	if [ "$all_uris" != "$uris" ]; then
		printf %s\\n "$out" >&2
		Fatal "calculated package file list is not local."
	fi
	printf %s "$uris" &&
		Verbose "calculated package file list."
}

parse_xauth_entry()
{
	XAUTH_DISPLAY="$1" && shift ||:
	XAUTH_PROTO="$1" && shift ||:
	XAUTH_KEY="$1" && shift ||:

	if [ -z "$XAUTH_DISPLAY" -o -n "${XAUTH_DISPLAY##*:*}" \
	  -o -z "$XAUTH_PROTO" -o -z "$XAUTH_KEY" ]; then
		Info "Invalid auth entry for DISPLAY \`$DISPLAY', disabling X11 forwarding."
		return 1
	fi

	# Workaround for broken xauth entries.
	XAUTH_DISPLAY="$DISPLAY"
}

prepare_x11_forwarding()
{
	[ -n "$x11_forwarding" ] || return 0

	if [ -z "$DISPLAY" ]; then
		Info 'DISPLAY not set, disabling X11 forwarding.'
		return 1
	fi

	if [ -n "${DISPLAY##*:*}" ]; then
		Info 'Invalid DISPLAY set, disabling X11 forwarding.'
		return 1
	fi

	local xauth=xauth xentry
	if [ "$x11_forwarding" = trusted ]; then
		local display
		[ -n "${DISPLAY##localhost:*}" ] &&
			display="$DISPLAY" ||
			display="unix:${DISPLAY#localhost:}"
		xentry="$(xauth list "$display")" ||
			return 1
	elif [ "$x11_forwarding" = untrusted ]; then
		local xfile="$workdir/lockdir/xauth"
		touch "$xfile" &&
		$xauth -f "$xfile" generate "$DISPLAY" . untrusted timeout "$x11_timeout" ||
			return 1
		xentry="$(xauth -f "$xfile" list "$DISPLAY")" ||
			return 1
	else
		Fatal 'Invalid $x11_forwarding value.'
	fi

	parse_xauth_entry $xentry || return 1

	export XAUTH_DISPLAY XAUTH_PROTO XAUTH_KEY
}

# quote argument for /.host/entry.
quote_arg()
{
	local out
	out="$(printf %s "$*" |sed -e 's/[\`"$]/\\&/g')" || return 1
	printf %s "$out"
}

