#!/bin/sh
set -e

setvar VERSION = ''@VERSION@''

unset TMP TEMP TMPDIR || true

# might not be exported if we're running from init=/bin/sh or similar
export PATH

###########################################################################

if test -z $DEBOOTSTRAP_DIR {
	if test -x /debootstrap/debootstrap {
		setvar DEBOOTSTRAP_DIR = "/debootstrap"
	} else {
		setvar DEBOOTSTRAP_DIR = "/usr/share/debootstrap"
	}
}

source $DEBOOTSTRAP_DIR/functions
exec 4>&1

setvar LANG = 'C'
setvar USE_COMPONENTS = 'main'
setvar KEYRING = """"
setvar DISABLE_KEYRING = """"
setvar FORCE_KEYRING = """"
setvar VARIANT = """"
setvar MERGED_USR = ""no""
setvar ARCH = """"
setvar HOST_ARCH = """"
setvar HOST_OS = """"
setvar KEEP_DEBOOTSTRAP_DIR = """"
setvar USE_DEBIANINSTALLER_INTERACTION = """"
setvar SECOND_STAGE_ONLY = """"
setvar PRINT_DEBS = """"
setvar CHROOTDIR = """"
setvar MAKE_TARBALL = """"
setvar EXTRACTOR_OVERRIDE = """"
setvar UNPACK_TARBALL = """"
setvar ADDITIONAL = """"
setvar EXCLUDE = """"
setvar VERBOSE = """"
setvar CERTIFICATE = """"
setvar CHECKCERTIF = """"
setvar PRIVATEKEY = """"

setvar DEF_MIRROR = ""http://deb.debian.org/debian""
setvar DEF_HTTPS_MIRROR = ""https://deb.debian.org/debian""

export LANG USE_COMPONENTS
umask 022

###########################################################################

## phases:
##   finddebs dldebs printdebs first_stage second_stage

setvar RESOLVE_DEPS = 'true'

setvar WHAT_TO_DO = ""finddebs dldebs first_stage second_stage""
proc am_doing_phase {
	# usage:   if am_doing_phase finddebs; then ...; fi
	local x;
	for x in "$@" {
		if echo " $WHAT_TO_DO " | grep -q " $x " { return 0; }
	}
	return 1
}

###########################################################################

proc usage_err {
	info USAGE1 "usage: [OPTION]... <suite> <target> [<mirror> [<script>]]"
	info USAGE2 "Try \`${0##*/} --help' for more information."
	error @ARGV
}

proc usage {
	echo "Usage: ${0##*/} [OPTION]... <suite> <target> [<mirror> [<script>]]"
	echo "Bootstrap a Debian base system into a target directory."
	echo
	cat <<< """
      --help                 display this help and exit
      --version              display version information and exit
      --verbose              don't turn off the output of wget

      --download-only        download packages, but don't perform installation
      --print-debs           print the packages to be installed, and exit

      --arch=A               set the architecture to install (use if no dpkg)
                               [ --arch=powerpc ]

      --include=A,B,C        adds specified names to the list of base packages
      --exclude=A,B,C        removes specified packages from the list
      --components=A,B,C     use packages from the listed components of the
                             archive
      --variant=X            use variant X of the bootstrap scripts
                             (currently supported variants: buildd, fakechroot,
                              minbase)
      --merged-usr           make /{bin,sbin,lib}/ symlinks to /usr/
      --keyring=K            check Release files against keyring K
      --no-check-gpg         avoid checking Release file signatures
      --force-check-gpg      force checking Release file signatures
                             (also disables automatic fallback to HTTPS in case
                             of a missing keyring), aborting otherwise
      --no-resolve-deps      don't try to resolve dependencies automatically

      --unpack-tarball=T     acquire .debs from a tarball instead of http
      --make-tarball=T       download .debs and create a tarball (tgz format)
      --second-stage-target=DIR
                             Run second stage in a subdirectory instead of root
                               (can be used to create a foreign chroot)
                               (requires --second-stage)
      --extractor=TYPE       override automatic .deb extractor selection
                               (supported: $EXTRACTORS_SUPPORTED)
      --debian-installer     used for internal purposes by debian-installer
      --private-key=file     read the private key from file
      --certificate=file     use the client certificate stored in file (PEM)
      --no-check-certificate do not check certificate against certificate authorities
"""
}

###########################################################################

if test -z $PKGDETAILS {
	error 1 NO_PKGDETAILS "No pkgdetails available; either install perl, or build pkgdetails.c from the base-installer source package"
}

###########################################################################

if test $Argc != 0  {
    while true {
	case (1) {
	    --help {
		usage
		exit 0
		}
	    --version {
		echo "debootstrap $VERSION"
		exit 0
		}
	    --debian-installer {
		if ! shell {echo -n "" >&3} 2>/dev/null {
			error 1 ARG_DIBYHAND "If running debootstrap by hand, don't use --debian-installer"
		}
		setvar USE_DEBIANINSTALLER_INTERACTION = 'yes'
		shift
		}
	    --foreign {
		if test $PRINT_DEBS != "true" {
			setvar WHAT_TO_DO = ""finddebs dldebs first_stage""
		}
		shift
		}
	    --second-stage {
		setvar WHAT_TO_DO = ""second_stage""
		setvar SECOND_STAGE_ONLY = 'true'
		shift
		}
	    --second-stage-target|--second-stage-target=?* {
		if test $SECOND_STAGE_ONLY != "true"  {
			error 1 STAGE2ONLY "option %s only applies in the second stage" $1
		}
		if test $1 = "--second-stage-target" -a -n $2  {
			setvar CHROOTDIR = "$2"
			shift 2
		} elif test $1 != ${1#--second-stage-target=} {
			setvar CHROOTDIR = "${1#--second-stage-target=}"
			shift
		} else {
			error 1 NEEDARG "option requires an argument: %s" $1
		}
		}
	    --print-debs {
		setvar WHAT_TO_DO = ""finddebs printdebs kill_target""
		setvar PRINT_DEBS = 'true'
		shift
		}
	    --download-only {
		setvar WHAT_TO_DO = ""finddebs dldebs""
		shift
		}
	    --make-tarball|--make-tarball=?* {
		setvar WHAT_TO_DO = ""finddebs dldebs maketarball kill_target""
		if test $1 = "--make-tarball" -a -n $2  {
			setvar MAKE_TARBALL = "$2"
			shift 2
		} elif test $1 != ${1#--make-tarball=} {
			setvar MAKE_TARBALL = "${1#--make-tarball=}"
			shift
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		}
	    --resolve-deps {
		# redundant, but avoids breaking compatibility
		setvar RESOLVE_DEPS = 'true'
		shift
		}
	    --no-resolve-deps {
		setvar RESOLVE_DEPS = 'false'
		shift
		}
	    --keep-debootstrap-dir {
		setvar KEEP_DEBOOTSTRAP_DIR = 'true'
		shift
		}
	    --arch|--arch=?* {
		if test $1 = "--arch" -a -n $2  {
			setvar ARCH = "$2"
			shift 2
                } elif test $1 != ${1#--arch=} {
			setvar ARCH = "${1#--arch=}"
			shift
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		}
	    --extractor|--extractor=?* {
		if test $1 = "--extractor" -a -n $2  {
			setvar EXTRACTOR_OVERRIDE = "$2"
			shift 2
		} elif test $1 != ${1#--extractor=} {
			setvar EXTRACTOR_OVERRIDE = "${1#--extractor=}"
			shift
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		if valid_extractor $EXTRACTOR_OVERRIDE {
			if ! type $EXTRACTOR_OVERRIDE >/dev/null 2>&1 {
				error 1 MISSINGEXTRACTOR "The selected extractor cannot be found: %s" $EXTRACTOR_OVERRIDE
			}
		} else {
			error 1 BADEXTRACTOR "%s: unknown extractor" $EXTRACTOR_OVERRIDE
		}
		}
	    --unpack-tarball|--unpack-tarball=?* {
		if test $1 = "--unpack-tarball" -a -n $2  {
			setvar UNPACK_TARBALL = "$2"
			shift 2
		} elif test $1 != ${1#--unpack-tarball=} {
			setvar UNPACK_TARBALL = "${1#--unpack-tarball=}"
			shift
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		if test ! -f $UNPACK_TARBALL  {
			error 1 NOTARBALL "%s: No such file or directory" $UNPACK_TARBALL
		}
		}
	    --include|--include=?* {
		if test $1 = "--include" -a -n $2 {
			setvar ADDITIONAL = "$2"
			shift 2
                } elif test $1 != ${1#--include=} {
			setvar ADDITIONAL = "${1#--include=}"
			shift 1
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		setvar ADDITIONAL = "$(echo "$ADDITIONAL" | tr , " ")"
		}
	    --exclude|--exclude=?* {
		if test $1 = "--exclude" -a -n $2 {
			setvar EXCLUDE = "$2"
			shift 2
                } elif test $1 != ${1#--exclude=} {
			setvar EXCLUDE = "${1#--exclude=}"
			shift 1
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		setvar EXCLUDE = "$(echo "$EXCLUDE" | tr , " ")"
		}
	    --verbose {
		setvar VERBOSE = 'true'
		export VERBOSE
		shift 1
		}
	    --components|--components=?* {
		if test $1 = "--components" -a -n $2 {
			setvar USE_COMPONENTS = "$2"
			shift 2
                } elif test $1 != ${1#--components=} {
			setvar USE_COMPONENTS = "${1#--components=}"
			shift 1
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		setvar USE_COMPONENTS = "$(echo "$USE_COMPONENTS" | tr , "|")"
		}
	    --variant|--variant=?* {
		if test $1 = "--variant" -a -n $2 {
			setvar VARIANT = "$2"
			shift 2
                } elif test $1 != ${1#--variant=} {
			setvar VARIANT = "${1#--variant=}"
			shift 1
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		}
            --merged-usr {
		setvar MERGED_USR = 'yes'
		shift
		}
	    --no-merged-usr {
		setvar MERGED_USR = 'no'
		shift
		}
	    --keyring|--keyring=?* {
		if ! gpgv --version >/dev/null 2>&1 {
			error 1 NEEDGPGV "gpgv not installed, but required for Release verification"
		}
		if test $1 = "--keyring" -a -n $2 {
			setvar KEYRING = "$2"
			shift 2
                } elif test $1 != ${1#--keyring=} {
			setvar KEYRING = "${1#--keyring=}"
			shift 1
		} else {
			error 1 NEEDARG "option requires an argument %s" $1
		}
		}
	    --no-check-gpg {
			shift 1
			setvar DISABLE_KEYRING = '1'
		}
	    --force-check-gpg {
			shift 1
			setvar FORCE_KEYRING = '1'
		}
	    --certificate|--certificate=?* {
		if test $1 = "--certificate" -a -n $2 {
			setvar CERTIFICATE = ""--certificate=$2""
			shift 2
		} elif test $1 != ${1#--certificate=} {
			setvar CERTIFICATE = ""--certificate=${1#--certificate=}"" 
			shift 1
		} else {
		       error 1 NEEDARG "option requires an argument %s" $1 
		}
		}
	    --private-key|--private-key=?* {
		if test $1 = "--private-key" -a -n $2 {
			setvar PRIVATEKEY = ""--private-key=$2""
			shift 2
		} elif test $1 != ${1#--private-key=} {
			setvar PRIVATEKEY = ""--private-key=${1#--private-key=}"" 
			shift 1
		} else {
		       error 1 NEEDARG "option requires an argument %s" $1 
		}
		}
	    --no-check-certificate {
		setvar CHECKCERTIF = ""--no-check-certificate""
		shift
		}
	    -* {
		error 1 BADARG "unrecognized or invalid option %s" $1
		}
	    * {
		break
		}
	}
    }
}

###########################################################################

if test -n $DISABLE_KEYRING -a -n $FORCE_KEYRING {
	error 1 BADARG "Both --no-check-gpg and --force-check-gpg specified, please pick one (at most)"
}

###########################################################################

if test $SECOND_STAGE_ONLY = "true" {
	setvar SUITE = $(cat $DEBOOTSTRAP_DIR/suite)
	setvar ARCH = $(cat $DEBOOTSTRAP_DIR/arch)
	if test -e $DEBOOTSTRAP_DIR/variant {
		setvar VARIANT = $(cat $DEBOOTSTRAP_DIR/variant)
		setvar SUPPORTED_VARIANTS = "$VARIANT"
	}
	if test -z $CHROOTDIR {
		setvar TARGET = '/'
	} else {
		setvar TARGET = "$CHROOTDIR"
	}
	setvar SCRIPT = "$DEBOOTSTRAP_DIR/suite-script"
} else {
	if test -z $1 || test -z $2 {
		usage_err 1 NEEDSUITETARGET "You must specify a suite and a target."
	}
	setvar SUITE = "$1"
	setvar TARGET = "$2"
	setvar USER_MIRROR = "$3"
	setvar TARGET = "${TARGET%/}"
	if test ${TARGET#/} = ${TARGET} {
		if test ${TARGET%/*} = $TARGET  {
			setvar TARGET = "$(echo "`pwd`/$TARGET")"
		} else {
			setvar TARGET = "$(cd "${TARGET%/*}"; echo "`pwd`/${TARGET##*/}")"
		}
	}

	setvar SCRIPT = ""$DEBOOTSTRAP_DIR/scripts/$1""
	if test -n $VARIANT && test -e "${SCRIPT}.${VARIANT}" {
		setvar SCRIPT = ""${SCRIPT}.${VARIANT}""
		setvar SUPPORTED_VARIANTS = "$VARIANT"
	}
	if test $4 != "" {
		setvar SCRIPT = "$4"
	}
}

###########################################################################

if in_path dpkg && \
     dpkg --print-architecture >/dev/null 2>&1 {
	setvar HOST_ARCH = $(/usr/bin/dpkg --print-architecture)
} elif in_path udpkg && \
     udpkg --print-architecture >/dev/null 2>&1 {
	setvar HOST_ARCH = $(/usr/bin/udpkg --print-architecture)
} elif test -e $DEBOOTSTRAP_DIR/arch {
	setvar HOST_ARCH = $(cat $DEBOOTSTRAP_DIR/arch)
}
setvar HOST_OS = "$HOST_ARCH"
# basic host OS guessing for non-Debian systems
if test -z $HOST_OS {
	case{
		Linux {
			setvar HOST_OS = 'linux'
		}
		GNU/kFreeBSD {
			setvar HOST_OS = 'kfreebsd'
		}
		GNU {
			setvar HOST_OS = 'hurd'
		}
		FreeBSD* {
			setvar HOST_OS = 'freebsd'
		}
	}
}

if test -z $ARCH {
	setvar ARCH = "$HOST_ARCH"
}

if test -z $ARCH || test -z $HOST_OS {
        error 1 WHATARCH "Couldn't work out current architecture"

}

if test $HOST_OS = "kfreebsd" || test $HOST_OS = "freebsd" {
	for module in linprocfs fdescfs tmpfs linsysfs {
		kldstat -m $module > /dev/null 2>&1 || warning SANITYCHECK "Probably required module %s is not loaded" $module
	}
}

if test $TARGET = "/" {
	setvar CHROOT_CMD = """"
} else {
	setvar CHROOT_CMD = ""chroot $TARGET""
}

if test -z $SHA_SIZE {
	setvar SHA_SIZE = '256'
}
if ! in_path "sha${SHA_SIZE}sum" && ! in_path "sha${SHA_SIZE}" {
	setvar SHA_SIZE = '1'
}
setvar DEBOOTSTRAP_CHECKSUM_FIELD = ""SHA$SHA_SIZE""

export ARCH SUITE TARGET CHROOT_CMD SHA_SIZE DEBOOTSTRAP_CHECKSUM_FIELD

if am_doing_phase first_stage second_stage {
	if in_path id && test $(id -u) -ne 0 {
		error 1 NEEDROOT "debootstrap can only run as root"
	}
	# Ensure that we can create working devices and executables on the target.
	if ! check_sane_mount $TARGET {
		error 1 NOEXEC "Cannot install into target '$TARGET' mounted with noexec or nodev"
	}
}

if test ! -e $SCRIPT {
	error 1 NOSCRIPT "No such script: %s" $SCRIPT
}

###########################################################################

if test $TARGET != "" {
	mkdir -p "$TARGET/debootstrap"
}

###########################################################################

# Use of fd's by functions/scripts:
#
#    stdin/stdout/stderr: used normally
#    fd 4: I:/W:/etc information
#    fd 5,6: spare for functions
#    fd 7,8: spare for scripts

if test $USE_DEBIANINSTALLER_INTERACTION = yes {
	#    stdout=stderr: full log of debootstrap run
	#    fd 3: I:/W:/etc information
	exec 4>&3
} elif am_doing_phase printdebs {
	#    stderr: I:/W:/etc information
	#    stdout: debs needed
	exec 4>&2
} else {
	#    stderr: used in exceptional circumstances only
	#    stdout: I:/W:/etc information
	#    $TARGET/debootstrap/debootstrap.log: full log of debootstrap run
	exec 4>&1
	exec >>"$TARGET/debootstrap/debootstrap.log"
	exec 2>&1
}

###########################################################################

if test $UNPACK_TARBALL {
	if test ${UNPACK_TARBALL#/} = $UNPACK_TARBALL {
		error 1 TARPATH "Tarball must be given a complete path"
	}
	if test ${UNPACK_TARBALL%.tar} != $UNPACK_TARBALL {
		shell {cd $TARGET && tar -xf $UNPACK_TARBALL}
	} elif test ${UNPACK_TARBALL%.tgz} != $UNPACK_TARBALL {
		shell {cd $TARGET && zcat $UNPACK_TARBALL | tar -xf -}
	} else {
		error 1 NOTTAR "Unknown tarball: must be either .tar or .tgz"
	}
}

###########################################################################

source "$SCRIPT"

if test $SECOND_STAGE_ONLY = "true" {
	setvar MIRRORS = "null:"
} else {
	setvar MIRRORS = "$DEF_MIRROR"
	if test $USER_MIRROR != "" {
		setvar MIRRORS = "$USER_MIRROR"
		setvar MIRRORS = "${MIRRORS%/}"
	}
}

export MIRRORS

setvar ok = 'false'
for v in $SUPPORTED_VARIANTS {
	if doing_variant $v { setvar ok = 'true'; }
}
if ! $ok {
	error 1 UNSUPPVARIANT "unsupported variant"
}

###########################################################################

if am_doing_phase finddebs {
	if test $FINDDEBS_NEEDS_INDICES = "true" || \
	   test $RESOLVE_DEPS = "true" {
		download_indices
		setvar GOT_INDICES = 'true'
	}

	work_out_debs

	setvar base = $(without "$base $ADDITIONAL" "$EXCLUDE")

	if test $RESOLVE_DEPS = true {
		setvar requiredX = $(echo $(echo $required | tr ' ' '\n' | sort | uniq))
		setvar baseX = $(echo $(echo $base | tr ' ' '\n' | sort | uniq))

		setvar baseN = $(without "$baseX" "$requiredX")
		setvar baseU = $(without "$baseX" "$baseN")

		if test $baseU != "" {
			info REDUNDANTBASE "Found packages in base already in required: %s" $baseU
		}

		info RESOLVEREQ "Resolving dependencies of required packages..."
		setvar required = $(resolve_deps $requiredX)
		info RESOLVEBASE "Resolving dependencies of base packages..."
		setvar base = $(resolve_deps $baseX)
		setvar base = $(without "$base" "$required")

		setvar requiredX = $(without "$required" "$requiredX")
		setvar baseX = $(without "$base" "$baseX")
		if test $requiredX != "" {
			info NEWREQUIRED "Found additional required dependencies: %s" $requiredX
		}
		if test $baseX != "" {
			info NEWBASE "Found additional base dependencies: %s" $baseX
		}
	}

	setvar all_debs = ""$required $base""
}

if am_doing_phase printdebs {
	echo $all_debs
}

if am_doing_phase dldebs {
	if test $GOT_INDICES != "true" {
		download_indices
	}
	download $all_debs
}

if am_doing_phase maketarball {
	shell {cd $TARGET;
	 tar czf - var/lib/apt var/cache/apt} >$MAKE_TARBALL
}

if am_doing_phase first_stage {
	choose_extractor

	# first stage sets up the chroot -- no calls should be made to
	# "chroot $TARGET" here; but they should be possible by the time it's
	# finished
	first_stage_install

	if ! am_doing_phase second_stage {
		cp $0				 "$TARGET/debootstrap/debootstrap"
		cp $DEBOOTSTRAP_DIR/functions	 "$TARGET/debootstrap/functions"
		cp $SCRIPT			 "$TARGET/debootstrap/suite-script"
		echo $ARCH			>"$TARGET/debootstrap/arch"
		echo $SUITE			>"$TARGET/debootstrap/suite"
		test "" = $VARIANT ||
		echo $VARIANT			>"$TARGET/debootstrap/variant"
		echo $required		>"$TARGET/debootstrap/required"
		echo $base			>"$TARGET/debootstrap/base"

		chmod 755 "$TARGET/debootstrap/debootstrap"
	}
}

if am_doing_phase second_stage {
	if test $SECOND_STAGE_ONLY = true {
		setvar required = "$(cat $DEBOOTSTRAP_DIR/required)"
		setvar base = "$(cat $DEBOOTSTRAP_DIR/base)"
		setvar all_debs = ""$required $base""
	}

	# second stage uses the chroot to clean itself up -- has to be able to
	# work from entirely within the chroot (in case we've booted into it,
	# possibly over NFS eg)

	second_stage_install

	# create sources.list
	# first, kill debootstrap.invalid sources.list
	if test -e "$TARGET/etc/apt/sources.list" {
		rm -f "$TARGET/etc/apt/sources.list"
	}
	if test ${MIRRORS#http://} != $MIRRORS {
		setup_apt_sources ${MIRRORS%% *}
		mv_invalid_to ${MIRRORS%% *}
	} else {
		setup_apt_sources $DEF_MIRROR
		mv_invalid_to $DEF_MIRROR
	}

	if test -e "$TARGET/debootstrap/debootstrap.log" {
		if test $KEEP_DEBOOTSTRAP_DIR = true {
			cp "$TARGET/debootstrap/debootstrap.log" "$TARGET/var/log/bootstrap.log"
		} else {
			# debootstrap.log is still open as stdout/stderr and needs
			# to remain so, but after unlinking it some NFS servers
			# implement this by a temporary file in the same directory,
			# which makes it impossible to rmdir that directory.
			# Moving it instead works around the problem.
			mv "$TARGET/debootstrap/debootstrap.log" "$TARGET/var/log/bootstrap.log"
		}
	}
	sync

	if test $KEEP_DEBOOTSTRAP_DIR = true {
		if test -x "$TARGET/debootstrap/debootstrap" {
			chmod 644 "$TARGET/debootstrap/debootstrap"
		}
	} else {
		rm -rf "$TARGET/debootstrap"
	}
}

if am_doing_phase kill_target {
	if test $KEEP_DEBOOTSTRAP_DIR != true {
		info KILLTARGET "Deleting target directory"
		rm -rf $TARGET
	}
}