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

smallyes() {
  YES="${1-y}"
  while echo "$YES" ; do : ; done
}

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

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

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

info () {
  # <name> <string> <args>
  local name="$1"
  local fmt="$2"
  shift; shift
  if [ "$USE_DEBIANINSTALLER_INTERACTION" ]; then
    (echo "I: $name"
    for x in "$@"; do echo "IA: $x"; done
    echo "IF: $fmt") >&3
  elif [ "$USE_BOOTFLOPPIES_INTERACTION" ]; then
    printf "I: $fmt\n" "$@" >&3
  elif [ "$USE_GETTEXT_INTERACTION" ]; then
    printf "I: `LANG=$GETTEXT_LANG gettext debootstrap "$fmt"`\n" "$@"
  else
    printf "I: $fmt\n" "$@"
  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" -a "$PROGRESS_NEXT" ]; then
    wget "$@" 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END >&3
    ret=$?
  elif [ "$USE_BOOTFLOPPIES_INTERACTION" -a "$PROGRESS_NEXT" ]; then
    wget "$@" 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END "$PROGRESS_WHAT" >&3
    ret=$?
  else
    wget $QSWITCH "$@"
    ret=$?
  fi
  return $ret
}

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

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

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

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

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

check_md5 () {
  # args: dest md5 size
  local expmd5="$2"
  local expsize="$3"
  relmd5=`md5sum < "$1" | sed 's/ .*$//'`
  relsize=`wc -c < "$1"`
  if [ "$expsize" -ne "$relsize" -o "$expmd5" != "$relmd5" ]; then
    return 1
  fi
  return 0
}

get () {
  # args: from dest [md5sum size] [alt {md5sum size type}]
  filename="$(echo "$2" | sed 's,^.*/,,')"
  if echo "$filename" | grep -q '\.deb$'; then
    displayname="$(echo "$filename" | sed 's,_.*$,,')"
  else
    displayname="$filename"
  fi
  
  if [ -e "$2" ]; then
    info VALIDATING "Validating %s" "$displayname"
    if [ "$3" = "" ] || check_md5 $2 $3 $4; then
      return 0
    else
      rm -f "$2"
    fi
  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 md5="$(eval echo \${$a})"
    local siz="$(eval echo \${$(( $a+1 ))})"
    local typ="$(eval echo \${$(( $a+2 ))})"
    local from
    local dest

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

    info RETRIEVING "Retrieving %s" "$displayname"
    if ! just_get "$from" "$dest"; then continue; fi
    info VALIDATING "Validating %s" "$displayname"
    if [ "$md5" = "" ] || check_md5 $dest $md5 $siz; then
      case "$typ" in
        "gz") gunzip "$dest" ;;
        "bz2") bunzip2 "$dest" ;;
      esac
      return 0
    else
      warning CORRUPTFILE "%s was corrupt" "$from"
    fi
  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" -o "${from#ftp://}" != "$from" ]; then
    # http/ftp mirror
    if wgetprogress -O "$dest" "$from"; then
      return 0
    elif [ -s "$dest" ]; then
      local iters=0
      while [ "$iters" -lt 3 ]; do
        warning RETRYING "Retrying failed download of %s" "$from"
        if wgetprogress -c -O "$dest" "$from"; then break; fi
        iters="$(($iters + 1))"
      done
    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
  else
    error 1 UNKNOWNLOC "unknown location %s" "$from"
  fi
}

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

debfor () {
    "$DEBFOR" "$@"
}

apt_debfor () {
  for p in "$@"; do (
    cd "$TARGET/var/cache/apt/archives"
    local chk=0
    for x in ${p}_*_*.deb; do
      if [ -e "$x" ]; then
        echo "/var/cache/apt/archives/$x"
        chk=1
      fi
    done
    if [ "$chk" = 0 ]; then return 1; fi
  ); done
}

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_md5 () {
  local reldest="$1"
  local path="$2"
  sed -n '/^[Mm][Dd]5[Ss][Uu][Mm]/,/^[^ ]/p' < $reldest | while read a b c; do
    if [ "$c" = "$path" ]; then echo "$a $b"; fi
  done | head -n 1
}

download_release_indices () {
  local m1=${MIRRORS%% *}
  local reldest="$TARGET/$($DLDEST rel $SUITE $m1 dists/$SUITE/Release)"
  progress 0 100 DOWNREL "Downloading Release file"
  progress_next 100
  get "$m1/dists/$SUITE/Release" $reldest || 
    error 1 NOGETREL "Failed getting release file %s" "$m1/dists/$SUITE/Release"

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

  local totalpkgs=0
  for c in $COMPONENTS; do
      local subpath="$c/binary-$ARCH/Packages"
      local normmd="`get_release_md5 $reldest ${subpath}`"
      if [ "$normmd" = "" ]; then
        mv $reldest "$reldest.malformed"
        error 1 MISSINGRELENTRY "Invalid Release file, no entry for %s" "$subpath"
      fi
      totalpkgs="$(( $totalpkgs + ${normmd#* } ))"
  done

  local donepkgs=0
  progress 0 $totalpkgs DOWNPKGS "Downloading Packages files"
  for c in $COMPONENTS; do
    local subpath="$c/binary-$ARCH/Packages"
    local path="dists/$SUITE/$subpath"
    local bz2md="`get_release_md5 $reldest ${subpath}.bz2`"
    local gzmd="`get_release_md5 $reldest ${subpath}.gz`"
    local normmd="`get_release_md5 $reldest ${subpath}`"
    local ext="$normmd ."
    if [ -x /usr/bin/bunzip2 -a "$bz2md" != "" ]; then
      ext="$ext $bz2md bz2"
    fi
    if [ -x /bin/gunzip -a "$gzmd" != "" ]; then
      ext="$ext $gzmd gz"
    fi
    progress_next "$(($donepkgs + ${normmd#* }))"
    for m in $MIRRORS; do
      local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
      if get "$m/$path" "$pkgdest" $ext; then break; fi
    done
    donepkgs="$(($donepkgs + ${normmd#* }))"
    progress $donepkgs $totalpkgs DOWNPKGS "Downloading Packages files"
  done
}

download_release () {
  download_release_indices

  local m1=${MIRRORS%% *}

  local numdebs=0
  for p in "$@"; do
    numdebs="$(($numdebs + 1))"
  done

  local countdebs=0
  progress $countdebs $numdebs SIZEDEBS "Finding package sizes"
  
  local totaldebs=0
  for p in "$@"; do
    for c in $COMPONENTS; do
      local path="dists/$SUITE/$c/binary-$ARCH/Packages"
      local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m1 $path)"
      if [ ! -e "$pkgdest" ]; then continue; fi
      details="$($PKGDETAILS $p $m1 $pkgdest)"
      if [ -z "$details" ]; then continue; fi
      size="${details##* }"; details="${details% *}"
      totaldebs="$(($totaldebs + $size))"
    done
    countdebs="$(($countdebs + 1))"
    progress $countdebs $numdebs SIZEDEBS "Finding package sizes"
    
    info CHECKINGSIZE "Checking %s..." "$p"
  done 

  local dloaddebs=0

  progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages"
  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 $p $m $pkgdest)"
        if [ -z "$details" ]; then continue; fi
        size="${details##* }"; details="${details% *}"
        md5="${details##* }"; details="${details% *}"
	progress_next "$(($dloaddebs + $size))"
        local debdest="$TARGET/$($DLDEST deb $details)"
        if get "$m/${details##* }" $debdest $md5 $size; then
          dloaddebs="$(($dloaddebs + $size))"
          details="done"
          break
        fi
      done
      if [ "$details" != "" ]; then
        break
      fi
    done
    progress $dloaddebs $totaldebs DOWNDEBS "Downloading packages"
    if [ "$details" != "done" ]; then
      error 1 COULDNTDL "Couldn't download %s" "$p"
    fi
  done
}

download_main () {
  local m1=${MIRRORS%% *}
  progress 0 100 DOWNMAINPKGS "Downloading Packages file"
  progress_next 100
  COMPONENTS=main
  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 [ -x /bin/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"

  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 $p $m $pkgdest)"
        if [ -z "$details" ]; then continue; fi
        size="${details##* }"; details="${details% *}"
        md5="${details##* }"; details="${details% *}"
        local debdest="$TARGET/$($DLDEST deb $details)"
        if get "$m/${details##* }" $debdest $md5 $size; then
          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
}

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

extract () { (
  cd "$TARGET"
  local 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"
    ar -p ./$pkg data.tar.gz | zcat | tar -xf -
  done
); }

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

in_target_failmsg () {
  local code="$1"
  local msg="$2"
  local arg="$3"
  shift; shift; shift
  if ! eval chroot "$TARGET" "$@"; then
    warning "$code" "$msg" "$arg"
    return 1
  fi
  return 0
}

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

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

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

setup_etc () {
  mkdir -p "$TARGET/etc"
  
  conditional_cp /etc/resolv.conf "$TARGET"
  conditional_cp /etc/hostname "$TARGET"
  
  if [ "$DLDEST" = apt_dest -a ! -e "$TARGET/etc/apt/sources.list" ]; then
    mkdir -p "$TARGET/etc/apt"
    for m in $MIRRORS; do
      local cs=""
      for c in $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
      m="http://debootstrap.invalid/"
      if [ "$cs" != "" ]; then echo "deb $m $SUITE$cs"; fi
    done > "$TARGET/etc/apt/sources.list"
  fi
}

setup_proc () {
  on_exit "umount $TARGET/dev/pts || true"
  on_exit "umount $TARGET/dev/shm || true"
  on_exit "umount $TARGET/proc/bus/usb || true"
  on_exit "umount $TARGET/proc"
  umount $TARGET/proc 2>/dev/null || true
  in_target mount -t proc proc /proc
}

setup_devices () {
  if [ -e $DEVICES_TARGZ ]; then
    (cd "$TARGET"; zcat $DEVICES_TARGZ | tar -xf -)
  else
    if [ -e /dev/.devfsd ]; then
      in_target mount -t devfs devfs /dev
    else
      error 1 NODEVTGZ "no %s. cannot create devices" "$DEVICES_TARGZ"
    fi
  fi
}

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
}

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

repeat () {
  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})
    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 ))"
}
