############################################################### smallutils smallyes() { YES="${1-y}" while echo "$YES" 2>/dev/null ; do : ; done } in_path () { local OLD_IFS="$IFS" IFS=":" for dir in $PATH; do if [ -x "$dir/$1" ]; then IFS="$OLD_IFS" return 0 fi done IFS="$OLD_IFS" return 1 } ############################################################### interaction error () { # local err="$1" local name="$2" local fmt="$3" shift; shift; shift if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then (echo "E: $name" for x in "$@"; do echo "EA: $x"; done echo "EF: $fmt") >&4 else (printf "E: $fmt\n" "$@") >&4 fi exit $err } warning () { # local name="$1" local fmt="$2" shift; shift if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then (echo "W: $name" for x in "$@"; do echo "WA: $x"; done echo "WF: $fmt") >&4 else printf "W: $fmt\n" "$@" >&4 fi } info () { # local name="$1" local fmt="$2" shift; shift if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then (echo "I: $name" for x in "$@"; do echo "IA: $x"; done echo "IF: $fmt") >&4 else printf "I: $fmt\n" "$@" >&4 fi } PROGRESS_NOW=0 PROGRESS_END=0 PROGRESS_NEXT="" PROGRESS_WHAT="" progress_next () { PROGRESS_NEXT="$1" } wgetprogress () { [ ! "$VERBOSE" ] && QSWITCH="-q" local ret=0 if [ "$USE_DEBIANINSTALLER_INTERACTION" ] && [ "$PROGRESS_NEXT" ]; then wget "$@" 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END >&3 ret=$? else wget $QSWITCH "$@" ret=$? fi return $ret } progress () { # local now="$1" local end="$2" local name="$3" local fmt="$4" shift; shift; shift; shift if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then PROGRESS_NOW="$now" PROGRESS_END="$end" PROGRESS_NEXT="" (echo "P: $now $end $name" for x in "$@"; do echo "PA: $x"; done echo "PF: $fmt") >&3 fi } dpkg_progress () { # UNPACKING|CONFIGURING local now="$1" local end="$2" local name="$3" local desc="$4" local action="$5" local expect= if [ "$action" = UNPACKING ]; then expect=half-installed elif [ "$action" = CONFIGURING ]; then expect=half-configured fi dp () { now="$(($now + ${1:-1}))" } exitcode=0 while read status pkg qstate; do if [ "$status" = "EXITCODE" ]; then exitcode="$pkg" continue fi [ "$qstate" = "$expect" ] || continue case $qstate in half-installed) dp; progress "$now" "$end" "$name" "$desc" info "$action" "Unpacking %s..." "${pkg%:}" expect=unpacked ;; unpacked) expect=half-installed ;; half-configured) dp; progress "$now" "$end" "$name" "$desc" info "$action" "Configuring %s..." "${pkg%:}" expect=installed ;; installed) expect=half-configured ;; esac done return $exitcode } ############################################################# set variables default_mirror () { DEF_MIRROR="$1" } FINDDEBS_NEEDS_INDICES=false finddebs_style () { case "$1" in hardcoded) ;; from-indices) FINDDEBS_NEEDS_INDICES=true ;; *) error 1 BADFINDDEBS "unknown finddebs style" ;; esac } mk_download_dirs () { if [ $DLDEST = "apt_dest" ]; then mkdir -p "$TARGET/$APTSTATE/lists/partial" mkdir -p "$TARGET/var/cache/apt/archives/partial" fi } download_style () { case "$1" in apt) if [ "$2" = "var-state" ]; then APTSTATE=var/state/apt else APTSTATE=var/lib/apt fi DLDEST=apt_dest export APTSTATE DLDEST DEBFOR ;; *) error 1 BADDLOAD "unknown download style" ;; esac } keyring () { if [ -z "$KEYRING" ]; then if [ -e "$1" ]; then KEYRING="$1" elif [ -z "$DISABLE_KEYRING" ]; then if [ -n "$DEF_HTTPS_MIRROR" ] && [ -z "$USER_MIRROR" ] && [ -z "$FORCE_KEYRING" ]; then info KEYRING "Keyring file not available at %s; switching to https mirror %s" "$1" "$DEF_HTTPS_MIRROR" USER_MIRROR="$DEF_HTTPS_MIRROR" else warning KEYRING "Cannot check Release signature; keyring file not available %s" "$1" if [ -n "$FORCE_KEYRING" ]; then error 1 KEYRING "Keyring-based check was requested; aborting accordingly" fi fi fi fi } ########################################################## variant handling doing_variant () { if [ "$1" = "$VARIANT" ]; then return 0; fi if [ "$1" = "-" ] && [ "$VARIANT" = "" ]; then return 0; fi return 1 } SUPPORTED_VARIANTS="-" variants () { SUPPORTED_VARIANTS="$*" for v in $*; do if doing_variant "$v"; then return 0; fi done error 1 UNSUPPVARIANT "unsupported variant" } ################################################# work out names for things mirror_style () { case "$1" in release) DOWNLOAD_INDICES=download_release_indices DOWNLOAD_DEBS=download_release ;; main) DOWNLOAD_INDICES=download_main_indices DOWNLOAD_DEBS=download_main ;; *) error 1 BADMIRROR "unknown mirror style" ;; esac export DOWNLOAD_INDICES export DOWNLOAD_DEBS } force_md5 () { DEBOOTSTRAP_CHECKSUM_FIELD=MD5SUM export DEBOOTSTRAP_CHECKSUM_FIELD } verify_checksum () { # args: dest checksum size local expchecksum="$2" local expsize="$3" if [ "$DEBOOTSTRAP_CHECKSUM_FIELD" = "MD5SUM" ]; then if in_path md5sum; then relchecksum=`md5sum < "$1" | sed 's/ .*$//'` elif in_path md5; then relchecksum=`md5 < "$1"` else error 1 SIGCHECK "Cannot check md5sum" fi else if in_path "sha${SHA_SIZE}sum"; then relchecksum=`sha${SHA_SIZE}sum < "$1" | sed 's/ .*$//'` elif in_path "sha${SHA_SIZE}"; then relchecksum=`sha${SHA_SIZE} < "$1"` else error 1 SIGCHECK "Cannot check sha${SHA_SIZE}sum" fi fi relsize=`wc -c < "$1"` if [ "$expsize" -ne "$relsize" ] || [ "$expchecksum" != "$relchecksum" ]; then return 1 fi return 0 } get () { # args: from dest 'nocache' # args: from dest [checksum size] [alt {checksum size type}] local displayname local versionname if [ "${2%.deb}" != "$2" ]; then displayname="$(echo "$2" | sed 's,^.*/,,;s,_.*$,,')" versionname="$(echo "$2" | sed 's,^.*/,,' | cut -d_ -f2 | sed 's/%3a/:/')" else displayname="$(echo "$1" | sed 's,^.*/,,')" fi if [ -e "$2" ]; then if [ -z "$3" ]; then return 0 elif [ "$3" = nocache ]; then rm -f "$2" else info VALIDATING "Validating %s %s" "$displayname" "$versionname" if verify_checksum "$2" "$3" "$4"; then return 0 else rm -f "$2" fi fi fi # Drop 'nocache' option if [ "$3" = nocache ]; then set "$1" "$2" fi if [ "$#" -gt 5 ]; then local st=3 if [ "$5" = "-" ]; then st=6; fi local order="$(a=$st; while [ "$a" -le $# ]; do eval echo \"\${$(($a+1))}\" $a; a=$(($a + 3)); done | sort -n | sed 's/.* //')" else local order=3 fi for a in $order; do local checksum="$(eval echo \${$a})" local siz="$(eval echo \${$(( $a+1 ))})" local typ="$(eval echo \${$(( $a+2 ))})" local from local dest local iters=0 case "$typ" in xz) from="$1.xz"; dest="$2.xz" ;; bz2) from="$1.bz2"; dest="$2.bz2" ;; gz) from="$1.gz"; dest="$2.gz" ;; *) from="$1"; dest="$2" ;; esac if [ "${dest#/}" = "$dest" ]; then dest="./$dest" fi local dest2="$dest" if [ -d "${dest2%/*}/partial" ]; then dest2="${dest2%/*}/partial/${dest2##*/}" fi while [ "$iters" -lt 10 ]; do info RETRIEVING "Retrieving %s %s" "$displayname" "$versionname" if ! just_get "$from" "$dest2"; then continue 2; fi if [ "$checksum" != "" ]; then info VALIDATING "Validating %s %s" "$displayname" "$versionname" if verify_checksum "$dest2" "$checksum" "$siz"; then checksum="" fi fi if [ -z "$checksum" ]; then [ "$dest2" = "$dest" ] || mv "$dest2" "$dest" case "$typ" in gz) gunzip "$dest" ;; bz2) bunzip2 "$dest" ;; xz) unxz "$dest" ;; esac return 0 else rm -f "$dest2" warning RETRYING "Retrying failed download of %s" "$from" iters="$(($iters + 1))" fi done warning CORRUPTFILE "%s was corrupt" "$from" done return 1 } just_get () { # args: from dest local from="$1" local dest="$2" mkdir -p "${dest%/*}" if [ "${from#null:}" != "$from" ]; then error 1 NOTPREDL "%s was not pre-downloaded" "${from#null:}" elif [ "${from#http://}" != "$from" ] || [ "${from#ftp://}" != "$from" ]; then # http/ftp mirror if wgetprogress -O "$dest" "$from"; then return 0 else rm -f "$dest" return 1 fi elif [ "${from#https://}" != "$from" ] ; then # http/ftp mirror if wgetprogress $CHECKCERTIF $CERTIFICATE $PRIVATEKEY -O "$dest" "$from"; then return 0 else rm -f "$dest" return 1 fi elif [ "${from#file:}" != "$from" ]; then local base="${from#file:}" if [ "${base#//}" != "$base" ]; then base="/${from#file://*/}" fi if [ -e "$base" ]; then cp "$base" "$dest" return 0 else return 1 fi elif [ "${from#ssh:}" != "$from" ]; then local ssh_dest="$(echo $from | sed -e 's#ssh://##' -e 's#/#:/#')" if [ -n "$ssh_dest" ]; then scp "$ssh_dest" "$dest" return 0 else return 1 fi else error 1 UNKNOWNLOC "unknown location %s" "$from" fi } download () { mk_download_dirs "$DOWNLOAD_DEBS" $(echo "$@" | tr ' ' '\n' | sort) } download_indices () { mk_download_dirs "$DOWNLOAD_INDICES" $(echo "$@" | tr ' ' '\n' | sort) } debfor () { (while read pkg path; do for p in "$@"; do [ "$p" = "$pkg" ] || continue; echo "$path" done done <"$TARGET/debootstrap/debpaths" ) } apt_dest () { # args: # deb package version arch mirror path # pkg suite component arch mirror path # rel suite mirror path case "$1" in deb) echo "/var/cache/apt/archives/${2}_${3}_${4}.deb" | sed 's/:/%3a/' ;; pkg) local m="$5" m="debootstrap.invalid" #if [ "${m#http://}" != "$m" ]; then # m="${m#http://}" #elif [ "${m#file://}" != "$m" ]; then # m="file_localhost_${m#file://*/}" #elif [ "${m#file:/}" != "$m" ]; then # m="file_localhost_${m#file:/}" #fi printf "%s" "$APTSTATE/lists/" echo "${m}_$6" | sed 's/\//_/g' ;; rel) local m="$3" m="debootstrap.invalid" #if [ "${m#http://}" != "$m" ]; then # m="${m#http://}" #elif [ "${m#file://}" != "$m" ]; then # m="file_localhost_${m#file://*/}" #elif [ "${m#file:/}" != "$m" ]; then # m="file_localhost_${m#file:/}" #fi printf "%s" "$APTSTATE/lists/" echo "${m}_$4" | sed 's/\//_/g' ;; esac } ################################################################## download get_release_checksum () { local reldest="$1" local path="$2" if [ "$DEBOOTSTRAP_CHECKSUM_FIELD" = MD5SUM ]; then local match="^[Mm][Dd]5[Ss][Uu][Mm]" else local match="^[Ss][Hh][Aa]$SHA_SIZE:" fi sed -n "/$match/,/^[^ ]/p" < "$reldest" | \ while read a b c; do if [ "$c" = "$path" ]; then echo "$a $b"; fi done | head -n 1 } extract_release_components () { local reldest="$1"; shift TMPCOMPONENTS="$(sed -n 's/Components: *//p' "$reldest")" for c in $TMPCOMPONENTS ; do eval " case \"\$c\" in $USE_COMPONENTS) COMPONENTS=\"\$COMPONENTS \$c\" ;; esac " done COMPONENTS="$(echo $COMPONENTS)" if [ -z "$COMPONENTS" ]; then mv "$reldest" "$reldest.malformed" error 1 INVALIDREL "Invalid Release file, no valid components" fi } CODENAME="" validate_suite () { local reldest="$1" CODENAME=$(sed -n "s/^Codename: *//p" "$reldest") local suite=$(sed -n "s/^Suite: *//p" "$reldest") if [ "$SUITE" != "$suite" ] && [ "$SUITE" != "$CODENAME" ]; then error 1 WRONGSUITE "Asked to install suite %s, but got %s (codename: %s) from mirror" "$SUITE" "$suite" "$CODENAME" fi } split_inline_sig () { local inreldest="$1" local reldest="$2" local relsigdest="$3" # Note: InRelease files are fun since one needs to remove the # last newline from the PGP SIGNED MESSAGE part, while keeping # the PGP SIGNATURE part intact. This shell implementation # should work on most if not all systems, instead of trying to # sed/tr/head, etc. rm -f "$reldest" "$relsigdest" nl="" state=pre-begin while IFS= read -r line; do case "${state}" in pre-begin) if [ "x${line}" = "x-----BEGIN PGP SIGNED MESSAGE-----" ]; then state=begin fi ;; begin) if [ "x${line}" = "x" ]; then state=data fi ;; data) if [ "x${line}" = "x-----BEGIN PGP SIGNATURE-----" ]; then printf "%s\n" "${line}" > "$relsigdest" state=signature else printf "${nl}%s" "${line}" >> "$reldest" nl="\n" fi ;; signature) printf "%s\n" "${line}" >> "$relsigdest" if [ "x${line}" = "x-----END PGP SIGNATURE-----" ]; then break fi esac done < "$inreldest" } download_release_sig () { local m1="$1" local inreldest="$2" local reldest="$3" local relsigdest="$4" progress 0 100 DOWNREL "Downloading Release file" progress_next 100 if get "$m1/dists/$SUITE/InRelease" "$inreldest" nocache; then split_inline_sig "$inreldest" "$reldest" "$relsigdest" progress 100 100 DOWNREL "Downloading Release file" else get "$m1/dists/$SUITE/Release" "$reldest" nocache || error 1 NOGETREL "Failed getting release file %s" "$m1/dists/$SUITE/Release" progress 100 100 DOWNREL "Downloading Release file" fi if [ -n "$KEYRING" ] && [ -z "$DISABLE_KEYRING" ]; then progress 0 100 DOWNRELSIG "Downloading Release file signature" if ! [ -f "$relsigdest" ]; then progress_next 50 get "$m1/dists/$SUITE/Release.gpg" "$relsigdest" nocache || error 1 NOGETRELSIG "Failed getting release signature file %s" \ "$m1/dists/$SUITE/Release.gpg" progress 50 100 DOWNRELSIG "Downloading Release file signature" fi info RELEASESIG "Checking Release signature" # Don't worry about the exit status from gpgv; parsing the output will # take care of that. (gpgv --status-fd 1 --keyring "$KEYRING" --ignore-time-conflict \ "$relsigdest" "$reldest" || true) | read_gpg_status progress 100 100 DOWNRELSIG "Downloading Release file signature" fi } download_release_indices () { local m1="${MIRRORS%% *}" local inreldest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/InRelease")" local reldest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/Release")" local relsigdest="$TARGET/$($DLDEST rel "$SUITE" "$m1" "dists/$SUITE/Release.gpg")" download_release_sig "$m1" "$inreldest" "$reldest" "$relsigdest" validate_suite "$reldest" extract_release_components $reldest local totalpkgs=0 for c in $COMPONENTS; do local subpath="$c/binary-$ARCH/Packages" local xzi="`get_release_checksum "$reldest" "$subpath.xz"`" local bz2i="`get_release_checksum "$reldest" "$subpath.bz2"`" local gzi="`get_release_checksum "$reldest" "$subpath.gz"`" local normi="`get_release_checksum "$reldest" "$subpath"`" local i= if [ "$normi" != "" ]; then i="$normi" elif in_path bunzip2 && [ "$bz2i" != "" ]; then i="$bz2i" elif in_path unxz && [ "$xzi" != "" ]; then i="$xzi" elif in_path gunzip && [ "$gzi" != "" ]; then i="$gzi" fi if [ "$i" != "" ]; then totalpkgs="$(( $totalpkgs + ${i#* } ))" else mv "$reldest" "$reldest.malformed" error 1 MISSINGRELENTRY "Invalid Release file, no entry for %s" "$subpath" fi done local donepkgs=0 local pkgdest progress 0 $totalpkgs DOWNPKGS "Downloading Packages files" for c in $COMPONENTS; do local subpath="$c/binary-$ARCH/Packages" local path="dists/$SUITE/$subpath" local xzi="`get_release_checksum "$reldest" "$subpath.xz"`" local bz2i="`get_release_checksum "$reldest" "$subpath.bz2"`" local gzi="`get_release_checksum "$reldest" "$subpath.gz"`" local normi="`get_release_checksum "$reldest" "$subpath"`" local ext= local i= if [ "$normi" != "" ]; then ext="$ext $normi ." i="$normi" fi if in_path unxz && [ "$xzi" != "" ]; then ext="$ext $xzi xz" i="${i:-$xzi}" fi if in_path bunzip2 && [ "$bz2i" != "" ]; then ext="$ext $bz2i bz2" i="${i:-$bz2i}" fi if in_path gunzip && [ "$gzi" != "" ]; then ext="$ext $gzi gz" i="${i:-$gzi}" fi progress_next "$(($donepkgs + ${i#* }))" for m in $MIRRORS; do pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")" if get "$m/$path" "$pkgdest" $ext; then break; fi done if [ ! -f "$pkgdest" ]; then error 1 COULDNTDL "Couldn't download %s" "$path" fi donepkgs="$(($donepkgs + ${i#* }))" progress $donepkgs $totalpkgs DOWNPKGS "Downloading Packages files" done } get_package_sizes () { # mirror pkgdest debs.. local m="$1"; shift local pkgdest="$1"; shift $PKGDETAILS PKGS "$m" "$pkgdest" "$@" | ( newleft="" totaldebs=0 countdebs=0 while read p details; do if [ "$details" = "-" ]; then newleft="$newleft $p" else size="${details##* }"; totaldebs="$(($totaldebs + $size))" countdebs="$(($countdebs + 1))" fi done echo "$countdebs $totaldebs$newleft" ) } # note, leftovers come back on fd5 !! download_debs () { local m="$1" local pkgdest="$2" shift; shift $PKGDETAILS PKGS "$m" "$pkgdest" "$@" | ( leftover="" while read p ver arc mdup fil checksum size; do if [ "$ver" = "-" ]; then leftover="$leftover $p" else progress_next "$(($dloaddebs + $size))" local debdest="$($DLDEST deb "$p" "$ver" "$arc" "$m" "$fil")" if get "$m/$fil" "$TARGET/$debdest" "$checksum" "$size"; then dloaddebs="$(($dloaddebs + $size))" echo >>$TARGET/debootstrap/deburis "$p $ver $m/$fil" echo >>$TARGET/debootstrap/debpaths "$p $debdest" else warning COULDNTDL "Couldn't download package %s (ver %s arch %s)" "$p" "$ver" "$arc" leftover="$leftover $p" fi fi done echo >&5 ${leftover# } ) } download_release () { local m1="${MIRRORS%% *}" local numdebs="$#" local countdebs=0 progress $countdebs $numdebs SIZEDEBS "Finding package sizes" local totaldebs=0 local leftoverdebs="$*" # Fix possible duplicate package names, which would screw up counts: leftoverdebs=$(printf "$leftoverdebs"|tr ' ' '\n'|sort -u|tr '\n' ' ') numdebs=$(printf "$leftoverdebs"|wc -w) for c in $COMPONENTS; do if [ "$countdebs" -ge "$numdebs" ]; then break; fi local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")" if [ ! -e "$pkgdest" ]; then continue; fi info CHECKINGSIZES "Checking component %s on %s..." "$c" "$m1" leftoverdebs="$(get_package_sizes "$m1" "$pkgdest" $leftoverdebs)" countdebs=$(($countdebs + ${leftoverdebs%% *})) leftoverdebs=${leftoverdebs#* } totaldebs=${leftoverdebs%% *} leftoverdebs=${leftoverdebs#* } progress $countdebs $numdebs SIZEDEBS "Finding package sizes" done if [ "$countdebs" -ne "$numdebs" ]; then error 1 LEFTOVERDEBS "Couldn't find these debs: %s" "$leftoverdebs" fi local dloaddebs=0 progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages" :>$TARGET/debootstrap/debpaths pkgs_to_get="$*" for c in $COMPONENTS; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" for m in $MIRRORS; do local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")" if [ ! -e "$pkgdest" ]; then continue; fi pkgs_to_get="$(download_debs "$m" "$pkgdest" $pkgs_to_get 5>&1 1>&6)" if [ -z "$pkgs_to_get" ]; then break; fi done 6>&1 if [ -z "$pkgs_to_get" ]; then break; fi done progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages" if [ "$pkgs_to_get" != "" ]; then error 1 COULDNTDLPKGS "Couldn't download packages: %s" "$pkgs_to_get" fi } download_main_indices () { local m1="${MIRRORS%% *}" local comp="${USE_COMPONENTS}" progress 0 100 DOWNMAINPKGS "Downloading Packages file" progress_next 100 if [ -z "$comp" ]; then comp=main; fi COMPONENTS="$(echo $comp | tr '|' ' ')" export COMPONENTS for m in $MIRRORS; do for c in $COMPONENTS; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")" if in_path gunzip && get "$m/${path}.gz" "${pkgdest}.gz"; then rm -f "$pkgdest" gunzip "$pkgdest.gz" elif get "$m/$path" "$pkgdest"; then true fi done done progress 100 100 DOWNMAINPKGS "Downloading Packages file" } download_main () { local m1="${MIRRORS%% *}" :>$TARGET/debootstrap/debpaths for p in "$@"; do for c in $COMPONENTS; do local details="" for m in $MIRRORS; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")" if [ ! -e "$pkgdest" ]; then continue; fi details="$($PKGDETAILS PKGS "$m" "$pkgdest" "$p")" if [ "$details" = "$p -" ]; then details="" continue fi size="${details##* }"; details="${details% *}" checksum="${details##* }"; details="${details% *}" local debdest="$($DLDEST deb $details)" if get "$m/${details##* }" "$TARGET/$debdest" "$checksum" "$size"; then echo >>$TARGET/debootstrap/debpaths "$p $debdest" details="done" break fi done if [ "$details" != "" ]; then break fi done if [ "$details" != "done" ]; then error 1 COULDNTDL "Couldn't download %s" "$p" fi done } ###################################################### deb choosing support get_debs () { local field="$1" shift local m1 c for m1 in $MIRRORS; do for c in $COMPONENTS; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")" echo $("$PKGDETAILS" FIELD "$field" "$m1" "$pkgdest" "$@" | sed 's/ .*//') done done } ################################################################ extraction EXTRACTORS_SUPPORTED="dpkg-deb ar" EXTRACT_DEB_TAR_OPTIONS= # Native dpkg-deb based extractors extract_dpkg_deb_field () { local pkg="$1" local field="$2" dpkg-deb -f "$pkg" "$field" } extract_dpkg_deb_data () { local pkg="$1" dpkg-deb --fsys-tarfile "$pkg" | tar $EXTRACT_DEB_TAR_OPTIONS -xf - } # Raw .deb extractors extract_ar_deb_field () { local pkg="$1" local field="$2" local tarball=$(ar -t "$pkg" | grep "^control\.tar") case "$tarball" in control.tar.gz) cat_cmd=zcat ;; control.tar.xz) cat_cmd=xzcat ;; control.tar) cat_cmd=cat ;; *) error 1 UNKNOWNCONTROLCOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;; esac if 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="$1" local tarball=$(ar -t "$pkg" | grep "^data.tar") case "$tarball" in data.tar.gz) cat_cmd=zcat ;; data.tar.bz2) cat_cmd=bzcat ;; data.tar.xz) cat_cmd=xzcat ;; data.tar) cat_cmd=cat ;; *) error 1 UNKNOWNDATACOMP "Unknown compression type for %s in %s" "$tarball" "$pkg" ;; esac if 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" 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" local p=0 cat_cmd for pkg in $(debfor "$@"); do p="$(($p + 1))" progress "$p" "$#" EXTRACTPKGS "Extracting packages" packagename="$(echo "$pkg" | sed 's,^.*/,,;s,_.*$,,')" info EXTRACTING "Extracting %s..." "$packagename" extract_deb_data "./$pkg" done ); } in_target_nofail () { if ! $CHROOT_CMD "$@" 2>/dev/null; then true fi return 0 } in_target_failmsg () { local code="$1" local msg="$2" local arg="$3" shift; shift; shift if ! $CHROOT_CMD "$@"; then warning "$code" "$msg" "$arg" # Try to point user at actual failing package. msg="See %s for details" if [ -e "$TARGET/debootstrap/debootstrap.log" ]; then arg="$TARGET/debootstrap/debootstrap.log" local pkg="$(grep '^dpkg: error processing ' "$TARGET/debootstrap/debootstrap.log" | head -n 1 | sed 's/\(error processing \)\(package \|archive \)/\1/' | cut -d ' ' -f 4)" if [ -n "$pkg" ]; then msg="$msg (possibly the package $pkg is at fault)" fi else arg="the log" fi warning "$code" "$msg" "$arg" return 1 fi return 0 } in_target () { in_target_failmsg IN_TARGET_FAIL "Failure trying to run: %s" "$CHROOT_CMD $*" "$@" } ###################################################### standard setup stuff conditional_cp () { if [ ! -e "$2/$1" ]; then if [ -L "$1" ] && [ -e "$1" ]; then cat "$1" >"$2/$1" elif [ -e "$1" ]; then cp -a "$1" "$2/$1" fi fi } mv_invalid_to () { local m="$1" m="$(echo "${m#http://}" | tr '/' '_' | sed 's/_*//')" (cd "$TARGET/$APTSTATE/lists" for a in debootstrap.invalid_*; do mv "$a" "${m}_${a#*_}" done ) } setup_apt_sources () { mkdir -p "$TARGET/etc/apt" for m in "$@"; do local cs="" for c in ${COMPONENTS:-$USE_COMPONENTS}; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m" "$path")" if [ -e "$pkgdest" ]; then cs="$cs $c"; fi done if [ "$cs" != "" ]; then echo "deb $m $SUITE$cs"; fi done > "$TARGET/etc/apt/sources.list" } setup_etc () { mkdir -p "$TARGET/etc" conditional_cp /etc/resolv.conf "$TARGET" conditional_cp /etc/hostname "$TARGET" if [ "$DLDEST" = apt_dest ] && [ ! -e "$TARGET/etc/apt/sources.list" ]; then setup_apt_sources "http://debootstrap.invalid/" fi } UMOUNT_DIRS= umount_exit_function () { local realdir for dir in $UMOUNT_DIRS; do realdir="$(in_target_nofail readlink -f "$dir")" [ "$realdir" ] || continue ( cd / ; umount "$TARGET/${realdir#/}" ) || true done } umount_on_exit () { if [ "$UMOUNT_DIRS" ]; then UMOUNT_DIRS="$UMOUNT_DIRS $1" else UMOUNT_DIRS="$1" on_exit umount_exit_function fi } clear_mtab () { if [ -f "$TARGET/etc/mtab" ] && [ ! -h "$TARGET/etc/mtab" ]; then rm -f "$TARGET/etc/mtab" fi } setup_proc () { case "$HOST_OS" in *freebsd*) umount_on_exit /dev umount_on_exit /proc umount "$TARGET/proc" 2>/dev/null || true if [ "$HOST_OS" = kfreebsd ]; then in_target mount -t linprocfs proc /proc else mount -t linprocfs proc $TARGET/proc fi ;; hurd*) # firmlink $TARGET/{dev,servers,proc} to the system ones. settrans -a "$TARGET/dev" /hurd/firmlink /dev settrans -a "$TARGET/servers" /hurd/firmlink /servers settrans -a "$TARGET/proc" /hurd/firmlink /proc ;; *) umount_on_exit /dev/pts umount_on_exit /dev/shm umount_on_exit /proc/bus/usb umount_on_exit /proc umount "$TARGET/proc" 2>/dev/null || true in_target mount -t proc proc /proc if [ -d "$TARGET/sys" ] && \ grep -q '[[:space:]]sysfs' /proc/filesystems 2>/dev/null; then umount_on_exit /sys umount "$TARGET/sys" 2>/dev/null || true in_target mount -t sysfs sysfs /sys fi on_exit clear_mtab ;; esac umount_on_exit /lib/init/rw } setup_proc_fakechroot () { rm -rf "$TARGET/proc" ln -s /proc "$TARGET" } # create the static device nodes setup_devices () { if doing_variant fakechroot; then setup_devices_fakechroot return 0 fi case "$HOST_OS" in kfreebsd*) ;; freebsd) ;; hurd*) ;; *) setup_devices_simple ;; esac } # enable the dynamic device nodes setup_dynamic_devices () { if doing_variant fakechroot; then return 0 fi case "$HOST_OS" in kfreebsd*) in_target mount -t devfs devfs /dev ;; freebsd) mount -t devfs devfs $TARGET/dev ;; hurd*) # Use the setup-translators of the hurd package in_target /usr/lib/hurd/setup-translators -k ;; esac } setup_devices_simple () { # The list of devices that can be created in a container comes from # src/core/cgroup.c in the systemd source tree. mknod -m 666 $TARGET/dev/null c 1 3 mknod -m 666 $TARGET/dev/zero c 1 5 mknod -m 666 $TARGET/dev/full c 1 7 mknod -m 666 $TARGET/dev/random c 1 8 mknod -m 666 $TARGET/dev/urandom c 1 9 mknod -m 666 $TARGET/dev/tty c 5 0 mkdir $TARGET/dev/pts/ $TARGET/dev/shm/ # 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; then 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 fi ln -s /proc/self/fd $TARGET/dev/fd ln -s /proc/self/fd/0 $TARGET/dev/stdin ln -s /proc/self/fd/1 $TARGET/dev/stdout ln -s /proc/self/fd/2 $TARGET/dev/stderr } setup_devices_fakechroot () { rm -rf "$TARGET/dev" ln -s /dev "$TARGET" } setup_dselect_method () { case "$1" in apt) mkdir -p "$TARGET/var/lib/dpkg" echo "apt apt" > "$TARGET/var/lib/dpkg/cmethopt" chmod 644 "$TARGET/var/lib/dpkg/cmethopt" ;; *) error 1 UNKNOWNDSELECT "unknown dselect method" ;; esac } # Find out where the runtime dynamic linker and the shared libraries # can be installed on each architecture: native, multilib and multiarch. # This data can be verified by checking the files in the debian/sysdeps/ # directory of the glibc package. # # This function must be updated to support any new architecture which # either installs the RTLD in a directory different from /lib or builds # multilib library packages. setup_merged_usr() { if [ "$MERGED_USR" = "no" ]; then return 0; fi local link_dir case $ARCH in hurd-*) return 0 ;; amd64) link_dir="lib32 lib64 libx32" ;; i386) link_dir="lib64 libx32" ;; mips|mipsel) link_dir="lib32 lib64" ;; mips64*|mipsn32*) link_dir="lib32 lib64 libo32" ;; powerpc) link_dir="lib64" ;; ppc64) link_dir="lib32 lib64" ;; ppc64el) link_dir="lib64" ;; s390x) link_dir="lib32" ;; sparc) link_dir="lib64" ;; sparc64) link_dir="lib32 lib64" ;; x32) link_dir="lib32 lib64 libx32" ;; esac link_dir="bin sbin lib $link_dir" local dir for dir in $link_dir; do ln -s usr/$dir $TARGET/$dir mkdir -p $TARGET/usr/$dir done } ################################################################ pkgdetails # NOTE # For the debootstrap udeb, pkgdetails is provided by the bootstrap-base # udeb, so the pkgdetails API needs to be kept in sync with that. if in_path perl; then PKGDETAILS=pkgdetails_perl pkgdetails_field () { # uniq field mirror Packages values... perl -le ' $unique = shift @ARGV; $field = lc(shift @ARGV); $mirror = shift @ARGV; %fields = map { $_, 0 } @ARGV; $prevpkg = ""; while () { chomp; next if (/^ /); if (/^([^:]*:)\s*(.*)$/) { $f = lc($1); $v = $2; if ($f eq "package:") { $last = 0; $pkg = $v; if ($pkg ne $prevpkg) { print $output if defined $output; if ($unique && defined $output_val) { delete $fields{$output_val}; $last = 1 unless keys %fields; } $prevpkg = $pkg; } undef $output; undef $output_val; last if $last; } $ver = $v if ($f eq "version:"); $arc = $v if ($f eq "architecture:"); $fil = $v if ($f eq "filename:"); $chk = $v if (lc $f eq lc($ENV{DEBOOTSTRAP_CHECKSUM_FIELD}).":"); $siz = $v if ($f eq "size:"); $val = $v if ($f eq $field); } elsif (/^$/) { if (defined $val && defined $fields{$val}) { $output = sprintf "%s %s %s %s %s %s %s", $pkg, $ver, $arc, $mirror, $fil, $chk, $siz; $output_val = $val; } undef $val; } } print $output if defined $output; delete $fields{$output_val} if $unique && defined $output_val; for $v (keys %fields) { printf ("%s -\n", $v) if ($unique); } ' "$@" } pkgdetails_perl () { if [ "$1" = "WGET%" ]; then shift; perl -e ' $v = 0; $allow_percentage = 0; while (read STDIN, $x, 1) { if ($x =~ m/\s/) { $allow_percentage = 1; } elsif ($allow_percentage and $x =~ m/\d/) { $v *= 10; $v += $x; } elsif ($allow_percentage and $x eq "%") { printf "P: %d %d%s\n", int($v / 100.0 * ($ARGV[1] - $ARGV[0]) + $ARGV[0]), $ARGV[2], ($#ARGV == 3 ? " $ARGV[3]" : ""); $v = 0; } else { $v = 0; $allow_percentage = 0; } }' "$@" elif [ "$1" = "GETDEPS" ]; then local pkgdest="$2"; shift; shift perl -e ' $prevpkg = ""; @d = (); while () { 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" "$@" | sort | uniq elif [ "$1" = "PKGS" ]; then local m="$2" local p="$3" shift; shift; shift pkgdetails_field 1 Package: "$m" "$@" < "$p" elif [ "$1" = "FIELD" ]; then local f="$2" local m="$3" local p="$4" shift; shift; shift; shift pkgdetails_field 0 "$f" "$m" "$@" < "$p" elif [ "$1" = "STANZAS" ]; then local pkgdest="$2"; shift; shift perl -e ' my $accum = ""; while () { $accum .= $_; $in = 1 if (/^Package: (.*)$/ && grep {$_ eq $1} @ARGV); if ($in and /^$/) { print $accum; if (substr($accum, -1) != "\n") { print "\n\n"; } elsif (substr($accum, -2, 1) != "\n") { print "\n"; } $in = 0; } $accum = "" if /^$/; }' <"$pkgdest" "$@" fi } elif [ -e "/usr/lib/debootstrap/pkgdetails" ]; then PKGDETAILS="/usr/lib/debootstrap/pkgdetails" elif [ -e "$DEBOOTSTRAP_DIR/pkgdetails" ]; then PKGDETAILS="$DEBOOTSTRAP_DIR/pkgdetails" else PKGDETAILS="" fi ##################################################### dependency resolution resolve_deps () { local m1="${MIRRORS%% *}" local PKGS="$*" local ALLPKGS="$PKGS"; local ALLPKGS2=""; while [ "$PKGS" != "" ]; do local NEWPKGS="" for c in ${COMPONENTS:-$USE_COMPONENTS}; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")" NEWPKGS="$NEWPKGS $("$PKGDETAILS" GETDEPS "$pkgdest" $PKGS)" done PKGS=$(echo "$PKGS $NEWPKGS" | tr ' ' '\n' | sort | uniq) local REALPKGS="" for c in ${COMPONENTS:-$USE_COMPONENTS}; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")" REALPKGS="$REALPKGS $("$PKGDETAILS" PKGS REAL "$pkgdest" $PKGS | sed -n 's/ .*REAL.*$//p')" done PKGS="$REALPKGS" ALLPKGS2=$(echo "$PKGS $ALLPKGS" | tr ' ' '\n' | sort | uniq) PKGS=$(without "$ALLPKGS2" "$ALLPKGS") ALLPKGS="$ALLPKGS2" done echo $ALLPKGS } setup_available () { local m1="${MIRRORS%% *}" for c in ${COMPONENTS:-$USE_COMPONENTS}; do local path="dists/$SUITE/$c/binary-$ARCH/Packages" local pkgdest="$TARGET/$($DLDEST pkg "$SUITE" "$c" "$ARCH" "$m1" "$path")" # XXX: What if a package is in more than one component? # -- cjwatson 2009-07-29 "$PKGDETAILS" STANZAS "$pkgdest" "$@" done >"$TARGET/var/lib/dpkg/available" for pkg; do echo "$pkg install" done | in_target dpkg --set-selections } get_next_predep () { local stanza="$(in_target_nofail dpkg --predep-package)" [ "$stanza" ] || return 1 echo "$stanza" | grep '^Package:' | sed 's/^Package://; s/^ *//' } ################################################################### helpers # Return zero if it is possible to create devices and execute programs in # this directory. (Both may be forbidden by mount options, e.g. nodev and # noexec respectively.) check_sane_mount () { mkdir -p "$1" case "$HOST_OS" in *freebsd*|hurd*) ;; *) mknod "$1/test-dev-null" c 1 3 || return 1 if ! echo test > "$1/test-dev-null"; then rm -f "$1/test-dev-null" return 1 fi rm -f "$1/test-dev-null" ;; esac SH=/bin/sh [ -x $SH ] || SH=`which sh` cat > "$1/test-exec" < "b" "c" (echo $1 | tr ' ' '\n' | sort | uniq; echo $2 $2 | tr ' ' '\n') | sort | uniq -u | tr '\n' ' ' echo } # Formerly called 'repeat', but that's a reserved word in zsh. repeatn () { local n="$1" shift while [ "$n" -gt 0 ]; do if "$@"; then break else n="$(( $n - 1 ))" sleep 1 fi done if [ "$n" -eq 0 ]; then return 1; fi return 0 } N_EXIT_THINGS=0 exit_function () { local n=0 while [ "$n" -lt "$N_EXIT_THINGS" ]; do (eval $(eval echo \${EXIT_THING_$n}) 2>/dev/null || true) n="$(( $n + 1 ))" done N_EXIT_THINGS=0 } trap "exit_function" 0 trap "exit 129" 1 trap "error 130 INTERRUPTED \"Interrupt caught ... exiting\"" 2 trap "exit 131" 3 trap "exit 143" 15 on_exit () { eval `echo EXIT_THING_${N_EXIT_THINGS}=\"$1\"` N_EXIT_THINGS="$(( $N_EXIT_THINGS + 1 ))" } ############################################################## fakechroot tools install_fakechroot_tools () { if [ "$VARIANT" = "fakechroot" ]; then export PATH=/usr/sbin:/sbin:$PATH fi mv "$TARGET/sbin/ldconfig" "$TARGET/sbin/ldconfig.REAL" echo \ "#!/bin/sh echo echo \"Warning: Fake ldconfig called, doing nothing\"" > "$TARGET/sbin/ldconfig" chmod 755 "$TARGET/sbin/ldconfig" echo \ "/sbin/ldconfig /sbin/ldconfig.REAL fakechroot" >> "$TARGET/var/lib/dpkg/diversions" mv "$TARGET/usr/bin/ldd" "$TARGET/usr/bin/ldd.REAL" cat << 'END' > "$TARGET/usr/bin/ldd" #!/usr/bin/perl # fakeldd # # Replacement for ldd with usage of objdump # # (c) 2003-2005 Piotr Roszatycki , 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 = ) { 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 = ) { $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 = ) { 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" }