1
0
Fork 0
debootstrap/functions
Daniel Baumann 04ec7bd664
Adding upstream version 1.0.141.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 09:44:37 +02:00

2181 lines
55 KiB
Text

# shellcheck shell=sh
############################################################### smallutils
smallyes() {
YES="${1-y}"
while echo "$YES" 2>/dev/null ; do : ; done
}
in_path () {
local OLD_IFS="$IFS"
local dir
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 name fmt x
err="$1"
name="$2"
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
# shellcheck disable=SC2059
(printf "E: $fmt\n" "$@") >&4
fi
exit "$err"
}
warning () {
# <name> <string> <args>
local name fmt x
name="$1"
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
# shellcheck disable=SC2059
printf "W: $fmt\n" "$@" >&4
fi
}
info () {
# <name> <string> <args>
local name fmt x
name="$1"
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
# shellcheck disable=SC2059
printf "I: $fmt\n" "$@" >&4
fi
}
PROGRESS_NOW=0
PROGRESS_END=0
PROGRESS_NEXT=""
progress_next () {
PROGRESS_NEXT="$1"
}
wgetprogress () {
[ ! "$VERBOSE" ] && NVSWITCH="-nv"
local ret=0
if [ "$USE_DEBIANINSTALLER_INTERACTION" ] && [ "$PROGRESS_NEXT" ]; then
# The exit status of a pipeline is that of the last command in
# the pipeline, so wget's exit status must be saved in the
# pipeline's first command. Since commands in a pipeline run in
# subshells, we have to print the exit status (on a file
# descriptor other than standard output, which is used by the
# pipeline itself) and then assign it to $ret outside of the
# pipeline. The "||" is necessary due to "set -e"; otherwise, a
# non-zero exit status would cause the echo command to be
# skipped. If wget succeeds, $ret will be "", so it then has to
# be set to a default value of 0.
ret=$({ { wget -U "debootstrap/$VERSION (debian-installer)" "$@" 2>&1 >/dev/null || echo $? >&2; } | "$PKGDETAILS" "WGET%" "$PROGRESS_NOW" "$PROGRESS_NEXT" "$PROGRESS_END" >&3; } 2>&1)
: "${ret:=0}"
else
wget -U "debootstrap/$VERSION" ${NVSWITCH:+"$NVSWITCH"} "$@"
ret=$?
fi
return $ret
}
progress () {
# <now> <end> <name> <string> <args>
local now end name fmt x
now="$1"
end="$2"
name="$3"
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 end name desc action expect
now="$1"
end="$2"
name="$3"
desc="$4"
action="$5"
expect=""
if [ "$action" = UNPACKING ]; then
expect=half-installed
elif [ "$action" = CONFIGURING ]; then
expect=half-configured
fi
dp () {
now=$((now + 1))
}
exitcode=0
while read -r 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 () {
# avoid unnecessary warning with --second-stage
if [ -z "$KEYRING" ] && [ "$SECOND_STAGE_ONLY" != true ]; then
if [ -e "$1" ]; then
KEYRING="$1"
elif [ -z "$DISABLE_KEYRING" ]; then
if [ -z "$USER_MIRROR" ] && [ -z "$FORCE_KEYRING" ]; then
if [ "http" = "${DEF_MIRROR%%://*}" ]; then
USER_MIRROR="https://${DEF_MIRROR#http://}"
info KEYRING "Keyring file not available at %s; switching to https mirror %s" "$1" "$USER_MIRROR"
fi
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
}
detect_container () {
if [ "${container-}" = lxc ]; then
CONTAINER="lxc"
elif [ "$container" = mmdebstrap-unshare ]; then
CONTAINER="mmdebstrap-unshare"
elif grep -qs container=lxc-libvirt /proc/1/environ; then
CONTAINER="lxc-libvirt"
elif grep -qs ^systemd-nspawn$ /run/systemd/container || grep -qs systemd-nspawn /proc/1/environ || [ "${container-}" = "systemd-nspawn" ]; then
CONTAINER="systemd-nspawn"
elif grep -qs '[[:space:]]/docker/.*/sys/fs/cgroup' /proc/1/mountinfo || [ -e "/.dockerenv" ]; then
CONTAINER="docker"
else
CONTAINER=""
fi
# TODO: detect sub-hurds
}
########################################################## variant handling
doing_variant () {
if [ "$1" = "$VARIANT" ]; then return 0; fi
if [ "$1" = "-" ] && [ "$VARIANT" = "" ]; then return 0; fi
return 1
}
SUPPORTED_VARIANTS="-"
variants () {
local v
SUPPORTED_VARIANTS="$*"
for v in "$@"; do
if doing_variant "$v"; then return 0; fi
done
error 1 UNSUPPVARIANT "unsupported variant"
}
########################################################### option handling
check_conflicting_option () {
if { [ "$set_what_to_do" = --foreign ] && [ "${1%%=*}" = --unpack-tarball ]; } || \
{ [ "${set_what_to_do%%=*}" = "--unpack-tarball" ] && [ "$1" = --foreign ]; }; then
LOOSEN_CONFLICTING_RESTRICTION="true"
elif [ -n "$set_what_to_do" ]; then
error 1 ARG_CONFLICTING "$set_what_to_do is specified with $1, please use only one of those options."
fi
set_what_to_do="$1"
}
################################################# 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}]
# args: from dest 'byhash' [checksum size] [alt {checksum size type}]
local displayname
local versionname=""
local from_base
local dest_base
local nocache=""
local byhash=""
local a
from_base="$1"; shift
dest_base="$1"; shift
if [ "$1" = "nocache" ]; then
nocache="true"; shift
elif [ "$1" = "byhash" ]; then
byhash="true"; shift
fi
if [ "${dest_base%.deb}" != "$dest_base" ]; then
displayname="$(echo "$dest_base" | sed 's,^.*/,,;s,_.*$,,')"
versionname="$(echo "$dest_base" | sed 's,^.*/,,' | cut -d_ -f2 | sed 's/%3a/:/')"
else
displayname="$(echo "$from_base" | sed 's,^.*/,,')"
fi
if [ -e "$dest_base" ]; then
if [ -z "${1-}" ]; then
return 0
elif [ -n "$nocache" ]; then
rm -f "$dest_base"
else
info VALIDATING "Validating %s %s" "$displayname" "$versionname"
if verify_checksum "$dest_base" "$1" "$2"; then
return 0
else
rm -f "$dest_base"
fi
fi
fi
if [ "$#" -gt 3 ]; then
local st=1
if [ "$3" = "-" ]; then st=4; fi
local order
order="$(a=$st; while [ "$a" -le $# ]; do eval "echo \"\${$((a+1))}\" $a";
a=$((a + 3)); done | sort -n | sed 's/.* //')"
else
local order=1
fi
for a in $order; do
local checksum siz typ from dest iters
checksum="$(eval "echo \${$a-}")"
siz="$(eval "echo \${$(( a+1 ))-}")"
typ="$(eval "echo \${$(( a+2 ))-}")"
iters="0"
case "$typ" in
xz) from="$from_base.xz"; dest="$dest_base.xz" ;;
bz2) from="$from_base.bz2"; dest="$dest_base.bz2" ;;
gz) from="$from_base.gz"; dest="$dest_base.gz" ;;
*) from="$from_base"; dest="$dest_base" ;;
esac
if [ -n "$CACHE_DIR" ]; then
dest="${dest%%*/}"
elif [ "${dest#/}" = "$dest" ]; then
dest="./$dest"
fi
local dest2="$dest"
if [ -d "${dest2%/*}/partial" ]; then
dest2="${dest2%/*}/partial/${dest2##*/}"
fi
while [ "$iters" -lt 10 ]; do
local from2=""
info RETRIEVING "Retrieving %s %s" "$displayname" "$versionname"
if [ "$checksum" != "" ] && [ "$byhash" != "" ]; then
# assume we don't mix acquire-by-hash and md5
from2="$(dirname "$from")/by-hash/SHA${SHA_SIZE}/$checksum"
fi
if [ ! -e "$dest2" ]; then
if [ -z "$from2" ] || ! just_get "$from2" "$dest2"; then
if ! just_get "$from" "$dest2"; then continue 2; fi
fi
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#https://}" != "$from" ] || [ "${from#ftp://}" != "$from" ]; then
# http/https/ftp mirror
if wgetprogress ${CHECKCERTIF:+"$CHECKCERTIF"} ${CERTIFICATE:+"$CERTIFICATE"} ${PRIVATEKEY:+"$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
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 -r 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"
printf "%s" "$APTSTATE/lists/"
echo "${m}_$6" | sed -e 's,^[^:]\+://,,' -e 's/\//_/g'
;;
rel)
local m="$3"
printf "%s" "$APTSTATE/lists/"
echo "${m}_$4" | sed -e 's,^[^:]\+://,,' -e 's/\//_/g'
;;
esac
}
################################################################## download
get_release_checksum () {
local reldest path
reldest="$1"
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 -r a b c; do
if [ "$c" = "$path" ]; then echo "$a $b"; fi
done | head -n 1
}
extract_release_components () {
local c
local reldest="$1"; shift
TMPCOMPONENTS="$(sed -n 's/Components: *//p' "$reldest")"
for c in $TMPCOMPONENTS ; do
case " ${COMPONENTS-} " in
*" $c "*) continue ;;
esac;
eval "
case \"\$c\" in
$USE_COMPONENTS)
COMPONENTS=\"\${COMPONENTS-} \$c\"
;;
esac
"
done
if [ -z "${COMPONENTS-}" ]; then
mv "$reldest" "$reldest.malformed"
error 1 INVALIDREL "Invalid Release file, no valid components"
fi
}
repo_supports_arch_all () {
local a no_arch_all_support
local reldest="$1"; shift
TMPARCHS="$(sed -n 's/Architectures: *//p' "$reldest")"
ARCH_ALL_SUPPORTED=0
for a in $TMPARCHS ; do
if [ "$a" = "all" ]; then
ARCH_ALL_SUPPORTED=1
break
fi
done
no_arch_all_support=$(grep "^No-Support-for-Architecture-all: Packages$" "$reldest" || true)
if [ "$no_arch_all_support" != "" ]; then
ARCH_ALL_SUPPORTED=0
fi
}
CODENAME=""
validate_suite () {
local reldest suite s
reldest="$1"
CODENAME=$(sed -n "s/^Codename: *//p" "$reldest")
suite=$(sed -n "s/^Suite: *//p" "$reldest")
for s in $SUITE $EXTRA_SUITES; do
if [ "$s" = "$suite" ] || [ "$s" = "$CODENAME" ]; then
return 0
fi
done
if [ "$EXTRA_SUITES" = "" ]; then
error 1 WRONGSUITE "Asked to install suite %s, but got %s (codename: %s) from mirror" "$SUITE" "$suite" "$CODENAME"
else
error 1 WRONGSUITE "Asked to install suites %s %s, but got %s (codename: %s) from mirror" "$SUITE" "$EXTRA_SUITES" "$suite" "$CODENAME"
fi
}
split_inline_sig () {
local inreldest reldest relsigdest
inreldest="$1"
reldest="$2"
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 [ "${line}" = "-----BEGIN PGP SIGNED MESSAGE-----" ]; then
state="begin"
fi
;;
begin)
if [ "${line}" = "" ]; then
state="data"
fi
;;
data)
if [ "${line}" = "-----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 [ "${line}" = "-----END PGP SIGNATURE-----" ]; then
break
fi
esac
done < "$inreldest"
}
download_release_sig () {
local m1 suite inreldest reldest relsigdest
m1="$1"
suite="$2"
inreldest="$3"
reldest="$4"
relsigdest="$5"
progress 0 100 DOWNREL "Downloading Release file"
progress_next 100
if [ -n "$INRELEASE_PATH" ]; then
get "$m1/dists/$suite/$INRELEASE_PATH" "$inreldest" nocache ||
error 1 NOGETREL "Failed getting release file %s" \
"$m1/dists/$suite/$INRELEASE_PATH"
split_inline_sig "$inreldest" "$reldest" "$relsigdest"
progress 100 100 DOWNREL "Downloading Release file"
elif 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"
# If everything is installed, prefer gpgv for now
if in_path gpgv; then
# 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
elif in_path sopv; then
local rc=0
sopv verify "$relsigdest" "$KEYRING" <"$reldest" || rc=$?
check_sop_status "$rc"
elif in_path sqv; then
local rc=0
sqv --keyring "$KEYRING" "$relsigdest" "$reldest" || rc=$?
check_sqv_status "$rc"
else
# This is already checked at argument parsing time, so shouldn't happen here
error 1 NEEDPGPV "none of sopv, sqv or gpgv are installed, but required for Release verification"
fi
progress 100 100 DOWNRELSIG "Downloading Release file signature"
fi
}
download_release_indices () {
local m1 inreldest reldest relsigdest totalpkgs \
subpath xzi bz2i gzi normi i ext \
donepkgs pkgdest acquirebyhash archs s c a m
m1="${MIRRORS%% *}"
for s in $SUITE $EXTRA_SUITES; do
inreldest="$TARGET/$($DLDEST rel "$s" "$m1" "dists/$s/InRelease")"
reldest="$TARGET/$($DLDEST rel "$s" "$m1" "dists/$s/Release")"
relsigdest="$TARGET/$($DLDEST rel "$s" "$m1" "dists/$s/Release.gpg")"
download_release_sig "$m1" "$s" "$inreldest" "$reldest" "$relsigdest"
validate_suite "$reldest"
extract_release_components "$reldest"
repo_supports_arch_all "$reldest"
archs="$ARCH"
if [ $ARCH_ALL_SUPPORTED -eq 1 ]; then
archs="all $ARCH"
fi
acquirebyhash=$(grep "^Acquire-By-Hash: yes$" "$reldest" || true)
for a in $archs; do
totalpkgs=0
for c in $COMPONENTS; do
subpath="$c/binary-$a/Packages"
xzi="$(get_release_checksum "$reldest" "$subpath.xz")"
bz2i="$(get_release_checksum "$reldest" "$subpath.bz2")"
gzi="$(get_release_checksum "$reldest" "$subpath.gz")"
normi="$(get_release_checksum "$reldest" "$subpath")"
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
donepkgs=0
progress 0 $totalpkgs DOWNPKGS "Downloading Packages files for $a"
for c in $COMPONENTS; do
subpath="$c/binary-$a/Packages"
path="dists/$s/$subpath"
xzi="$(get_release_checksum "$reldest" "$subpath.xz")"
bz2i="$(get_release_checksum "$reldest" "$subpath.bz2")"
gzi="$(get_release_checksum "$reldest" "$subpath.gz")"
normi="$(get_release_checksum "$reldest" "$subpath")"
ext=""
if [ "$acquirebyhash" != "" ]; then
ext="$ext byhash"
fi
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 "$s" "$c" "$a" "$m" "$path")"
if get "$m/$path" "$pkgdest" $ext; then break; fi
done
if [ ! -f "$pkgdest" ]; then
error 1 COULDNTDL "Couldn't download %s" "$m/$path"
fi
donepkgs=$((donepkgs + ${i#* }))
progress $donepkgs $totalpkgs DOWNPKGS "Downloading Packages files"
done
done
done
}
get_package_sizes () {
# mirror pkgdest debs..
local m pkgdest
m="$1"; shift
pkgdest="$1"; shift
$PKGDETAILS PKGS "$m" "$pkgdest" "$@" | (
newleft=""
totaldebs=0
countdebs=0
while read -r 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 pkgdest debdest debcache
m="$1"
pkgdest="$2"
shift; shift
"$PKGDETAILS" PKGS "$m" "$pkgdest" "$@" | (
leftover=""
while read -r p ver arc mdup fil checksum size; do
if [ "$ver" = "-" ]; then
leftover="$leftover $p"
else
progress_next $((dloaddebs + size))
debdest="$($DLDEST deb "$p" "$ver" "$arc" "$m" "$fil")"
debcache="$(echo "$p"_"$ver"_"$arc".deb | sed 's/:/%3a/')"
if [ -z "$CACHE_DIR" ] && 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"
elif [ -d "$CACHE_DIR" ] && get "$m/$fil" "$CACHE_DIR/$debcache" "$checksum" "$size"; then
dloaddebs=$((dloaddebs + size))
echo >>"$TARGET/debootstrap/deburis" "$p $ver $m/$fil"
echo >>"$TARGET/debootstrap/debpaths" "$p $debdest"
cp "$CACHE_DIR/$debcache" "$TARGET/$debdest"
else
warning COULDNTDL "Couldn't download package %s (ver %s arch %s) at %s" "$p" "$ver" "$arc" "$m/$fil"
leftover="$leftover $p"
fi
fi
done
echo >&5 ${leftover# }
)
}
download_release () {
local m1 numdebs countdebs totaldebs leftoverdebs path pkgdest \
dloaddebs archs s c a m
m1="${MIRRORS%% *}"
numdebs="$#"
countdebs=0
progress "$countdebs" "$numdebs" SIZEDEBS "Finding package sizes"
totaldebs=0
leftoverdebs="$*"
# Fix possible duplicate package names, which would screw up counts:
leftoverdebs=$(printf "%s" "$leftoverdebs"|tr ' ' '\n'|sort -u|tr '\n' ' ')
numdebs=$(printf "%s" "$leftoverdebs"|wc -w)
archs="$ARCH"
if [ $ARCH_ALL_SUPPORTED -eq 1 ]; then
archs="all $ARCH"
fi
for s in $SUITE $EXTRA_SUITES; do
for a in $archs; do
for c in $COMPONENTS; do
if [ "$countdebs" -ge "$numdebs" ]; then break; fi
path="dists/$s/$c/binary-$a/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$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
done
done
if [ "$countdebs" -ne "$numdebs" ]; then
error 1 LEFTOVERDEBS "Couldn't find these debs: %s" "$leftoverdebs"
fi
dloaddebs=0
progress "$dloaddebs" "$totaldebs" DOWNDEBS "Downloading packages"
:>"$TARGET/debootstrap/debpaths"
pkgs_to_get="$*"
for s in $SUITE $EXTRA_SUITES; do
for a in $archs; do
for c in $COMPONENTS; do
path="dists/$s/$c/binary-$a/Packages"
for m in $MIRRORS; do
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$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
done
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 comp path pkgdest m s c
m1="${MIRRORS%% *}"
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 s in $SUITE $EXTRA_SUITES; do
for c in $COMPONENTS; do
path="dists/$s/$c/binary-$ARCH/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$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
done
progress 100 100 DOWNMAINPKGS "Downloading Packages file"
}
download_main () {
local m1 path pkgdest debdest p s c m
m1="${MIRRORS%% *}"
:>"$TARGET/debootstrap/debpaths"
for p in "$@"; do
for s in $SUITE $EXTRA_SUITES; do
for c in $COMPONENTS; do
local details=""
for m in $MIRRORS; do
path="dists/$s/$c/binary-$ARCH/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$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% *}"
debdest="$($DLDEST deb $details)"
if get "$m/${details##* }" "$TARGET/$debdest" "$checksum" "$size"; then
echo >>"$TARGET/debootstrap/debpaths" "$p $debdest"
details="done"
break
else
details=""
fi
done
if [ "$details" != "" ]; then
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 m1 s c a path pkgdest
field="$1"
shift
archs="$ARCH"
if [ $ARCH_ALL_SUPPORTED -eq 1 ]; then
archs="all $ARCH"
fi
for m1 in $MIRRORS; do
for s in $SUITE $EXTRA_SUITES; do
for c in $COMPONENTS; do
for a in $archs; do
path="dists/$s/$c/binary-$a/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$m1" "$path")"
"$PKGDETAILS" FIELD "$field" "$m1" "$pkgdest" "$@" | sed 's/ .*//'
done
done
done
done
}
################################################################ extraction
EXTRACTORS_SUPPORTED="dpkg-deb ar"
EXTRACT_DEB_TAR_OPTIONS=
# Native dpkg-deb based extractors
extract_dpkg_deb_field () {
local pkg field
pkg="$1"
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 - || error 1 FILEEXIST "Tried to extract package, but tar failed. Exit..."
}
# Raw .deb extractors
extract_ar_deb_field () {
local pkg field tarball
pkg="$1"
field="$2"
tarball=$(ar -t "$pkg" | grep "^control\.tar")
case "$tarball" in
control.tar.gz) cat_cmd=zcat ;;
control.tar.xz) cat_cmd=xzcat ;;
control.tar.zst) cat_cmd=zstdcat ;;
control.tar) cat_cmd=cat ;;
*) error 1 UNKNOWNCONTROLCOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;;
esac
if in_path $cat_cmd; 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 tarball
pkg="$1"
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.zst) cat_cmd=zstdcat ;;
data.tar) cat_cmd=cat ;;
*) error 1 UNKNOWNDATACOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;;
esac
if in_path "$cat_cmd"; 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"
local E
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 in_path dpkg-deb; 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" || exit 1
local p cat_cmd pkg
p=0
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 ! PATH=/sbin:/usr/sbin:/bin:/usr/bin eval "$CHROOT_CMD \"\$@\"" 2>/dev/null; then
true
fi
return 0
}
in_target_failmsg () {
local code msg arg
code="$1"
msg="$2"
arg="$3"
shift; shift; shift
if ! PATH=/sbin:/usr/sbin:/bin:/usr/bin eval "$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
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 "$1" "$2/$1"
fi
fi
}
setup_apt_sources () {
local m s c
mkdir -p "$TARGET/etc/apt"
for m in "$@"; do
for s in $SUITE $EXTRA_SUITES; do
local cs c path pkgdest
cs=""
for c in ${COMPONENTS:-$USE_COMPONENTS}; do
path="dists/$s/$c/binary-$ARCH/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$ARCH" "$m" "$path")"
if [ -e "$pkgdest" ]; then cs="$cs $c"; fi
done
if [ "$cs" != "" ]; then echo "deb $m $s$cs"; fi
done
done > "$TARGET/etc/apt/sources.list"
}
setup_etc () {
mkdir -p "$TARGET/etc"
conditional_cp /etc/resolv.conf "$TARGET"
conditional_cp /etc/hostname "$TARGET"
}
UMOUNT_DIRS=
umount_exit_function () {
local realdir dir
for dir in $UMOUNT_DIRS; do
realdir="$(in_target_nofail readlink -f "$dir")"
[ "$realdir" ] || continue
# if /proc was recursively bind-mounted, because we are running
# inside an unshared user namespace inside docker for example,
# then it cannot be recursively unmounted (even when mounted
# with rslave) so we do it lazily instead
if [ "$dir" = "/proc" ]; then
( cd / ; umount --lazy "$TARGET/${realdir#/}" ) || true
else
( cd / ; umount "$TARGET/${realdir#/}" ) || true
fi
done
}
umount_on_exit () {
if [ "$UMOUNT_DIRS" ]; then
UMOUNT_DIRS="$1 $UMOUNT_DIRS"
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 /dev/fd
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*)
if [ "$TARGET" != / ]; then
settrans -a "$TARGET/proc" /hurd/firmlink /proc
fi
;;
*)
umount_on_exit /dev/pts
umount_on_exit /dev/shm
umount_on_exit /proc
umount_on_exit /proc/bus/usb
if [ -L "$TARGET/proc" ];then
rm -f $TARGET/proc
mkdir $TARGET/proc
else
umount "$TARGET/proc" 2>/dev/null || true
fi
# some container environment are used at second-stage, it already treats /proc and so on
if [ -z "$(ls -A "$TARGET/proc")" ]; then
# second-stage in docker, we cannot detect it is inside docker... just ignore warning
mount -t proc proc "$TARGET/proc" || mount -o rbind /proc "$TARGET/proc" || true
umount_on_exit /proc
fi
if [ -n "$(ls -A "$TARGET/sys")" ] && \
grep -qs '[[:space:]]sysfs' "$TARGET/proc/filesystems" || \
[ "$CONTAINER" = "docker" ]; then
umount_on_exit /sys
umount "$TARGET/sys" 2>/dev/null || true
else
# second-stage in docker, we cannot detect it is inside docker... just ignore warning
mount -t sysfs sysfs "$TARGET/sys" || true
umount_on_exit /sys
fi
on_exit clear_mtab
;;
esac
umount_on_exit /lib/init/rw
}
setup_proc_symlink () {
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|darwin)
;;
hurd*)
touch "$TARGET/servers/exec"
touch "$TARGET/servers/startup"
touch "$TARGET/dev/console"
;;
*)
if ! setup_devices_simple ||
! sh -c ': >"$1"' -- "$TARGET/dev/null" 2>/dev/null; then
setup_devices_bind
fi
;;
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"
mount -t fdescfs -o linrdlnk fdescfs "$TARGET/dev/fd" ;;
hurd*)
# Use the setup-translators of the hurd package
in_target /usr/lib/hurd/setup-translators -k
# firmlink $TARGET/{dev,servers} to the system ones.
if [ "$TARGET" != / ]; then
settrans -a "$TARGET/dev" /hurd/firmlink /dev
settrans -a "$TARGET/servers" /hurd/firmlink /servers
fi
;;
esac
}
# Create a device node if it does not exist. By default, the mode is 666.
mknod_if_needed () {
local device type major minor mode
device="$1"
type="$2"
major="$3"
minor="$4"
mode="${5:-666}"
if [ ! -e "$device" ]; then
mknod -m "$mode" "$device" "$type" "$major" "$minor"
fi
}
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_if_needed "$TARGET/dev/null" c 1 3
mknod_if_needed "$TARGET/dev/zero" c 1 5
mknod_if_needed "$TARGET/dev/full" c 1 7
mknod_if_needed "$TARGET/dev/random" c 1 8
mknod_if_needed "$TARGET/dev/urandom" c 1 9
mknod_if_needed "$TARGET/dev/tty" c 5 0
if [ ! "$CONTAINER" = "systemd-nspawn" ]; then
mknod_if_needed "$TARGET/dev/console" c 5 1
fi
# To avoid pre-exist directory causes error, specify "-p" option
mkdir -p "$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_if_needed "$TARGET/dev/ptmx" c 5 2; then
warning MKNOD "Could not create /dev/ptmx, falling back to symlink. This chroot will require /dev/pts mounted with ptmxmode=666"
ln -sf pts/ptmx "$TARGET/dev/ptmx"
fi
ln -sfT /proc/self/fd "$TARGET/dev/fd"
ln -sf /proc/self/fd/0 "$TARGET/dev/stdin"
ln -sf /proc/self/fd/1 "$TARGET/dev/stdout"
ln -sf /proc/self/fd/2 "$TARGET/dev/stderr"
}
setup_devices_fakechroot () {
rm -rf "${TARGET:?}/dev"
ln -s /dev "$TARGET"
}
setup_devices_bind () {
local device
mount -t tmpfs nodev "$TARGET/dev"
umount_on_exit /dev
for device in null zero full random urandom tty pts shm ptmx; do
if [ -d "/dev/$device" ]; then
mkdir "$TARGET/dev/$device"
elif [ -c "/dev/$device" ]; then
touch "$TARGET/dev/$device"
else
continue
fi
mount -o bind "/dev/$device" "$TARGET/dev/$device"
umount_on_exit "/dev/$device"
done
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_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
}
can_usrmerge_symlink() {
# Absolute symlinks can be relocated without problems.
test "${2#/}" = "$2" || return 0
while :; do
if test "${2#/}" != "$2"; then
# Handle double-slashes.
set -- "$1" "${2#/}"
elif test "${2#./}" != "$2"; then
# Handle ./ inside a link target.
set -- "$1" "${2#./}"
elif test "$2" = ..; then
# A parent directory symlink is ok if it does not
# cross the top level directory.
test "${1%/*/*}" != "$1" -a -n "${1%/*/*}"
return $?
elif test "${2#../}" != "$2"; then
# Symbolic link crossing / cannot be moved safely.
# This is prohibited by Debian Policy 10.5.
test "${1%/*/*}" = "$1" -o -z "${1%/*/*}" && return 1
set -- "${1%/*}" "${2#../}"
else
# Consider the symlink ok if its target does not
# contain a parent directory. When we fail here,
# the link target is non-minimal and doesn't happen
# in the archive.
test "${2#*/../}" = "$2"
return $?
fi
done
}
merge_usr_entry() {
local entry canon
canon="$TARGET/usr/${1#"$TARGET/"}"
test -h "$canon" &&
error 1 USRMERGEFAIL "cannot move %s as its destination exists as a symlink" "${1#"$TARGET"}"
if ! test -e "$canon"; then
mv "$1" "$canon"
return 0
fi
test -d "$1" ||
error 1 USRMERGEFAIL "cannot move non-directory %s as its destination exists" "${1#"$TARGET"}"
test -d "$canon" ||
error 1 USRMERGEFAIL "cannot move directory %s as its destination is not a directory" "${1#"$TARGET"}"
for entry in "$1/"* "$1/."*; do
# Some shells return . and .. on dot globs.
test "${entry%/.}" != "${entry%/..}" && continue
if test -h "$entry" && ! can_usrmerge_symlink "${entry#"$TARGET"}" "$(readlink "$entry")"; then
error 1 USRMERGEFAIL "cannot move relative symlink crossing top-level directory" "${entry#"$TARGET"}"
fi
# Ignore glob match failures
if test "${entry%'/*'}" != "${entry%'/.*'}" && ! test -e "$entry"; then
continue
fi
merge_usr_entry "$entry"
done
rmdir "$1"
}
merge_usr() {
if [ "$MERGED_USR" = "no" ]; then
# With the usrmerge becoming pseudo-essential we need to use this flag
# to ensure that even if it gets installed the buildd is not converted
# when debootstrap needs to create an unmerged-usr installation.
case "$CODENAME" in
etch*|lenny|squeeze|wheezy|jessie*|stretch|buster|bullseye)
;;
*)
mkdir -p "$TARGET/etc"
echo "this system will not be supported in the future" > "$TARGET/etc/unsupported-skip-usrmerge-conversion"
if ! doing_variant buildd; then
warning SANITYCHECK "Upgrading non-merged-/usr environments post-bookworm is unsupported. Only do this for CI/QA infrastructure that will be re-bootstrapped rather than upgraded."
fi
;;
esac
return 0;
fi
local dir
# This is list includes all possible multilib directories. It must be
# updated when new multilib directories are being added. Hopefully,
# all new architectures use multiarch instead, so we never get to
# update this.
for dir in bin lib lib32 lib64 libo32 libx32 sbin; do
test -h "$TARGET/$dir" && continue
test -e "$TARGET/$dir" || continue
merge_usr_entry "$TARGET/$dir"
ln -s "usr/$dir" "$TARGET/$dir"
done
}
# Previous implementation of merged /usr: not used within debootstrap,
# but used by mmdebstrap's hooks/merged-usr/setup00.sh, mainly to get
# the correct per-architecture $link_dir list of non-default multilib
# directories.
setup_merged_usr() {
if doing_variant buildd && [ -z "$MERGED_USR" ]; then
case "$CODENAME" in
etch*|lenny|squeeze|wheezy|jessie*|stretch|buster|bullseye|bookworm)
MERGED_USR="no"
;;
esac
fi
if [ "$MERGED_USR" = "no" ]; then
# With the usrmerge becoming pseudo-essential we need to use this flag
# to ensure that even if it gets installed the buildd is not converted
# when debootstrap needs to create an unmerged-usr installation.
case "$CODENAME" in
etch*|lenny|squeeze|wheezy|jessie*|stretch|buster|bullseye)
;;
*)
mkdir -p "$TARGET/etc"
echo "this system will not be supported in the future" > "$TARGET/etc/unsupported-skip-usrmerge-conversion"
if ! doing_variant buildd; then
warning SANITYCHECK "Upgrading non-merged-/usr environments post-bookworm is unsupported. Only do this for CI/QA infrastructure that will be re-bootstrapped rather than upgraded."
fi
;;
esac
return 0;
fi
local link_dir=""
case $ARCH in
amd64) link_dir="lib32 lib64 libx32" ;;
i386) link_dir="lib64 libx32" ;;
mips|mipsel)
link_dir="lib32 lib64" ;;
mips64*|mipsn32*)
link_dir="lib32 lib64 libo32" ;;
loongarch64*)
link_dir="lib32 lib64" ;;
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
# test if grep supports --perl-regexp
set +e
echo x | grep --perl-regexp . >/dev/null 2>&1
if [ $? -eq 2 ]; then
gropt=-E
else
gropt=--perl-regexp
fi
set -e
pkgdetails_field () {
# uniq field mirror Packages values...
perl -le '
$unique = shift @ARGV; $field = lc(shift @ARGV); $mirror = shift @ARGV;
%expected = map { $_, 0 } @ARGV;
%outputs;
$prevpkg = "";
$chksumfield = lc($ENV{DEBOOTSTRAP_CHECKSUM_FIELD}).":";
sub emit_or_store_pkg {
if ($unique && defined $output_val) {
# Store the output for deduplicated emission later
$outputs{$output_val} = $output;
} else {
print $output if defined $output;
}
}
while (<STDIN>) {
if (/^([^:]*:)\s*(.*)$/) {
$f = lc($1); $v = $2;
if ($f eq "package:") {
$pkg = $v;
if ($pkg ne $prevpkg) {
emit_or_store_pkg;
$prevpkg = $pkg;
}
undef $output;
undef $output_val;
}
$ver = $v if ($f eq "version:");
$arc = $v if ($f eq "architecture:");
$fil = $v if ($f eq "filename:");
$chk = $v if ($f eq $chksumfield);
$siz = $v if ($f eq "size:");
$val = $v if ($f eq $field);
} elsif (/^$/) {
if (defined $val && defined $expected{$val}) {
$output = sprintf "%s %s %s %s %s %s %s",
$pkg, $ver, $arc, $mirror, $fil, $chk, $siz;
$output_val = $val;
}
undef $val;
}
}
emit_or_store_pkg;
if ($unique) {
# Emit all of our deduplicated values
map { print } sort values %outputs;
# And emit any expected packages that were not found
foreach my $v (keys %expected) {
printf ("%s -\n", $v) if !defined $outputs{$v};
}
}
' "$@"
}
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
LC_ALL=C grep "$gropt" '^$|^Package:|^Depends:|^Pre-Depends:' "${pkgdest}" | perl -e '
%seen = map { $_ => 1 } @ARGV;
while (<STDIN>) {
if (/^Package: (.*)$/) {
$pkg = $1;
next;
} elsif (/^$/) {
$in = 0;
next;
}
$in = 1 if $seen{$pkg};
if ($in and (/^Depends: (.*)$/ or /^Pre-Depends: (.*)$/)) {
for $d (split /\s*,\s*/, $1) {
$d =~ s/\s*[|].*$//;
$d =~ s/\s*[(].*[)]\s*//;
$d =~ s/:.*//;
$depends{$d} = 1;
}
}
}
foreach (sort keys %depends) {
print "$_\n";
}
' "$@"
elif [ "$1" = "PKGS" ]; then
local m="$2"
local p="$3"
shift; shift; shift
LC_ALL=C grep "$gropt" '^$|^Architecture:|^Filename:|^MD5sum:|^Package:|^Priority:|^SHA256:|^Size:|^Version:|^Depends:|^Pre-Depends:' "$p" | pkgdetails_field 1 Package: "$m" "$@"
elif [ "$1" = "FIELD" ]; then
local f="$2"
local m="$3"
local p="$4"
shift; shift; shift; shift
LC_ALL=C grep "$gropt" '^$|^Package:|^Priority:|^Essential:' "$p" | pkgdetails_field 0 "$f" "$m" "$@"
elif [ "$1" = "STANZAS" ]; then
local pkgdest="$2"; shift; shift
perl -e '
my $accum = "";
%seen = map { $_ => 1 } @ARGV;
while (<STDIN>) {
$accum .= $_;
$in = 1 if (/^Package: (.*)$/ && $seen{$1});
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="";
local s c a
archs="$ARCH"
if [ $ARCH_ALL_SUPPORTED -eq 1 ]; then
archs="all $ARCH"
fi
while [ "$PKGS" != "" ]; do
local NEWPKGS=""
for a in $archs; do
for s in $SUITE $EXTRA_SUITES; do
for c in ${COMPONENTS:-$(echo "${USE_COMPONENTS}" | tr '|' ' ')}; do
local path pkgdest
path="dists/$s/$c/binary-$a/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$m1" "$path")"
NEWPKGS="$NEWPKGS $("$PKGDETAILS" GETDEPS "$pkgdest" $PKGS)"
done
done
done
if [ -n "${EXCLUDE_DEPENDENCY:-}" ]; then
NEWPKGS="$(without "$NEWPKGS" "$EXCLUDE_DEPENDENCY")"
fi
PKGS=$(echo "$PKGS $NEWPKGS" | tr ' ' '\n' | sort | uniq)
local REALPKGS=""
for a in $archs; do
for s in $SUITE $EXTRA_SUITES; do
for c in ${COMPONENTS:-$(echo "${USE_COMPONENTS}" | tr '|' ' ')}; do
local path pkgdest
path="dists/$s/$c/binary-$a/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$m1" "$path")"
REALPKGS="$REALPKGS $("$PKGDETAILS" PKGS REAL "$pkgdest" $PKGS | sed -n 's/ .*REAL.*$//p')"
done
done
done
PKGS="$REALPKGS"
ALLPKGS2=$(echo "$PKGS $ALLPKGS" | tr ' ' '\n' | sort | uniq)
PKGS=$(without "$ALLPKGS2" "$ALLPKGS")
ALLPKGS="$ALLPKGS2"
done
echo "$ALLPKGS"
}
setup_available () {
local m1 c path pkgdest pkg
m1="${MIRRORS%% *}"
archs="$ARCH"
if [ $ARCH_ALL_SUPPORTED -eq 1 ]; then
archs="all $ARCH"
fi
for s in $SUITE $EXTRA_SUITES; do
for a in $archs; do
for c in ${COMPONENTS:-$(echo "${USE_COMPONENTS}" | tr '|' ' ')}; do
path="dists/$s/$c/binary-$a/Packages"
pkgdest="$TARGET/$($DLDEST pkg "$s" "$c" "$a" "$m1" "$path")"
# XXX: What if a package is in more than one component?
# -- cjwatson 2009-07-29
# XXX: ...or suite?
# -- jrtc27 2019-06-11
"$PKGDETAILS" STANZAS "$pkgdest" "$@"
done
done
done >"$TARGET/var/lib/dpkg/available"
for pkg; do
echo "$pkg install"
done | in_target dpkg --set-selections
}
get_next_predep () {
local stanza
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*|darwin)
;;
*)
if ! doing_variant fakechroot; then
case "$CONTAINER" in
lxc|lxc-libvirt|mmdebstrap-unshare)
;;
*)
if ! mknod "$1/test-dev-null" c 1 3 2>/dev/null ||
! echo test > "$1/test-dev-null"; then
# mknod failed (e.g. user namespace) or writing failed
# (e.g. nodev). Try if bind-mounting works
touch "$1/test-dev-null"
if ! mount -o bind /dev/null "$1/test-dev-null"; then
rm -f "$1/test-dev-null"
return 1
fi
if ! echo test > "$1/test-dev-null"; then
umount "$1/test-dev-null"
rm -f "$1/test-dev-null"
return 1
fi
umount "$1/test-dev-null"
fi
rm -f "$1/test-dev-null"
;;
esac
fi
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
}
check_sop_status () {
local err="$1"
case "$err" in
0)
info VALIDRELSIG "Valid Release signature"
;;
3)
error 1 UNKNOWNRELSIG "Release signed by unknown key\n The specified keyring $KEYRING may be incorrect or out of date.\n You can find the latest Debian release key at https://ftp-master.debian.org/keys.html"
;;
41)
error 1 BADRELSIG "Invalid Release signature"
;;
*)
error 1 SIGCHECK "Error executing sopv to check Release signature (SOP error $err)"
;;
esac
}
check_sqv_status () {
local err="$1"
case "$err" in
0)
info VALIDRELSIG "Valid Release signature"
;;
1)
error 1 BADRELSIG "Invalid Release signature"
;;
*)
error 1 SIGCHECK "Error executing sqv to check Release signature (sqv error $err)"
;;
esac
}
read_gpg_status () {
local badsig unkkey validsig
while read -r 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)\n The specified keyring $KEYRING may be incorrect or out of date.\n You can find the latest Debian release key at https://ftp-master.debian.org/keys.html" "$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 "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"
}