############################################################### smallutils

smallyes() {
	YES="${1-y}"
	while echo "$YES" 2>/dev/null ; do : ; done
}

in_path () {
	local OLD_IFS="$IFS"
	IFS=":"
	for dir in $PATH; do
		if [ -x "$dir/$1" ]; then
			IFS="$OLD_IFS"
			return 0
		fi
	done
	IFS="$OLD_IFS"
	return 1
}

############################################################### interaction

error () {
	# <error code> <name> <string> <args>
	local err="$1"
	local name="$2"
	local fmt="$3"
	shift; shift; shift
	if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then
		(echo "E: $name"
		for x in "$@"; do echo "EA: $x"; done
		echo "EF: $fmt") >&4
	else
		(printf "E: $fmt\n" "$@") >&4
	fi
	exit $err
}

warning () {
	# <name> <string> <args>
	local name="$1"
	local fmt="$2"
	shift; shift
	if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then
		(echo "W: $name"
		for x in "$@"; do echo "WA: $x"; done
		echo "WF: $fmt") >&4
	else
		printf "W: $fmt\n" "$@" >&4
	fi
}

info () {
	# <name> <string> <args>
	local name="$1"
	local fmt="$2"
	shift; shift
	if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then
		(echo "I: $name"
		for x in "$@"; do echo "IA: $x"; done
		echo "IF: $fmt") >&4
	else
		printf "I: $fmt\n" "$@" >&4
	fi
}

PROGRESS_NOW=0
PROGRESS_END=0
PROGRESS_NEXT=""
PROGRESS_WHAT=""

progress_next () {
	PROGRESS_NEXT="$1"
}

wgetprogress () {
	[ ! "$VERBOSE" ] && QSWITCH="-q"
	local ret=0
	if [ "$USE_DEBIANINSTALLER_INTERACTION" ] && [ "$PROGRESS_NEXT" ]; then
		wget "$@" 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END >&3
		ret=$?
	else
		wget $QSWITCH "$@" 
		ret=$?
	fi
	return $ret
}

progress () {
	# <now> <end> <name> <string> <args>
	local now="$1"
	local end="$2"
	local name="$3"
	local fmt="$4"
	shift; shift; shift; shift
	if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then
		PROGRESS_NOW="$now"
		PROGRESS_END="$end"
		PROGRESS_NEXT=""
		(echo "P: $now $end $name"
		for x in "$@"; do echo "PA: $x"; done
		echo "PF: $fmt") >&3
	fi
}

dpkg_progress () {
	# <now> <end> <name> <desc> UNPACKING|CONFIGURING
	local now="$1"
	local end="$2"
	local name="$3"
	local desc="$4"
	local action="$5"
	local expect=

	if [ "$action" = UNPACKING ]; then
		expect=half-installed
	elif [ "$action" = CONFIGURING ]; then
		expect=half-configured
	fi

	dp () {
		now="$(($now + ${1:-1}))"
	}

	exitcode=0
	while read status pkg qstate; do
		if [ "$status" = "EXITCODE" ]; then
			exitcode="$pkg"
			continue
		fi
		[ "$qstate" = "$expect" ] || continue
		case $qstate in
		    half-installed)
			dp; progress "$now" "$end" "$name" "$desc"
			info "$action" "Unpacking %s..." "${pkg%:}"
			expect=unpacked
			;;
		    unpacked)
			expect=half-installed
			;;
		    half-configured)
			dp; progress "$now" "$end" "$name" "$desc"
			info "$action" "Configuring %s..." "${pkg%:}"
			expect=installed
			;;
		    installed)
			expect=half-configured
			;;
		esac
	done
	return $exitcode
}

############################################################# set variables

default_mirror () {
	DEF_MIRROR="$1"
}

FINDDEBS_NEEDS_INDICES=false
finddebs_style () {
	case "$1" in
	    hardcoded)
		;;
	    from-indices)
		FINDDEBS_NEEDS_INDICES=true
		;;
	    *)
		error 1 BADFINDDEBS "unknown finddebs style"
		;;
	 esac
}

mk_download_dirs () {
	if [ $DLDEST = "apt_dest" ]; then
		mkdir -p "$TARGET/$APTSTATE/lists/partial"
		mkdir -p "$TARGET/var/cache/apt/archives/partial"
	fi
}

download_style () {
	case "$1" in
	    apt)
		if [ "$2" = "var-state" ]; then
			APTSTATE=var/state/apt
		else
			APTSTATE=var/lib/apt
		fi
		DLDEST=apt_dest
		export APTSTATE DLDEST DEBFOR
		;;
	    *)
		error 1 BADDLOAD "unknown download style"
		;;
	esac
}

keyring () {
	if [ -z "$KEYRING" ]; then
		if [ -e "$1" ]; then
			KEYRING="$1"
		elif [ -z "$DISABLE_KEYRING" ]; then
			if [ -n "$DEF_HTTPS_MIRROR" ] && [ -z "$USER_MIRROR" ] && [ -z "$FORCE_KEYRING" ]; then
				info KEYRING "Keyring file not available at %s; switching to https mirror %s" "$1" "$DEF_HTTPS_MIRROR"
				USER_MIRROR="$DEF_HTTPS_MIRROR"
			else
				warning KEYRING "Cannot check Release signature; keyring file not available %s" "$1"
				if [ -n "$FORCE_KEYRING" ]; then
					error 1 KEYRING "Keyring-based check was requested; aborting accordingly"
				fi
			fi
		fi
	fi
}

########################################################## variant handling

doing_variant () {
	if [ "$1" = "$VARIANT" ]; then return 0; fi
	if [ "$1" = "-" ] && [ "$VARIANT" = "" ]; then return 0; fi
	return 1
}

SUPPORTED_VARIANTS="-"
variants () {
	SUPPORTED_VARIANTS="$*"
	for v in $*; do
		if doing_variant "$v"; then return 0; fi
	done
	error 1 UNSUPPVARIANT "unsupported variant"
}

################################################# work out names for things

mirror_style () {
	case "$1" in
	    release)
		DOWNLOAD_INDICES=download_release_indices
		DOWNLOAD_DEBS=download_release
		;;
	    main)
		DOWNLOAD_INDICES=download_main_indices
		DOWNLOAD_DEBS=download_main
		;;
	    *)
		error 1 BADMIRROR "unknown mirror style"
		;;
	esac
	export DOWNLOAD_INDICES
	export DOWNLOAD_DEBS
}

force_md5 () {
	DEBOOTSTRAP_CHECKSUM_FIELD=MD5SUM
	export DEBOOTSTRAP_CHECKSUM_FIELD
}

verify_checksum () {
	# args: dest checksum size
	local expchecksum="$2"
	local expsize="$3"
	if [ "$DEBOOTSTRAP_CHECKSUM_FIELD" = "MD5SUM" ]; then
		if in_path md5sum; then
			relchecksum=`md5sum < "$1" | sed 's/ .*$//'`
		elif in_path md5; then
			relchecksum=`md5 < "$1"`
		else
			error 1 SIGCHECK "Cannot check md5sum"
		fi
	else
		if in_path "sha${SHA_SIZE}sum"; then
			relchecksum=`sha${SHA_SIZE}sum < "$1" | sed 's/ .*$//'`
		elif in_path "sha${SHA_SIZE}"; then
			relchecksum=`sha${SHA_SIZE} < "$1"`
		else
			error 1 SIGCHECK "Cannot check sha${SHA_SIZE}sum"
		fi
	fi
	relsize=`wc -c < "$1"`
	if [ "$expsize" -ne "$relsize" ] || [ "$expchecksum" != "$relchecksum" ]; then
		return 1
	fi
	return 0
}

get () {
	# args: from dest 'nocache'
	# args: from dest [checksum size] [alt {checksum size type}]
	local displayname
	local versionname
	if [ "${2%.deb}" != "$2" ]; then
		displayname="$(echo "$2" | sed 's,^.*/,,;s,_.*$,,')"
		versionname="$(echo "$2" | sed 's,^.*/,,' | cut -d_ -f2 | sed 's/%3a/:/')"
	else
		displayname="$(echo "$1" | sed 's,^.*/,,')"
	fi

	if [ -e "$2" ]; then
		if [ -z "$3" ]; then
			return 0
		elif [ "$3" = nocache ]; then
			rm -f "$2"
		else
			info VALIDATING "Validating %s %s" "$displayname" "$versionname"
			if verify_checksum "$2" "$3" "$4"; then
				return 0
			else
				rm -f "$2"
			fi
		fi
	fi
	# Drop 'nocache' option
	if [ "$3" = nocache ]; then
		set "$1" "$2"
	fi

	if [ "$#" -gt 5 ]; then
		local st=3
		if [ "$5" = "-" ]; then st=6; fi
		local order="$(a=$st; while [ "$a" -le $# ]; do eval echo \"\${$(($a+1))}\" $a;
		a=$(($a + 3)); done | sort -n | sed 's/.* //')"
	else
		local order=3
	fi
	for a in $order; do
		local checksum="$(eval echo \${$a})"
		local siz="$(eval echo \${$(( $a+1 ))})"
		local typ="$(eval echo \${$(( $a+2 ))})"
		local from
		local dest
		local iters=0

		case "$typ" in
		    xz)  from="$1.xz"; dest="$2.xz" ;;
		    bz2) from="$1.bz2"; dest="$2.bz2" ;;
		    gz)  from="$1.gz"; dest="$2.gz" ;;
		    *)   from="$1"; dest="$2" ;;
		esac

		if [ "${dest#/}" = "$dest" ]; then
			dest="./$dest"
		fi
		local dest2="$dest"
		if [ -d "${dest2%/*}/partial" ]; then
			dest2="${dest2%/*}/partial/${dest2##*/}"
		fi

		while [ "$iters" -lt 10 ]; do
			info RETRIEVING "Retrieving %s %s" "$displayname" "$versionname"
			if ! just_get "$from" "$dest2"; then continue 2; fi
			if [ "$checksum" != "" ]; then
				info VALIDATING "Validating %s %s" "$displayname" "$versionname"
				if verify_checksum "$dest2" "$checksum" "$siz"; then
					checksum=""
				fi
			fi
			if [ -z "$checksum" ]; then
				[ "$dest2" = "$dest" ] || mv "$dest2" "$dest"
				case "$typ" in
				    gz)  gunzip "$dest" ;;
				    bz2) bunzip2 "$dest" ;;
				    xz)  unxz "$dest" ;;
				esac
				return 0
			else
				rm -f "$dest2"
				warning RETRYING "Retrying failed download of %s" "$from"
				iters="$(($iters + 1))"
			fi
		done
		warning CORRUPTFILE "%s was corrupt" "$from"
	done
	return 1
}

just_get () {
	# args: from dest
	local from="$1"
	local dest="$2"
	mkdir -p "${dest%/*}"
	if [ "${from#null:}" != "$from" ]; then
		error 1 NOTPREDL "%s was not pre-downloaded" "${from#null:}"
	elif [ "${from#http://}" != "$from" ] || [ "${from#ftp://}" != "$from" ]; then
		# http/ftp mirror
		if wgetprogress -O "$dest" "$from"; then
			return 0
		else
			rm -f "$dest"
			return 1
		fi
	elif [ "${from#https://}" != "$from" ] ; then
		# http/ftp mirror
		if wgetprogress $CHECKCERTIF $CERTIFICATE $PRIVATEKEY -O "$dest" "$from"; then
			return 0
		else
			rm -f "$dest"
			return 1
		fi
	elif [ "${from#file:}" != "$from" ]; then
		local base="${from#file:}"
		if [ "${base#//}" != "$base" ]; then
			base="/${from#file://*/}"
		fi
		if [ -e "$base" ]; then
			cp "$base" "$dest"
			return 0
		else
			return 1
		fi
	elif [ "${from#ssh:}" != "$from" ]; then
		local ssh_dest="$(echo $from | sed -e 's#ssh://##' -e 's#/#:/#')"
		if [ -n "$ssh_dest" ]; then
			scp "$ssh_dest" "$dest"
			return 0
		else
			return 1
		fi
	else
		error 1 UNKNOWNLOC "unknown location %s" "$from"
	fi
}

download () {
	mk_download_dirs
	"$DOWNLOAD_DEBS" $(echo "$@" | tr ' ' '\n' | sort)
}

download_indices () {
	mk_download_dirs
	"$DOWNLOAD_INDICES" $(echo "$@" | tr ' ' '\n' | sort)
}

debfor () {
	(while read pkg path; do
		for p in "$@"; do
			[ "$p" = "$pkg" ] || continue;
			echo "$path"
		done
	 done <"$TARGET/debootstrap/debpaths"
	)
}

apt_dest () {
	# args:
	#   deb package version arch mirror path
	#   pkg suite component arch mirror path
	#   rel suite mirror path
	case "$1" in
	    deb)
		echo "/var/cache/apt/archives/${2}_${3}_${4}.deb" | sed 's/:/%3a/'
		;;
	    pkg)
		local m="$5"
		m="debootstrap.invalid"
		#if [ "${m#http://}" != "$m" ]; then
		#	m="${m#http://}"
		#elif [ "${m#file://}" != "$m" ]; then
		#	m="file_localhost_${m#file://*/}"
		#elif [ "${m#file:/}" != "$m" ]; then
		#	m="file_localhost_${m#file:/}"
		#fi

		printf "%s" "$APTSTATE/lists/"
		echo "${m}_$6" | sed 's/\//_/g'
		;;
	    rel)
		local m="$3"
		m="debootstrap.invalid"
		#if [ "${m#http://}" != "$m" ]; then
		#	m="${m#http://}"
		#elif [ "${m#file://}" != "$m" ]; then
		#	m="file_localhost_${m#file://*/}"
		#elif [ "${m#file:/}" != "$m" ]; then
		#	m="file_localhost_${m#file:/}"
		#fi
		printf "%s" "$APTSTATE/lists/"
		echo "${m}_$4" | sed 's/\//_/g'
		;;
	esac
}

################################################################## download

get_release_checksum () {
	local reldest="$1"
	local path="$2"
	if [ "$DEBOOTSTRAP_CHECKSUM_FIELD" = MD5SUM ]; then
		local match="^[Mm][Dd]5[Ss][Uu][Mm]"
	else
		local match="^[Ss][Hh][Aa]$SHA_SIZE:"
	fi
	sed -n "/$match/,/^[^ ]/p" < "$reldest" | \
		while read a b c; do
			if [ "$c" = "$path" ]; then echo "$a $b"; fi
		done | head -n 1
}

extract_release_components () {
	local reldest="$1"; shift
	TMPCOMPONENTS="$(sed -n 's/Components: *//p' "$reldest")"
	for c in $TMPCOMPONENTS ; do
		eval "
		case \"\$c\" in
		    $USE_COMPONENTS)
			COMPONENTS=\"\$COMPONENTS \$c\"
			;;
		esac
		"
	done
	COMPONENTS="$(echo $COMPONENTS)"
	if [ -z "$COMPONENTS" ]; then
		mv "$reldest" "$reldest.malformed"
		error 1 INVALIDREL "Invalid Release file, no valid components"
	fi
}

CODENAME=""
validate_suite () {
	local reldest="$1"

	CODENAME=$(sed -n "s/^Codename: *//p" "$reldest")
	local suite=$(sed -n "s/^Suite: *//p" "$reldest")

	if [ "$SUITE" != "$suite" ] && [ "$SUITE" != "$CODENAME" ]; then
		error 1 WRONGSUITE "Asked to install suite %s, but got %s (codename: %s) from mirror" "$SUITE" "$suite" "$CODENAME"
	fi
}

split_inline_sig () {
	local inreldest="$1"
	local reldest="$2"
	local relsigdest="$3"

	# Note: InRelease files are fun since one needs to remove the
	# last newline from the PGP SIGNED MESSAGE part, while keeping
	# the PGP SIGNATURE part intact. This shell implementation
	# should work on most if not all systems, instead of trying to
	# sed/tr/head, etc.
	rm -f "$reldest" "$relsigdest"
	nl=""
	state=pre-begin
	while IFS= read -r line; do
		case "${state}" in
		    pre-begin)
			if [ "x${line}" = "x-----BEGIN PGP SIGNED MESSAGE-----" ]; then
				state=begin
			fi
			;;
		    begin)
			if [ "x${line}" = "x" ]; then
				state=data
			fi
			;;
		    data)
			if [ "x${line}" = "x-----BEGIN PGP SIGNATURE-----" ]; then
				printf "%s\n" "${line}" > "$relsigdest"
				state=signature
			else
				printf "${nl}%s" "${line}" >> "$reldest"
				nl="\n"
			fi
			;;
		    signature)
			printf "%s\n" "${line}" >> "$relsigdest"
			if [ "x${line}" = "x-----END PGP SIGNATURE-----" ]; then
				break
			fi
		esac
	done < "$inreldest"
}

download_release_sig () {
	local m1="$1"
	local inreldest="$2"
	local reldest="$3"
	local relsigdest="$4"

	progress 0 100 DOWNREL "Downloading Release file"
	progress_next 100
	if get "$m1/dists/$SUITE/InRelease" "$inreldest" nocache; then
		split_inline_sig "$inreldest" "$reldest" "$relsigdest"
		progress 100 100 DOWNREL "Downloading Release file"
	else
		get "$m1/dists/$SUITE/Release" "$reldest" nocache ||
			error 1 NOGETREL "Failed getting release file %s" "$m1/dists/$SUITE/Release"
		progress 100 100 DOWNREL "Downloading Release file"
	fi
	if [ -n "$KEYRING" ] && [ -z "$DISABLE_KEYRING" ]; then
		progress 0 100 DOWNRELSIG "Downloading Release file signature"
		if ! [ -f "$relsigdest" ]; then
			progress_next 50
			get "$m1/dists/$SUITE/Release.gpg" "$relsigdest" nocache ||
				error 1 NOGETRELSIG "Failed getting release signature file %s" \
				"$m1/dists/$SUITE/Release.gpg"
			progress 50 100 DOWNRELSIG "Downloading Release file signature"
		fi

		info RELEASESIG "Checking Release signature"
		# Don't worry about the exit status from gpgv; parsing the output will
		# take care of that.
		(gpgv --status-fd 1 --keyring "$KEYRING" --ignore-time-conflict \
		 "$relsigdest" "$reldest" || true) | read_gpg_status
		progress 100 100 DOWNRELSIG "Downloading Release file signature"
	fi
}

download_release_indices () {
	local m1="${MIRRORS%% *}"
	local inreldest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/InRelease")"
	local reldest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/Release")"
	local relsigdest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/Release.gpg")"

	download_release_sig "$m1" "$inreldest" "$reldest" "$relsigdest"

	validate_suite "$reldest"

	extract_release_components $reldest

	local totalpkgs=0
	for c in $COMPONENTS; do
		local subpath="$c/binary-$ARCH/Packages"
		local xzi="`get_release_checksum "$reldest" "$subpath.xz"`"
		local bz2i="`get_release_checksum "$reldest" "$subpath.bz2"`"
		local gzi="`get_release_checksum "$reldest" "$subpath.gz"`"
		local normi="`get_release_checksum "$reldest" "$subpath"`"
		local i=
		if [ "$normi" != "" ]; then
			i="$normi"
		elif in_path bunzip2 && [ "$bz2i" != "" ]; then
			i="$bz2i"
		elif in_path unxz && [ "$xzi" != "" ]; then
			i="$xzi"
		elif in_path gunzip && [ "$gzi" != "" ]; then
			i="$gzi"
		fi
		if [ "$i" != "" ]; then
			totalpkgs="$(( $totalpkgs + ${i#* } ))"
		else
			mv "$reldest" "$reldest.malformed"
			error 1 MISSINGRELENTRY "Invalid Release file, no entry for %s" "$subpath"
		fi
	done

	local donepkgs=0
	local pkgdest
	progress 0 $totalpkgs DOWNPKGS "Downloading Packages files"
	for c in $COMPONENTS; do
		local subpath="$c/binary-$ARCH/Packages"
		local path="dists/$SUITE/$subpath"
		local xzi="`get_release_checksum "$reldest" "$subpath.xz"`"
		local bz2i="`get_release_checksum "$reldest" "$subpath.bz2"`"
		local gzi="`get_release_checksum "$reldest" "$subpath.gz"`"
		local normi="`get_release_checksum "$reldest" "$subpath"`"
		local ext=
		local i=
		if [ "$normi" != "" ]; then
			ext="$ext $normi ."
			i="$normi"
		fi
		if in_path unxz && [ "$xzi" != "" ]; then
			ext="$ext $xzi xz"
			i="${i:-$xzi}"
		fi
		if in_path bunzip2 && [ "$bz2i" != "" ]; then
			ext="$ext $bz2i bz2"
			i="${i:-$bz2i}"
		fi
		if in_path gunzip && [ "$gzi" != "" ]; then
			ext="$ext $gzi gz"
			i="${i:-$gzi}"
		fi
		progress_next "$(($donepkgs + ${i#* }))"
		for m in $MIRRORS; do
			pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
			if get "$m/$path" "$pkgdest" $ext; then break; fi
		done
		if [ ! -f "$pkgdest" ]; then
			error 1 COULDNTDL "Couldn't download %s" "$path"
		fi
		donepkgs="$(($donepkgs + ${i#* }))"
		progress $donepkgs $totalpkgs DOWNPKGS "Downloading Packages files"
	done
}

get_package_sizes () {
	# mirror pkgdest debs..
	local m="$1"; shift
	local pkgdest="$1"; shift
	$PKGDETAILS PKGS "$m" "$pkgdest" "$@" | (
		newleft=""
		totaldebs=0
		countdebs=0
		while read p details; do
			if [ "$details" = "-" ]; then
				newleft="$newleft $p"
			else
				size="${details##* }";
				totaldebs="$(($totaldebs + $size))"
				countdebs="$(($countdebs + 1))"
			fi
		done
		echo "$countdebs $totaldebs$newleft"
	)
}

# note, leftovers come back on fd5 !!
download_debs () {
	local m="$1"
	local pkgdest="$2"
	shift; shift

	$PKGDETAILS PKGS "$m" "$pkgdest" "$@" | (
		leftover=""
		while read p ver arc mdup fil checksum size; do
			if [ "$ver" = "-" ]; then
				leftover="$leftover $p"
			else
				progress_next "$(($dloaddebs + $size))"
				local debdest="$($DLDEST deb "$p" "$ver" "$arc" "$m" "$fil")"
				if get "$m/$fil" "$TARGET/$debdest" "$checksum" "$size"; then
					dloaddebs="$(($dloaddebs + $size))"
					echo >>$TARGET/debootstrap/deburis "$p $ver $m/$fil"
					echo >>$TARGET/debootstrap/debpaths "$p $debdest"
				else
					warning COULDNTDL "Couldn't download package %s (ver %s arch %s)" "$p" "$ver" "$arc"
					leftover="$leftover $p"
				fi
			fi
		done
		echo >&5 ${leftover# }
	)
}

download_release () {
	local m1="${MIRRORS%% *}"

	local numdebs="$#"

	local countdebs=0
	progress $countdebs $numdebs SIZEDEBS "Finding package sizes"

	local totaldebs=0
	local leftoverdebs="$*"

	# Fix possible duplicate package names, which would screw up counts:
	leftoverdebs=$(printf "$leftoverdebs"|tr ' ' '\n'|sort -u|tr '\n' ' ')
	numdebs=$(printf "$leftoverdebs"|wc -w)

	for c in $COMPONENTS; do
		if [ "$countdebs" -ge "$numdebs" ]; then break; fi

		local path="dists/$SUITE/$c/binary-$ARCH/Packages"
		local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
		if [ ! -e "$pkgdest" ]; then continue; fi

		info CHECKINGSIZES "Checking component %s on %s..." "$c" "$m1"

		leftoverdebs="$(get_package_sizes "$m1" "$pkgdest" $leftoverdebs)"

		countdebs=$(($countdebs + ${leftoverdebs%% *}))
		leftoverdebs=${leftoverdebs#* }

		totaldebs=${leftoverdebs%% *}
		leftoverdebs=${leftoverdebs#* }

		progress $countdebs $numdebs SIZEDEBS "Finding package sizes"
	done

	if [ "$countdebs" -ne "$numdebs" ]; then
		error 1 LEFTOVERDEBS "Couldn't find these debs: %s" "$leftoverdebs"
	fi

	local dloaddebs=0

	progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages"
	:>$TARGET/debootstrap/debpaths

	pkgs_to_get="$*"
	for c in $COMPONENTS; do
	    local path="dists/$SUITE/$c/binary-$ARCH/Packages"
	    for m in $MIRRORS; do
		local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
		if [ ! -e "$pkgdest" ]; then continue; fi
		pkgs_to_get="$(download_debs "$m" "$pkgdest" $pkgs_to_get 5>&1 1>&6)"
		if [ -z "$pkgs_to_get" ]; then break; fi
	    done 6>&1
	    if [ -z "$pkgs_to_get" ]; then break; fi
	done
	progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages"
	if [ "$pkgs_to_get" != "" ]; then
		error 1 COULDNTDLPKGS "Couldn't download packages: %s" "$pkgs_to_get"
	fi
}

download_main_indices () {
	local m1="${MIRRORS%% *}"
	local comp="${USE_COMPONENTS}"
	progress 0 100 DOWNMAINPKGS "Downloading Packages file"
	progress_next 100

	if [ -z "$comp" ]; then comp=main; fi
	COMPONENTS="$(echo $comp | tr '|' ' ')"

	export COMPONENTS
	for m in $MIRRORS; do
	    for c in $COMPONENTS; do
		local path="dists/$SUITE/$c/binary-$ARCH/Packages"
		local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
		if in_path gunzip && get "$m/${path}.gz" "${pkgdest}.gz"; then
			rm -f "$pkgdest"
			gunzip "$pkgdest.gz"
		elif get "$m/$path" "$pkgdest"; then
			true
		fi
	    done
	done
	progress 100 100 DOWNMAINPKGS "Downloading Packages file"
}

download_main () {
	local m1="${MIRRORS%% *}"

	:>$TARGET/debootstrap/debpaths
	for p in "$@"; do
	    for c in $COMPONENTS; do
		local details=""
		for m in $MIRRORS; do
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
			if [ ! -e "$pkgdest" ]; then continue; fi
			details="$($PKGDETAILS PKGS "$m" "$pkgdest" "$p")"
			if [ "$details" = "$p -" ]; then
				details=""
				continue
			fi
			size="${details##* }"; details="${details% *}"
			checksum="${details##* }"; details="${details% *}"
			local debdest="$($DLDEST deb $details)"
			if get "$m/${details##* }" "$TARGET/$debdest" "$checksum" "$size"; then
				echo >>$TARGET/debootstrap/debpaths "$p $debdest"
				details="done"
				break
			fi
		done
		if [ "$details" != "" ]; then
			break
		fi
	    done
	    if [ "$details" != "done" ]; then
		error 1 COULDNTDL "Couldn't download %s" "$p"
	    fi
	done
}

###################################################### deb choosing support

get_debs () {
	local field="$1"
	shift
	local m1 c
	for m1 in $MIRRORS; do
		for c in $COMPONENTS; do
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
			echo $("$PKGDETAILS" FIELD "$field" "$m1" "$pkgdest" "$@" | sed 's/ .*//')
		done
	done
}

################################################################ extraction

EXTRACTORS_SUPPORTED="dpkg-deb ar"
EXTRACT_DEB_TAR_OPTIONS=

# Native dpkg-deb based extractors
extract_dpkg_deb_field () {
	local pkg="$1"
	local field="$2"

	dpkg-deb -f "$pkg" "$field"
}

extract_dpkg_deb_data () {
	local pkg="$1"

	dpkg-deb --fsys-tarfile "$pkg" | tar $EXTRACT_DEB_TAR_OPTIONS -xf -
}

# Raw .deb extractors
extract_ar_deb_field () {
	local pkg="$1"
	local field="$2"
	local tarball=$(ar -t "$pkg" | grep "^control\.tar")

	case "$tarball" in
		control.tar.gz) cat_cmd=zcat ;;
		control.tar.xz) cat_cmd=xzcat ;;
		control.tar) cat_cmd=cat ;;
		*) error 1 UNKNOWNCONTROLCOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;;
	esac

	if type $cat_cmd >/dev/null 2>&1; then
		ar -p "$pkg" "$tarball" | $cat_cmd |
		    tar -O -xf - control ./control 2>/dev/null |
		    grep -i "^$field:" | sed -e 's/[^:]*: *//' | head -n 1
	else
		error 1 UNPACKCMDUNVL "Extracting %s requires the %s command, which is not available" "$pkg" "$cat_cmd"
	fi
}

extract_ar_deb_data () {
	local pkg="$1"
	local tarball=$(ar -t "$pkg" | grep "^data.tar")

	case "$tarball" in
		data.tar.gz) cat_cmd=zcat ;;
		data.tar.bz2) cat_cmd=bzcat ;;
		data.tar.xz) cat_cmd=xzcat ;;
		data.tar) cat_cmd=cat ;;
		*) error 1 UNKNOWNDATACOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;;
	esac

	if type $cat_cmd >/dev/null 2>&1; then
		ar -p "$pkg" "$tarball" | $cat_cmd | tar $EXTRACT_DEB_TAR_OPTIONS -xf -
	else
		error 1 UNPACKCMDUNVL "Extracting %s requires the %s command, which is not available" "$pkg" "$cat_cmd"
	fi
}

valid_extractor () {
	local extractor="$1"

	for E in $EXTRACTORS_SUPPORTED; do
		if [ "$extractor" = "$E" ]; then
			return 0
		fi
	done

	return 1
}

choose_extractor () {
	local extractor

	if [ -n "$EXTRACTOR_OVERRIDE" ]; then
		extractor="$EXTRACTOR_OVERRIDE"
	elif type dpkg-deb >/dev/null 2>&1; then
		extractor="dpkg-deb"
	else
		extractor="ar"
	fi

	info CHOSENEXTRACTOR "Chosen extractor for .deb packages: %s" "$extractor"
	case "$extractor" in
	dpkg-deb)
		extract_deb_field () { extract_dpkg_deb_field "$@"; }
		extract_deb_data () { extract_dpkg_deb_data "$@"; }
		;;
	ar)
		extract_deb_field () { extract_ar_deb_field "$@"; }
		extract_deb_data () { extract_ar_deb_data "$@"; }
		;;
	esac
}

extract () { (
	cd "$TARGET"
	local p=0 cat_cmd
	for pkg in $(debfor "$@"); do
		p="$(($p + 1))"
		progress "$p" "$#" EXTRACTPKGS "Extracting packages"
		packagename="$(echo "$pkg" | sed 's,^.*/,,;s,_.*$,,')"
		info EXTRACTING "Extracting %s..." "$packagename"
		extract_deb_data "./$pkg"
	done
); }

in_target_nofail () {
	if ! $CHROOT_CMD "$@" 2>/dev/null; then
		true
	fi
	return 0
}

in_target_failmsg () {
	local code="$1"
	local msg="$2"
	local arg="$3"
	shift; shift; shift
	if ! $CHROOT_CMD "$@"; then
		warning "$code" "$msg" "$arg"
		# Try to point user at actual failing package.
		msg="See %s for details"
		if [ -e "$TARGET/debootstrap/debootstrap.log" ]; then
			arg="$TARGET/debootstrap/debootstrap.log"
			local pkg="$(grep '^dpkg: error processing ' "$TARGET/debootstrap/debootstrap.log" | head -n 1 | sed 's/\(error processing \)\(package \|archive \)/\1/' | cut -d ' ' -f 4)"
			if [ -n "$pkg" ]; then
				msg="$msg (possibly the package $pkg is at fault)"
			fi
		else
			arg="the log"
		fi
		warning "$code" "$msg" "$arg"
		return 1
	fi
	return 0
}

in_target () {
	in_target_failmsg IN_TARGET_FAIL "Failure trying to run: %s" "$CHROOT_CMD $*" "$@"
}

###################################################### standard setup stuff

conditional_cp () {
	if [ ! -e "$2/$1" ]; then
		if [ -L "$1" ] && [ -e "$1" ]; then
			cat "$1" >"$2/$1"
		elif [ -e "$1" ]; then
			cp -a "$1" "$2/$1"
		fi
	fi
}

mv_invalid_to () {
	local m="$1"
	m="$(echo "${m#http://}" | tr '/' '_' | sed 's/_*//')"
	(cd "$TARGET/$APTSTATE/lists"
	 for a in debootstrap.invalid_*; do
		 mv "$a" "${m}_${a#*_}"
	 done
	)
}

setup_apt_sources () {
	mkdir -p "$TARGET/etc/apt"
	# Cope with the foreign case where COMPONENTS would be empty:
	sources_components="${COMPONENTS:-$USE_COMPONENTS}"
	for m in "$@"; do
		local cs=""
		for c in $sources_components; do
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
			if [ -e "$pkgdest" ]; then cs="$cs $c"; fi
		done
		if [ "$cs" != "" ]; then echo "deb $m $SUITE$cs"; fi
	done > "$TARGET/etc/apt/sources.list"
}

setup_etc () {
	mkdir -p "$TARGET/etc"

	conditional_cp /etc/resolv.conf "$TARGET"
	conditional_cp /etc/hostname "$TARGET"

	if [ "$DLDEST" = apt_dest ] && [ ! -e "$TARGET/etc/apt/sources.list" ]; then
		setup_apt_sources "http://debootstrap.invalid/"
	fi
}

UMOUNT_DIRS=

umount_exit_function () {
	local realdir
	for dir in $UMOUNT_DIRS; do
		realdir="$(in_target_nofail readlink -f "$dir")"
		[ "$realdir" ] || continue
		( cd / ; umount "$TARGET/${realdir#/}" ) || true
	done
}

umount_on_exit () {
	if [ "$UMOUNT_DIRS" ]; then
		UMOUNT_DIRS="$UMOUNT_DIRS $1"
	else
		UMOUNT_DIRS="$1"
		on_exit umount_exit_function
	fi
}

clear_mtab () {
	if [ -f "$TARGET/etc/mtab" ] && [ ! -h "$TARGET/etc/mtab" ]; then
		rm -f "$TARGET/etc/mtab"
	fi
}

setup_proc () {
	case "$HOST_OS" in
	    *freebsd*)
		umount_on_exit /dev
		umount_on_exit /proc
		umount "$TARGET/proc" 2>/dev/null || true
		if [ "$HOST_OS" = kfreebsd ]; then
			in_target mount -t linprocfs proc /proc
		else
			mount -t linprocfs proc $TARGET/proc
		fi
		;;
	    hurd*)
		# firmlink $TARGET/{dev,servers,proc} to the system ones.
		settrans -a "$TARGET/dev" /hurd/firmlink /dev
		settrans -a "$TARGET/servers" /hurd/firmlink /servers
	        settrans -a "$TARGET/proc" /hurd/firmlink /proc
		;;
	    *)
		umount_on_exit /dev/pts
		umount_on_exit /dev/shm
		umount_on_exit /proc/bus/usb
		umount_on_exit /proc
		umount "$TARGET/proc" 2>/dev/null || true
		in_target mount -t proc proc /proc
		if [ -d "$TARGET/sys" ] && \
		   grep -q '[[:space:]]sysfs' /proc/filesystems 2>/dev/null; then
			umount_on_exit /sys
			umount "$TARGET/sys" 2>/dev/null || true
			in_target mount -t sysfs sysfs /sys
		fi
		on_exit clear_mtab
		;;
	esac
	umount_on_exit /lib/init/rw
}

setup_proc_fakechroot () {
	rm -rf "$TARGET/proc"
	ln -s /proc "$TARGET"
}

# create the static device nodes
setup_devices () {
	if doing_variant fakechroot; then
		setup_devices_fakechroot
		return 0
	fi

	case "$HOST_OS" in
	    kfreebsd*)
		;;
	    freebsd)
		;;
	    hurd*)
		;;
	    *)
		setup_devices_simple
		;;
	esac
}

# enable the dynamic device nodes
setup_dynamic_devices () {
	if doing_variant fakechroot; then
		return 0
	fi

	case "$HOST_OS" in
	    kfreebsd*)
		in_target mount -t devfs devfs /dev ;;
	    freebsd)
		mount -t devfs devfs $TARGET/dev ;;
	    hurd*)
	        # Use the setup-translators of the hurd package
	        in_target /usr/lib/hurd/setup-translators -k ;;
	esac
}

setup_devices_simple () {
	# The list of devices that can be created in a container comes from
	# src/core/cgroup.c in the systemd source tree.
	mknod -m 666 $TARGET/dev/null	c 1 3
	mknod -m 666 $TARGET/dev/zero	c 1 5
	mknod -m 666 $TARGET/dev/full	c 1 7
	mknod -m 666 $TARGET/dev/random	c 1 8
	mknod -m 666 $TARGET/dev/urandom	c 1 9
	mknod -m 666 $TARGET/dev/tty	c 5 0
	mkdir $TARGET/dev/pts/ $TARGET/dev/shm/
	ln -s pts/ptmx $TARGET/dev/ptmx
	ln -s /proc/self/fd   $TARGET/dev/fd
	ln -s /proc/self/fd/0 $TARGET/dev/stdin
	ln -s /proc/self/fd/1 $TARGET/dev/stdout
	ln -s /proc/self/fd/2 $TARGET/dev/stderr
}

setup_devices_fakechroot () {
	rm -rf "$TARGET/dev"
	ln -s /dev "$TARGET"
}

setup_dselect_method () {
	case "$1" in
	    apt)
		mkdir -p "$TARGET/var/lib/dpkg"
		echo "apt apt" > "$TARGET/var/lib/dpkg/cmethopt"
		chmod 644 "$TARGET/var/lib/dpkg/cmethopt"
		;;
	    *)
		error 1 UNKNOWNDSELECT "unknown dselect method"
		;;
	esac
}

# Find out where the runtime dynamic linker and the shared libraries
# can be installed on each architecture: native, multilib and multiarch.
# This data can be verified by checking the files in the debian/sysdeps/
# directory of the glibc package.
#
# This function must be updated to support any new architecture which
# either installs the RTLD in a directory different from /lib or builds
# multilib library packages.
setup_merged_usr() {
	if [ "$MERGED_USR" = "no" ]; then return 0; fi

	local link_dir
	case $ARCH in
	    hurd-*)	return 0 ;;
	    amd64)	link_dir="lib32 lib64 libx32" ;;
	    i386)	link_dir="lib64 libx32" ;;
	    mips|mipsel)
			link_dir="lib32 lib64" ;;
	    mips64*|mipsn32*)
			link_dir="lib32 lib64 libo32" ;;
	    powerpc)	link_dir="lib64" ;;
	    ppc64)	link_dir="lib32 lib64" ;;
	    ppc64el)	link_dir="lib64" ;;
	    s390x)	link_dir="lib32" ;;
	    sparc)	link_dir="lib64" ;;
	    sparc64)	link_dir="lib32 lib64" ;;
	    x32)	link_dir="lib32 lib64 libx32" ;;
	esac
	link_dir="bin sbin lib $link_dir"

	local dir
	for dir in $link_dir; do
		ln -s usr/$dir $TARGET/$dir
		mkdir -p $TARGET/usr/$dir
	done
}

################################################################ pkgdetails

# NOTE
# For the debootstrap udeb, pkgdetails is provided by the bootstrap-base
# udeb, so the pkgdetails API needs to be kept in sync with that.

if in_path perl; then
	PKGDETAILS=pkgdetails_perl

	pkgdetails_field () {
		# uniq field mirror Packages values...
		perl -le '
$unique = shift @ARGV; $field = lc(shift @ARGV); $mirror = shift @ARGV;
%fields = map { $_, 0 } @ARGV;
$prevpkg = "";
while (<STDIN>) {
	chomp;
	next if (/^ /);
	if (/^([^:]*:)\s*(.*)$/) {
		$f = lc($1); $v = $2;
		if ($f eq "package:") {
			$last = 0;
			$pkg = $v;
			if ($pkg ne $prevpkg) {
				print $output if defined $output;
				if ($unique && defined $output_val) {
					delete $fields{$output_val};
					$last = 1 unless keys %fields;
				}
				$prevpkg = $pkg;
			}
			undef $output;
			undef $output_val;
			last if $last;
		}
		$ver = $v if ($f eq "version:");
		$arc = $v if ($f eq "architecture:");
		$fil = $v if ($f eq "filename:");
		$chk = $v if (lc $f eq lc($ENV{DEBOOTSTRAP_CHECKSUM_FIELD}).":");
		$siz = $v if ($f eq "size:");
		$val = $v if ($f eq $field);
	} elsif (/^$/) {
		if (defined $val && defined $fields{$val}) {
			$output = sprintf "%s %s %s %s %s %s %s",
			 $pkg, $ver, $arc, $mirror, $fil, $chk, $siz;
			$output_val = $val;
		}
		undef $val;
	}
}
print $output if defined $output;
delete $fields{$output_val} if $unique && defined $output_val;
for $v (keys %fields) {
	printf ("%s -\n", $v) if ($unique);
}
' "$@"
	}

	pkgdetails_perl () {
		if [ "$1" = "WGET%" ]; then
			shift;
			perl -e '
$v = 0;
$allow_percentage = 0;
while (read STDIN, $x, 1) {
	if ($x =~ m/\s/) {
		$allow_percentage = 1;
	} elsif ($allow_percentage and $x =~ m/\d/) {
		$v *= 10;
		$v += $x;
	} elsif ($allow_percentage and $x eq "%") {
		printf "P: %d %d%s\n", int($v / 100.0 * ($ARGV[1] - $ARGV[0]) + $ARGV[0]), $ARGV[2], ($#ARGV == 3 ? " $ARGV[3]" : "");
		$v = 0;
	} else {
		$v = 0;
		$allow_percentage = 0;
	}
}' "$@"
		elif [ "$1" = "GETDEPS" ]; then
			local pkgdest="$2"; shift; shift
			perl -e '
$prevpkg = "";
@d = ();
my %exclude;
$exclude{$_} = 1 for split(" ", $ENV{"EXCLUDE"});
while (<STDIN>) {
	chomp;
	if (/^Package: (.*)$/) {
		$pkg = $1;
		if ($pkg ne $prevpkg) {
			for my $d (@d) {
				print "$d\n";
			}
		}
		$prevpkg = $1;
		@d = ();
	}
	$in = 1 if (grep {$_ eq $pkg} @ARGV);
	$in = 0 if (/^$/);
	if ($in and (/^Depends: (.*)$/ or /^Pre-Depends: (.*)$/)) {
		for $d (split /\s*,\s*/, $1) {
			for $p (split /\s*\|\s*/, $d) {
				$p =~ s/\s*[(].*[)]\s*//;
				if (!$exclude{$p}) {
					push @d, $p;
					last;
				}
			}
		}
	}
}
for my $d (@d) {
	print "$d\n";
}' <"$pkgdest" "$@" | sort | uniq
		elif [ "$1" = "PKGS" ]; then
			local m="$2"
			local p="$3"
			shift; shift; shift
			pkgdetails_field 1 Package: "$m" "$@" < "$p"
		elif [ "$1" = "FIELD" ]; then
			local f="$2"
			local m="$3"
			local p="$4"
			shift; shift; shift; shift
			pkgdetails_field 0 "$f" "$m" "$@" < "$p"
		elif [ "$1" = "STANZAS" ]; then
			local pkgdest="$2"; shift; shift
			perl -e '
my $accum = "";
while (<STDIN>) {
	$accum .= $_;
	$in = 1 if (/^Package: (.*)$/ && grep {$_ eq $1} @ARGV);
	if ($in and /^$/) {
		print $accum;
		if (substr($accum, -1) != "\n") {
			print "\n\n";
		} elsif (substr($accum, -2, 1) != "\n") {
			print "\n";
		}
		$in = 0;
	}
	$accum = "" if /^$/;
}' <"$pkgdest" "$@"
		fi
	}
elif [ -e "/usr/lib/debootstrap/pkgdetails" ]; then
	PKGDETAILS="/usr/lib/debootstrap/pkgdetails"
elif [ -e "$DEBOOTSTRAP_DIR/pkgdetails" ]; then
	PKGDETAILS="$DEBOOTSTRAP_DIR/pkgdetails"
else
	PKGDETAILS=""
fi

##################################################### dependency resolution

resolve_deps () {
	local m1="${MIRRORS%% *}"

	local PKGS="$*"
	local ALLPKGS="$PKGS";
	local ALLPKGS2="";
	while [ "$PKGS" != "" ]; do
		local NEWPKGS=""
		for c in ${COMPONENTS:-$USE_COMPONENTS}; do
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
			NEWPKGS="$NEWPKGS $("$PKGDETAILS" GETDEPS "$pkgdest" $PKGS)"
		done
		PKGS=$(echo "$PKGS $NEWPKGS" | tr ' ' '\n' | sort | uniq)
		local REALPKGS=""
		for c in ${COMPONENTS:-$USE_COMPONENTS}; do
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
			REALPKGS="$REALPKGS $("$PKGDETAILS" PKGS REAL "$pkgdest" $PKGS | sed -n 's/ .*REAL.*$//p')"
		done
		PKGS="$REALPKGS"
		ALLPKGS2=$(echo "$PKGS $ALLPKGS" | tr ' ' '\n' | sort | uniq)
		PKGS=$(without "$ALLPKGS2" "$ALLPKGS")
		ALLPKGS="$ALLPKGS2"
	done
	echo $ALLPKGS
}

setup_available () {
	local m1="${MIRRORS%% *}"

	for c in ${COMPONENTS:-$USE_COMPONENTS}; do
		local path="dists/$SUITE/$c/binary-$ARCH/Packages"
		local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
		# XXX: What if a package is in more than one component?
		# -- cjwatson 2009-07-29
		"$PKGDETAILS" STANZAS "$pkgdest" "$@"
	done >"$TARGET/var/lib/dpkg/available"

	for pkg; do
		echo "$pkg install"
	done | in_target dpkg --set-selections
}

get_next_predep () {
	local stanza="$(in_target_nofail dpkg --predep-package)"
	[ "$stanza" ] || return 1
	echo "$stanza" | grep '^Package:' | sed 's/^Package://; s/^ *//'
}

################################################################### helpers

# Return zero if it is possible to create devices and execute programs in
# this directory. (Both may be forbidden by mount options, e.g. nodev and
# noexec respectively.)
check_sane_mount () {
	mkdir -p "$1"

	case "$HOST_OS" in
	    *freebsd*|hurd*)
		;;
	    *)
		mknod "$1/test-dev-null" c 1 3 || return 1
		if ! echo test > "$1/test-dev-null"; then
			rm -f "$1/test-dev-null"
			return 1
		fi
		rm -f "$1/test-dev-null"
		;;
	esac

	SH=/bin/sh
	[ -x $SH ] || SH=`which sh`

	cat > "$1/test-exec" <<EOF
#! $SH
:
EOF
	chmod +x "$1/test-exec"
	if ! "$1/test-exec"; then
		rm -f "$1/test-exec"
		return 1
	fi
	rm -f "$1/test-exec"

	return 0
}

read_gpg_status () {
	badsig=
	unkkey=
	validsig=
	while read prefix keyword keyid rest; do
		[ "$prefix" = '[GNUPG:]' ] || continue
		case $keyword in
		    BADSIG)	badsig="$keyid" ;;
		    NO_PUBKEY)	unkkey="$keyid" ;;
		    VALIDSIG)	validsig="$keyid" ;;
		esac
	done
	if [ "$validsig" ]; then
		info VALIDRELSIG "Valid Release signature (key id %s)" "$validsig"
	elif [ "$badsig" ]; then
		error 1 BADRELSIG "Invalid Release signature (key id %s)" "$badsig"
	elif [ "$unkkey" ]; then
		error 1 UNKNOWNRELSIG "Release signed by unknown key (key id %s)" "$unkkey"
	else
		error 1 SIGCHECK "Error executing gpgv to check Release signature"
	fi
}

without () {
	# usage:  without "a b c" "a d" -> "b" "c"
	(echo $1 | tr ' ' '\n' | sort | uniq;
	 echo $2 $2 | tr ' ' '\n') | sort | uniq -u | tr '\n' ' '
	echo
}

# Formerly called 'repeat', but that's a reserved word in zsh.
repeatn () {
	local n="$1"
	shift
	while [ "$n" -gt 0 ]; do
		if "$@"; then
			break
		else
			n="$(( $n - 1 ))"
			sleep 1
		fi
	done
	if [ "$n" -eq 0 ]; then return 1; fi
	return 0
}

N_EXIT_THINGS=0
exit_function () {
	local n=0
	while [ "$n" -lt "$N_EXIT_THINGS" ]; do
		(eval $(eval echo \${EXIT_THING_$n}) 2>/dev/null || true)
		n="$(( $n + 1 ))"
	done
	N_EXIT_THINGS=0
}

trap "exit_function" 0
trap "exit 129" 1
trap "error 130 INTERRUPTED \"Interrupt caught ... exiting\"" 2
trap "exit 131" 3
trap "exit 143" 15

on_exit () {
	eval `echo EXIT_THING_${N_EXIT_THINGS}=\"$1\"`
	N_EXIT_THINGS="$(( $N_EXIT_THINGS + 1 ))"
}

############################################################## fakechroot tools

install_fakechroot_tools () {
	if [ "$VARIANT" = "fakechroot" ]; then
		export PATH=/usr/sbin:/sbin:$PATH
	fi

	mv "$TARGET/sbin/ldconfig" "$TARGET/sbin/ldconfig.REAL"
	echo \
"#!/bin/sh
echo
echo \"Warning: Fake ldconfig called, doing nothing\"" > "$TARGET/sbin/ldconfig"
	chmod 755 "$TARGET/sbin/ldconfig"

	echo \
"/sbin/ldconfig
/sbin/ldconfig.REAL
fakechroot" >> "$TARGET/var/lib/dpkg/diversions"

	mv "$TARGET/usr/bin/ldd" "$TARGET/usr/bin/ldd.REAL"
	cat << 'END' > "$TARGET/usr/bin/ldd"
#!/usr/bin/perl

# fakeldd
#
# Replacement for ldd with usage of objdump
#
# (c) 2003-2005 Piotr Roszatycki <dexter@debian.org>, BSD


my %libs = ();

my $status = 0;
my $dynamic = 0;
my $biarch = 0;

my $ldlinuxsodir = "/lib";
my @ld_library_path = qw(/usr/lib /lib);


sub ldso($) {
	my ($lib) = @_;
	my @files = ();

	if ($lib =~ /^\//) {
	    $libs{$lib} = $lib;
	    push @files, $lib;
	} else {
	    foreach my $ld_path (@ld_library_path) {
		next unless -f "$ld_path/$lib";
		my $badformat = 0;
		open OBJDUMP, "objdump -p $ld_path/$lib 2>/dev/null |";
	 	while (my $line = <OBJDUMP>) {
		    if ($line =~ /file format (\S*)$/) {
				$badformat = 1 unless $format eq $1;
				last;
		    }
		}
		close OBJDUMP;
		next if $badformat;
		$libs{$lib} = "$ld_path/$lib";
		push @files, "$ld_path/$lib";
	    }
	    objdump(@files);
	}
}


sub objdump(@) {
	my (@files) = @_;
	my @libs = ();

	foreach my $file (@files) {
	    open OBJDUMP, "objdump -p $file 2>/dev/null |";
	    while (my $line = <OBJDUMP>) {
		$line =~ s/^\s+//;
		my @f = split (/\s+/, $line);
		if ($line =~ /file format (\S*)$/) {
		    if (not $format) {
			$format = $1;
			if ($unamearch eq "x86_64" and $format eq "elf32-i386") {
			    my $link = readlink "/lib/ld-linux.so.2";
			    if ($link =~ /^\/emul\/ia32-linux\//) {
				$ld_library_path[-2] = "/emul/ia32-linux/usr/lib";
				$ld_library_path[-1] = "/emul/ia32-linux/lib";
			    }
			} elsif ($unamearch =~ /^(sparc|sparc64)$/ and $format eq "elf64-sparc") {
			    $ldlinuxsodir = "/lib64";
			    $ld_library_path[-2] = "/usr/lib64";
			    $ld_library_path[-1] = "/lib64";
			}
		    } else {
			next unless $format eq $1;
		    }
		}
		if (not $dynamic and $f[0] eq "Dynamic") {
		    $dynamic = 1;
		}
		next unless $f[0] eq "NEEDED";
		if ($f[1] =~ /^ld-linux(\.|-)/) {
		    $f[1] = "$ldlinuxsodir/" . $f[1];
		}
		if (not defined $libs{$f[1]}) {
		    $libs{$f[1]} = undef;
		    push @libs, $f[1];
		}
	    }
	    close OBJDUMP;
	}

	foreach my $lib (@libs) {
	    ldso($lib);
	}
}


if ($#ARGV < 0) {
	print STDERR "fakeldd: missing file arguments\n";
	exit 1;
}

while ($ARGV[0] =~ /^-/) {
	my $arg = $ARGV[0];
	shift @ARGV;
	last if $arg eq "--";
}

open LD_SO_CONF, "/etc/ld.so.conf";
while ($line = <LD_SO_CONF>) {
	chomp $line;
	unshift @ld_library_path, $line;
}
close LD_SO_CONF;

unshift @ld_library_path, split(/:/, $ENV{LD_LIBRARY_PATH});

$unamearch = `/bin/uname -m`;
chomp $unamearch;

foreach my $file (@ARGV) {
	my $address;
	%libs = ();
	$dynamic = 0;

	if ($#ARGV > 0) {
		print "$file:\n";
	}

	if (not -f $file) {
		print STDERR "ldd: $file: No such file or directory\n";
		$status = 1;
		next;
	}

	objdump($file);

	if ($dynamic == 0) {
		print "\tnot a dynamic executable\n";
		$status = 1;
	} elsif (scalar %libs eq "0") {
		print "\tstatically linked\n";
	}

	if ($format =~ /^elf64-/) {
		$address = "0x0000000000000000";
	} else {
		$address = "0x00000000";
	}

	foreach $lib (keys %libs) {
		if ($libs{$lib}) {
			printf "\t%s => %s (%s)\n", $lib, $libs{$lib}, $address;
		} else {
			printf "\t%s => not found\n", $lib;
		}
	}
}

exit $status;
END
	chmod 755 "$TARGET/usr/bin/ldd"

	echo \
"/usr/bin/ldd
/usr/bin/ldd.REAL
fakechroot" >> "$TARGET/var/lib/dpkg/diversions"

}
