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

proc smallyes {
	setvar YES = "${1-y}"
	while echo $YES  { : ; }
}

proc in_path {
	local OLD_IFS="$IFS"
	setvar IFS = "":""
	for dir in $PATH {
		if test -x "$dir/$1" {
			setvar IFS = "$OLD_IFS"
			return 0
		}
	}
	setvar IFS = "$OLD_IFS"
	return 1
}

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

proc error {
	# <error code> <name> <string> <args>
	local err="$1"
	local name="$2"
	local fmt="$3"
	shift; shift; shift
	if test $USE_DEBIANINSTALLER_INTERACTION {
		shell {echo "E: $name"
		for x in "$@" { echo "EA: $x"; }
		echo "EF: $fmt"} >&4
	} else {
		shell {printf "E: $fmt\n" @ARGV} >&4
	}
	exit $err
}

proc warning {
	# <name> <string> <args>
	local name="$1"
	local fmt="$2"
	shift; shift
	if test $USE_DEBIANINSTALLER_INTERACTION {
		shell {echo "W: $name"
		for x in "$@" { echo "WA: $x"; }
		echo "WF: $fmt"} >&4
	} else {
		printf "W: $fmt\n" @ARGV >&4
	}
}

proc info {
	# <name> <string> <args>
	local name="$1"
	local fmt="$2"
	shift; shift
	if test $USE_DEBIANINSTALLER_INTERACTION {
		shell {echo "I: $name"
		for x in "$@" { echo "IA: $x"; }
		echo "IF: $fmt"} >&4
	} else {
		printf "I: $fmt\n" @ARGV >&4
	}
}

setvar PROGRESS_NOW = '0'
setvar PROGRESS_END = '0'
setvar PROGRESS_NEXT = """"
setvar PROGRESS_WHAT = """"

proc progress_next {
	setvar PROGRESS_NEXT = "$1"
}

proc wgetprogress {
	test ! $VERBOSE && setvar QSWITCH = ""-q""
	local ret=0
	if test $USE_DEBIANINSTALLER_INTERACTION && test $PROGRESS_NEXT {
		wget @ARGV 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END >&3
		setvar ret = ""$?
	} else {
		wget $QSWITCH @ARGV 
		setvar ret = ""$?
	}
	return $ret
}

proc progress {
	# <now> <end> <name> <string> <args>
	local now="$1"
	local end="$2"
	local name="$3"
	local fmt="$4"
	shift; shift; shift; shift
	if test $USE_DEBIANINSTALLER_INTERACTION {
		setvar PROGRESS_NOW = "$now"
		setvar PROGRESS_END = "$end"
		setvar PROGRESS_NEXT = """"
		shell {echo "P: $now $end $name"
		for x in "$@" { echo "PA: $x"; }
		echo "PF: $fmt"} >&3
	}
}

proc 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 test $action = UNPACKING {
		setvar expect = 'half-installed'
	} elif test $action = CONFIGURING {
		setvar expect = 'half-configured'
	}

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

	setvar exitcode = '0'
	while read status pkg qstate {
		if test $status = "EXITCODE" {
			setvar exitcode = "$pkg"
			continue
		}
		test $qstate = $expect || continue
		case (qstate) {
		    half-installed {
			dp; progress $now $end $name $desc
			info $action "Unpacking %s..." ${pkg%:}
			setvar expect = 'unpacked'
			}
		    unpacked {
			setvar expect = 'half-installed'
			}
		    half-configured {
			dp; progress $now $end $name $desc
			info $action "Configuring %s..." ${pkg%:}
			setvar expect = 'installed'
			}
		    installed {
			setvar expect = 'half-configured'
			}
		}
	}
	return $exitcode
}

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

proc default_mirror {
	setvar DEF_MIRROR = "$1"
}

setvar FINDDEBS_NEEDS_INDICES = 'false'
proc finddebs_style {
	case (1) {
	    hardcoded {
		}
	    from-indices {
		setvar FINDDEBS_NEEDS_INDICES = 'true'
		}
	    * {
		error 1 BADFINDDEBS "unknown finddebs style"
		}
	 }
}

proc mk_download_dirs {
	if test $DLDEST = "apt_dest" {
		mkdir -p "$TARGET/$APTSTATE/lists/partial"
		mkdir -p "$TARGET/var/cache/apt/archives/partial"
	}
}

proc download_style {
	case (1) {
	    apt {
		if test $2 = "var-state" {
			setvar APTSTATE = "var/state/apt"
		} else {
			setvar APTSTATE = "var/lib/apt"
		}
		setvar DLDEST = 'apt_dest'
		export APTSTATE DLDEST DEBFOR
		}
	    * {
		error 1 BADDLOAD "unknown download style"
		}
	}
}

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

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

proc doing_variant {
	if test $1 = $VARIANT { return 0; }
	if test $1 = "-" && test $VARIANT = "" { return 0; }
	return 1
}

setvar SUPPORTED_VARIANTS = ""-""
proc variants {
	setvar SUPPORTED_VARIANTS = ""$[join(ARGV)]""
	for v in $* {
		if doing_variant $v { return 0; }
	}
	error 1 UNSUPPVARIANT "unsupported variant"
}

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

proc mirror_style {
	case (1) {
	    release {
		setvar DOWNLOAD_INDICES = 'download_release_indices'
		setvar DOWNLOAD_DEBS = 'download_release'
		}
	    main {
		setvar DOWNLOAD_INDICES = 'download_main_indices'
		setvar DOWNLOAD_DEBS = 'download_main'
		}
	    * {
		error 1 BADMIRROR "unknown mirror style"
		}
	}
	export DOWNLOAD_INDICES
	export DOWNLOAD_DEBS
}

proc force_md5 {
	setvar DEBOOTSTRAP_CHECKSUM_FIELD = 'MD5SUM'
	export DEBOOTSTRAP_CHECKSUM_FIELD
}

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

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

	if test -e $2 {
		if test -z $3 {
			return 0
		} elif test $3 = nocache {
			rm -f $2
		} else {
			info VALIDATING "Validating %s %s" $displayname $versionname
			if verify_checksum $2 $3 $4 {
				return 0
			} else {
				rm -f $2
			}
		}
	}
	# Drop 'nocache' option
	if test $3 = nocache {
		set $1 $2
	}

	if test "$Argc" -gt 5 {
		local st=3
		if test $5 = "-" { setvar st = '6'; }
		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
	}
	for a in $order {
		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) {
		    xz {  setvar from = ""$1.xz""; setvar dest = ""$2.xz"" }
		    bz2 { setvar from = ""$1.bz2""; setvar dest = ""$2.bz2"" }
		    gz {  setvar from = ""$1.gz""; setvar dest = ""$2.gz"" }
		    * {   setvar from = "$1"; setvar dest = "$2" }
		}

		if test ${dest#/} = $dest {
			setvar dest = ""./$dest""
		}
		local dest2="$dest"
		if test -d "${dest2%/*}/partial" {
			setvar dest2 = ""${dest2%/*}/partial/${dest2##*/}""
		}

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

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

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

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

proc debfor {
	shell {while read pkg path {
		for p in "$@" {
			test $p = $pkg || continue;
			echo $path
		}
	 } <"$TARGET/debootstrap/debpaths"
	}
}

proc apt_dest {
	# args:
	#   deb package version arch mirror path
	#   pkg suite component arch mirror path
	#   rel suite mirror path
	case (1) {
	    deb {
		echo "/var/cache/apt/archives/${2}_${3}_${4}.deb" | sed 's/:/%3a/'
		}
	    pkg {
		local m="$5"
		setvar 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"
		setvar 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'
		}
	}
}

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

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

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

setvar CODENAME = """"
proc validate_suite {
	local reldest="$1"

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

	if test $SUITE != $suite && test $SUITE != $CODENAME {
		error 1 WRONGSUITE "Asked to install suite %s, but got %s (codename: %s) from mirror" $SUITE $suite $CODENAME
	}
}

proc 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
	setvar nl = """"
	setvar state = 'pre-begin'''
	while IFS= read -r line {
		case{
		    pre-begin {
			if test "x${line}" = "x-----BEGIN PGP SIGNED MESSAGE-----" {
				setvar state = 'begin'
			}
			}
		    begin {
			if test "x${line}" = "x" {
				setvar state = 'data'
			}
			}
		    data {
			if test "x${line}" = "x-----BEGIN PGP SIGNATURE-----" {
				printf "%s\n" ${line} > "$relsigdest"
				setvar state = 'signature'
			} else {
				printf "${nl}%s" ${line} >> "$reldest"
				setvar nl = ""\n""
			}
			}
		    signature {
			printf "%s\n" ${line} >> "$relsigdest"
			if test "x${line}" = "x-----END PGP SIGNATURE-----" {
				break
			}
		}
}
	} < "$inreldest"
}

proc 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 {
		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"
	}
	if test -n $KEYRING && test -z $DISABLE_KEYRING {
		progress 0 100 DOWNRELSIG "Downloading Release file signature"
		if ! test -f $relsigdest {
			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"
		}

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

proc 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 {
		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 test $normi != "" {
			setvar i = "$normi"
		} elif in_path bunzip2 && test $bz2i != "" {
			setvar i = "$bz2i"
		} elif in_path unxz && test $xzi != "" {
			setvar i = "$xzi"
		} elif in_path gunzip && test $gzi != "" {
			setvar i = "$gzi"
		}
		if test $i != "" {
			setvar totalpkgs = """$(( $totalpkgs + ${i#* } ))"
		} else {
			mv $reldest "$reldest.malformed"
			error 1 MISSINGRELENTRY "Invalid Release file, no entry for %s" $subpath
		}
	}

	local donepkgs=0
	local pkgdest
	progress 0 $totalpkgs DOWNPKGS "Downloading Packages files"
	for c in $COMPONENTS {
		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 test $normi != "" {
			setvar ext = ""$ext $normi .""
			setvar i = "$normi"
		}
		if in_path unxz && test $xzi != "" {
			setvar ext = ""$ext $xzi xz""
			setvar i = "${i:-$xzi}"
		}
		if in_path bunzip2 && test $bz2i != "" {
			setvar ext = ""$ext $bz2i bz2""
			setvar i = "${i:-$bz2i}"
		}
		if in_path gunzip && test $gzi != "" {
			setvar ext = ""$ext $gzi gz""
			setvar i = "${i:-$gzi}"
		}
		progress_next "$(($donepkgs + ${i#* }))"
		for m in $MIRRORS {
			setvar pkgdest = ""$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")""
			if get "$m/$path" $pkgdest $ext { break; }
		}
		if test ! -f $pkgdest {
			error 1 COULDNTDL "Couldn't download %s" $path
		}
		setvar donepkgs = """$(($donepkgs + ${i#* }))"
		progress $donepkgs $totalpkgs DOWNPKGS "Downloading Packages files"
	}
}

proc get_package_sizes {
	# mirror pkgdest debs..
	local m="$1"; shift
	local pkgdest="$1"; shift
	$PKGDETAILS PKGS $m $pkgdest @ARGV | shell {
		setvar newleft = """"
		setvar totaldebs = '0'
		setvar countdebs = '0'
		while read p details {
			if test $details = "-" {
				setvar newleft = ""$newleft $p""
			} else {
				setvar size = "${details##* }";
				setvar totaldebs = """$(($totaldebs + $size))"
				setvar countdebs = """$(($countdebs + 1))"
			}
		}
		echo "$countdebs $totaldebs$newleft"
	}
}

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

	$PKGDETAILS PKGS $m $pkgdest @ARGV | shell {
		setvar leftover = """"
		while read p ver arc mdup fil checksum size {
			if test $ver = "-" {
				setvar 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 {
					setvar dloaddebs = """$(($dloaddebs + $size))"
					echo >>$TARGET/debootstrap/deburis "$p $ver $m/$fil>>$TARGET/debootstrap/deburis "$p $ver $m/$fil"
					echo >>$TARGET/debootstrap/debpaths "$p $debdest>>$TARGET/debootstrap/debpaths "$p $debdest"
				} else {
					warning COULDNTDL "Couldn't download package %s (ver %s arch %s)" $p $ver $arc
					setvar leftover = ""$leftover $p""
				}
			}
		}
		echo >&5 ${leftover# }>&5 ${leftover# }
	}
}

proc download_release {
	local m1="${MIRRORS%% *}"

	local numdebs="$Argc"

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

	local totaldebs=0
	local leftoverdebs="$[join(ARGV)]"

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

	for c in $COMPONENTS {
		if test $countdebs -ge $numdebs { break; }

		local path="dists/$SUITE/$c/binary-$ARCH/Packages"
		local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")"
		if test ! -e $pkgdest { continue; }

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

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

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

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

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

	if test $countdebs -ne $numdebs {
		error 1 LEFTOVERDEBS "Couldn't find these debs: %s" $leftoverdebs
	}

	local dloaddebs=0

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

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

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

	if test -z $comp { setvar comp = 'main'; }
	setvar COMPONENTS = "$(echo $comp | tr '|' ' ')"

	export COMPONENTS
	for m in $MIRRORS {
	    for c in $COMPONENTS {
		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" {
			rm -f $pkgdest
			gunzip "$pkgdest.gz"
		} elif get "$m/$path" $pkgdest {
			true
		}
	    }
	}
	progress 100 100 DOWNMAINPKGS "Downloading Packages file"
}

proc download_main {
	local m1="${MIRRORS%% *}"

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

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

proc get_debs {
	local field="$1"
	shift
	local m1 c
	for m1 in $MIRRORS {
		for c in $COMPONENTS {
			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/ .*//')
		}
	}
}

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

setvar EXTRACTORS_SUPPORTED = ""dpkg-deb ar""
setvar EXTRACT_DEB_TAR_OPTIONS = ''

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

	dpkg-deb -f $pkg $field
}

proc extract_dpkg_deb_data {
	local pkg="$1"

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

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

	case (tarball) {
		control.tar.gz { setvar cat_cmd = 'zcat' }
		control.tar.xz { setvar cat_cmd = 'xzcat' }
		control.tar { setvar cat_cmd = 'cat' }
		* { error 1 UNKNOWNCONTROLCOMP "Unknown compression type for %s in %s" $tarball $pkg }
	}

	if type $cat_cmd >/dev/null 2>&1 {
		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
	}
}

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

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

	if type $cat_cmd >/dev/null 2>&1 {
		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
	}
}

proc valid_extractor {
	local extractor="$1"

	for E in $EXTRACTORS_SUPPORTED {
		if test $extractor = $E {
			return 0
		}
	}

	return 1
}

proc choose_extractor {
	local extractor

	if test -n $EXTRACTOR_OVERRIDE {
		setvar extractor = "$EXTRACTOR_OVERRIDE"
	} elif type dpkg-deb >/dev/null 2>&1 {
		setvar extractor = ""dpkg-deb""
	} else {
		setvar extractor = ""ar""
	}

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

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

proc in_target_nofail {
	if ! $CHROOT_CMD @ARGV 2>/dev/null {
		true
	}
	return 0
}

proc in_target_failmsg {
	local code="$1"
	local msg="$2"
	local arg="$3"
	shift; shift; shift
	if ! $CHROOT_CMD @ARGV {
		warning $code $msg $arg
		# Try to point user at actual failing package.
		setvar msg = ""See %s for details""
		if test -e "$TARGET/debootstrap/debootstrap.log" {
			setvar 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 test -n $pkg {
				setvar msg = ""$msg (possibly the package $pkg is at fault)""
			}
		} else {
			setvar arg = ""the log""
		}
		warning $code $msg $arg
		return 1
	}
	return 0
}

proc in_target {
	in_target_failmsg IN_TARGET_FAIL "Failure trying to run: %s" "$CHROOT_CMD $[join(ARGV)]" @ARGV
}

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

proc conditional_cp {
	if test ! -e "$2/$1" {
		if test -L $1 && test -e $1 {
			cat $1 >"$2/$1"
		} elif test -e $1 {
			cp -a $1 "$2/$1"
		}
	}
}

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

proc setup_apt_sources {
	mkdir -p "$TARGET/etc/apt"
	for m in "$@" {
		local cs=""
		for c in ${COMPONENTS:-$USE_COMPONENTS} {
			local path="dists/$SUITE/$c/binary-$ARCH/Packages"
			local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")"
			if test -e $pkgdest { setvar cs = ""$cs $c""; }
		}
		if test $cs != "" { echo "deb $m $SUITE$cs"; }
	} > "$TARGET/etc/apt/sources.list"
}

proc setup_etc {
	mkdir -p "$TARGET/etc"

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

	if test $DLDEST = apt_dest && test ! -e "$TARGET/etc/apt/sources.list" {
		setup_apt_sources "http://debootstrap.invalid/"
	}
}

setvar UMOUNT_DIRS = ''

proc umount_exit_function {
	local realdir
	for dir in $UMOUNT_DIRS {
		setvar realdir = "$(in_target_nofail readlink -f "$dir")"
		test $realdir || continue
		shell { cd / ; umount "$TARGET/${realdir#/}" } || true
	}
}

proc umount_on_exit {
	if test $UMOUNT_DIRS {
		setvar UMOUNT_DIRS = ""$UMOUNT_DIRS $1""
	} else {
		setvar UMOUNT_DIRS = "$1"
		on_exit umount_exit_function
	}
}

proc clear_mtab {
	if test -f "$TARGET/etc/mtab" && test ! -h "$TARGET/etc/mtab" {
		rm -f "$TARGET/etc/mtab"
	}
}

proc setup_proc {
	case (HOST_OS) {
	    *freebsd* {
		umount_on_exit /dev
		umount_on_exit /proc
		umount "$TARGET/proc" 2>/dev/null || true
		if test $HOST_OS = kfreebsd {
			in_target mount -t linprocfs proc /proc
		} else {
			mount -t linprocfs proc $TARGET/proc
		}
		}
	    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 test -d "$TARGET/sys" && \
		   grep -q '[[:space:]]sysfs' /proc/filesystems 2>/dev/null {
			umount_on_exit /sys
			umount "$TARGET/sys" 2>/dev/null || true
			in_target mount -t sysfs sysfs /sys
		}
		on_exit clear_mtab
		}
	}
	umount_on_exit /lib/init/rw
}

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

# create the static device nodes
proc setup_devices {
	if doing_variant fakechroot {
		setup_devices_fakechroot
		return 0
	}

	case (HOST_OS) {
	    kfreebsd* {
		}
	    freebsd {
		}
	    hurd* {
		}
	    * {
		setup_devices_simple
		}
	}
}

# enable the dynamic device nodes
proc setup_dynamic_devices {
	if doing_variant fakechroot {
		return 0
	}

	case (HOST_OS) {
	    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 }
	}
}

proc 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/
	# Inside a container, we might not be allowed to create /dev/ptmx.
	# If not, do the next best thing.
	if ! mknod -m 666 $TARGET/dev/ptmx c 5 2 {
		warning MKNOD "Could not create /dev/ptmx, falling back to symlink. This chroot will require /dev/pts mounted with ptmxmode=666"
		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
}

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

proc setup_dselect_method {
	case (1) {
	    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"
		}
	}
}

# 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.
proc setup_merged_usr {
	if test $MERGED_USR = "no" { return 0; }

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

	local dir
	for dir in $link_dir {
		ln -s usr/$dir $TARGET/$dir
		mkdir -p $TARGET/usr/$dir
	}
}

################################################################ 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 {
	setvar PKGDETAILS = 'pkgdetails_perl'

	proc 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);
}
' @ARGV
	}

	proc pkgdetails_perl {
		if test $1 = "WGET%" {
			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;
	}
}' @ARGV
		} elif test $1 = "GETDEPS" {
			local pkgdest="$2"; shift; shift
			perl -e '
$prevpkg = "";
@d = ();
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) {
			$d =~ s/\s*[|].*$//;
			$d =~ s/\s*[(].*[)]\s*//;
			$d =~ s/:.*//;
			push @d, $d;
		}
	}
}
for my $d (@d) {
	print "$d\n";
}' <"$pkgdest" @ARGV<"$pkgdest" "$@" | sort | uniq
		} elif test $1 = "PKGS" {
			local m="$2"
			local p="$3"
			shift; shift; shift
			pkgdetails_field 1 Package: $m @ARGV < "$p"
		} elif test $1 = "FIELD" {
			local f="$2"
			local m="$3"
			local p="$4"
			shift; shift; shift; shift
			pkgdetails_field 0 $f $m @ARGV < "$p"
		} elif test $1 = "STANZAS" {
			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" @ARGV<"$pkgdest" "$@"
		}
	}
} elif test -e "/usr/lib/debootstrap/pkgdetails" {
	setvar PKGDETAILS = ""/usr/lib/debootstrap/pkgdetails""
} elif test -e "$DEBOOTSTRAP_DIR/pkgdetails" {
	setvar PKGDETAILS = ""$DEBOOTSTRAP_DIR/pkgdetails""
} else {
	setvar PKGDETAILS = """"
}

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

proc resolve_deps {
	local m1="${MIRRORS%% *}"

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

proc setup_available {
	local m1="${MIRRORS%% *}"

	for c in ${COMPONENTS:-$USE_COMPONENTS} {
		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 @ARGV
	} for pkg in @ARGV {
		echo "$pkg install"
	} | in_target dpkg --set-selections
}

proc get_next_predep {
	local stanza="$(in_target_nofail dpkg --predep-package)"
	test $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.)
proc check_sane_mount {
	mkdir -p $1

	case (HOST_OS) {
	    *freebsd*|hurd* {
		}
	    * {
		mknod "$1/test-dev-null" c 1 3 || return 1
		if ! echo test > "$1/test-dev-null" {
			rm -f "$1/test-dev-null"
			return 1
		}
		rm -f "$1/test-dev-null"
		}
	}

	setvar SH = "/bin/sh"
	test -x $SH || setvar SH = $(which sh)

	cat > "$1/test-exec" <<< """
#! $SH
:
"""
	chmod +x "$1/test-exec"
	if ! "$1/test-exec" {
		rm -f "$1/test-exec"
		return 1
	}
	rm -f "$1/test-exec"

	return 0
}

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

proc without {
	# usage:  without "a b c" "a d" -> "b" "c"
	shell {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.
proc repeatn {
	local n="$1"
	shift
	while test $n -gt 0 {
		if @ARGV {
			break
		} else {
			setvar n = """$(( $n - 1 ))"
			sleep 1
		}
	}
	if test $n -eq 0 { return 1; }
	return 0
}

setvar N_EXIT_THINGS = '0'
proc exit_function {
	local n=0
	while test $n -lt $N_EXIT_THINGS {
		shell {eval $(eval echo \${EXIT_THING_$n}) 2>/dev/null || true}
		setvar n = """$(( $n + 1 ))"
	}
	setvar 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

proc on_exit {
	eval $(echo EXIT_THING_${N_EXIT_THINGS}='"'$1'"')
	setvar N_EXIT_THINGS = """$(( $N_EXIT_THINGS + 1 ))"
}

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

proc install_fakechroot_tools {
	if test $VARIANT = "fakechroot" {
		export PATH=/usr/sbin:/sbin:$PATH
	}

	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 <<< ''' > "$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;
'''
> "$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"

}