#! /bin/bash
# common modules for pbuilder.
#   pbuilder -- personal Debian package builder
#   Copyright (C) 2001-2007 Junichi Uekawa
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA


function showhelp () {
    cat <<EOF
pbuilder - a personal builder
Copyright 2001-2007 Junichi Uekawa
Distributed under GNU Public License version 2 or later

pbuilder [operation] [pbuilder-options]
pdebuild [pdebuild-options] -- [pbuilder-options]

command lines:
pbuilder --create [--basetgz base.tgz-path] [--distribution etch|lenny|sid|experimental]
  Creates a base.tgz

pbuilder --update [--basetgz base.tgz-path] [--distribution etch|lenny|sid|experimental]
  Updates a base.tgz

pbuilder --build [--basetgz base.tgz-path] pbuilder_2.2.0-1.dsc
  Builds using the base.tgz. Requires a .dsc filename

pbuilder --clean
  Cleans the temporal build directory.

pbuilder --login
pbuilder --execute -- [command] [command-options]
  Logs in to the build environment and execute command.

pbuilder --dumpconfig
  Dumps configuration information to stdout for debugging.

pbuilder-options:
 --basetgz [base.tgz location]
 --buildplace [location of build]
 --mirror [mirror location]
 --othermirror [other mirror location in apt deb-line format, delimited with | signs]
 --http-proxy [proxy]
 --distribution [distribution(sid|etch|lenny|experimental)]
 --components [components]
 --buildresult [location-to-copy-build-result]
 --aptcache [location of retrieved package files]
 --removepackages [packages-to-remove on pbuilder create]
 --extrapackages [packages-to-add on pbuilder create]
 --configfile [configuration file to load]
 --hookdir [hook directory]
 --debemail [mail address]
 --debbuildopts [dpkg-buildpackage options]
 --logfile [filename to output log]
 --pkgname-logfile
 --aptconfdir [overriding apt config dir]
 --timeout [timeout time]
 --override-config 
 --binary-arch
 --preserve-buildplace
 --bindmounts [bind-mount-point]
 --debug
 --autocleanaptcache
 --debootstrapopts [debootstrap options]
 --save-after-login/--save-after-exec
 --debootstrap [debootstrap|cdebootstrap]

pdebuild-specific pbuilder-options:
 --pbuilderroot [command to obtain root privilege for pbuilder] 
 --pbuildersatisfydepends [command to satisfy build-dependencies]
 --buildsourceroot [command to obtain root privilege for dpkg-buildpackage]
 --use-pdebuild-internal
 --auto-debsign
 --debsign-k [keyid]
EOF
    exit 1
}

# test whether a directory is empty
# fails if "$*" exists but isn't a directory
# fails and outputs garbage if "$*" doesn't actually exist
is_empty_dir() {
  return "$(find "$*" -maxdepth 0 -type d -empty -printf 0 -o -printf 1)"
}

# sanity checks to ensure mountpoint $1 is truly unmounted in $BUILDPLACE
# (fails relatively often to ensure we don't "rm -rf" a bind mount)
function seems_truly_unmounted() {
    local mountpoint
    mountpoint="$1"
    if ! [ -e "$BUILDPLACE/$mountpoint" ]; then
        echo "W: $mountpoint doesn't exist" >&2 
        return 1
    fi
    if ! [ -d "$BUILDPLACE/$mountpoint" ]; then
        echo "W: $mountpoint isn't a directory" >&2 
        return 1
    fi
    if [ -r "$BUILDPLACE/proc/mounts" ] && grep -q "^[^ ]* $mountpoint " "$BUILDPLACE/proc/mounts"; then
        echo "W: $mountpoint is mounted according to build place's /proc/mounts" >&2 
        return 1
    fi
    if [ -r "/proc/mounts" ] && grep -q "^[^ ]* $BUILDPLACE/$mountpoint " "/proc/mounts"; then
        echo "W: $mountpoint is mounted according to system's /proc/mounts" >&2 
        return 1
    fi
    if ! is_empty_dir "$BUILDPLACE/$mountpoint"; then
        echo "W: $mountpoint not empty" >&2 
        return 1
    fi
    return 0
}

function umount_one () {
    if [ "${IGNORE_UMOUNT}" = "yes" ]; then
	# support ignore umount option.
	echo " -> ignoring umount of $1 filesystem"
	return
    fi
    echo " -> unmounting $1 filesystem"
    local UMOUNT_OUTPUT
    if ! UMOUNT_OUTPUT="$(LC_ALL=C umount "$BUILDPLACE/$1" 2>&1)"; then
        echo "W: Could not unmount $1: $UMOUNT_OUTPUT" >&2 
        local ignore_umount_error="no"
        case $UMOUNT_OUTPUT in
          "umount: "*": not found"|"umount:"*": not mounted")
            # run an additional set of sanity checks
            if seems_truly_unmounted "$1"; then
                ignore_umount_error="yes"
            else
                echo "W: Tried ignoring error in unmount, but sanity check failed: $1 might still be mounted" >&2 
            fi
            ;;
          *)
            :
            ;;
        esac
        if [ "$ignore_umount_error" != "yes" ]; then
	    echo "W: Retrying to unmount $1 in 5s" >&2 
	    sleep 5s
	    while ! umount "$BUILDPLACE/$1"; do
		sleep 5s
		cat <<EOF

  Could not unmount $1, some programs might
  still be using files in /proc (klogd?).
  Please check and kill these processes manually
  so that I can unmount $1.  Last umount error was:
$UMOUNT_OUTPUT

  This error only affects chroots; you may want to use
  user-mode-linux to avoid this message.

EOF
		chroot "$BUILDPLACE" bin/sh
	    done
        else
            echo "W: Ignored error in unmount" >&2 
        fi
    fi
}

function umountproc () {
    # push arguments on a stack to reverse direction.
    local reversed
    reversed=
    for mnt in $BINDMOUNTS; do
	reversed="$mnt $reversed"
    done
    for mnt in $reversed; do
	umount_one "$mnt"
    done
    if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then
	umount_one "selinux"
    fi
    if [ "$USEDEVPTS" = "yes" ]; then
	umount_one "dev/pts"
    fi
    if [ "$USEDEVFS" = "yes" ]; then
	umount_one "dev"
    fi
    if [ "$USEPROC" = "yes" ]; then
	if [ -e $BUILDPLACE/proc/sys/fs/binfmt_misc/status ]; then
	    umount_one "proc/sys/fs/binfmt_misc"
	fi
	umount_one "proc"
    fi
}


# Mount /proc /dev/pts /dev and bind-mount points
# Also create a policy-rc.d script if it doesn't already exist.
function mountproc () {
    if [ "$USEPROC" = "yes" ]; then
	echo " -> mounting /proc filesystem"
	mkdir -p $BUILDPLACE/proc
	mount -t proc /proc "$BUILDPLACE/proc"
	ln -s ../proc/mounts $BUILDPLACE/etc/mtab 2> /dev/null || true
    fi
    if [ "$USEDEVFS" = "yes" ]; then
	echo " -> mounting /dev filesystem"
	mkdir -p $BUILDPLACE/dev || true
	mount -t devfs /dev "$BUILDPLACE/dev" 
    fi
    if [ "$USEDEVPTS" = "yes" ]; then
	echo " -> mounting /dev/pts filesystem"
	mkdir -p $BUILDPLACE/dev/pts || true
	mount -t devpts /dev/pts "$BUILDPLACE/dev/pts" 
    fi
    if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then
	echo " -> mounting selinux filesystem"
	mkdir -p $BUILDPLACE/selinux
	mount -t selinuxfs /selinux "$BUILDPLACE/selinux"
    fi    
    for mnt in $BINDMOUNTS; do
	echo "-> Mounting $mnt"
	mkdir -p "$BUILDPLACE/$mnt"
	mount -obind "$mnt" "$BUILDPLACE/$mnt"
    done
    if [ -f "$BUILDPLACE/usr/sbin/policy-rc.d" ]; then
	echo " -> policy-rc.d already exists"
    else
	echo " -> installing dummy policy-rc.d"
	echo "\
#!/bin/sh

while true; do
case "\$1" in
  -*) shift ;;
  makedev) exit 0;;
  x11-common) exit 0;;
  *)  exit 101;;
esac
done

" >  "$BUILDPLACE/usr/sbin/policy-rc.d" 
	chmod a+x "$BUILDPLACE/usr/sbin/policy-rc.d" 
    fi
}

## function to clean subdirs, use instead of rm -r
function clean_subdirectories () {
  if [ -z "$1" ]; then
      echo "Fatal internal error in clean_subdirectories"
      exit 1;
  fi
  if [ ! -d "$1" ]; then
      echo "Warning: directory $1 does not exist in clean_subdirectories"
      return;
  fi
  echo "    -> removing directory $1 and its subdirectories"
  find "$1" -xdev \( \! -type d \) -print0 |xargs -0 rm -f
  find "$1" -xdev -depth -type d -print0 | \
      (xargs -0 rmdir || true)
}

function cleanbuildplace () {
    if [ "$?" -ne 0 ]; then
	echo " -> Aborting with an error";
    fi
    if [ "${INTERNAL_BUILD_UML}" != "yes" ]; then
	if [ -d "$BUILDPLACE" ]; then 
	    echo " -> cleaning the build env "
	    clean_subdirectories "$BUILDPLACE"
	fi;
    fi
}

function umountproc_cleanbuildplace () {
    # rolling back to abort.
    if [ "$?" -ne 0 ]; then
	echo " -> Aborting with an error";
    fi
    umountproc
    cleanbuildplace
}

function saveaptcache_umountproc_cleanbuildplace () {
    # save the apt cache, and call umountproc_cleanbuildplace
    save_aptcache
    umountproc_cleanbuildplace
}

function installaptlines (){
    echo "  -> Installing apt-lines"
    rm -f "$BUILDPLACE"/etc/apt/sources.list
    if [ -z "$DISTRIBUTION" ]; then
	echo "Distribution not specified, please specify" >&2
	exit 1
    fi
    if [ -z "$COMPONENTS" ] ; then
	COMPONENTS="main"
    fi
    if [ -n "$OTHERMIRROR" ]; then 
	echo "$OTHERMIRROR" | tr "|" "\n" >> "$BUILDPLACE"/etc/apt/sources.list 
    fi
    if [ -n "$MIRRORSITE" ] ; then
	cat >> "$BUILDPLACE"/etc/apt/sources.list << EOF
deb $MIRRORSITE $DISTRIBUTION $COMPONENTS
#deb-src $MIRRORSITE $DISTRIBUTION $COMPONENTS
EOF
    fi
    if [ -n "$APTCONFDIR" ]; then
	echo "  -> Copy " "$APTCONFDIR"/* " to chroot"
	cp -a "$APTCONFDIR/"* "$BUILDPLACE"/etc/apt
    fi

    if [ ! -d "$BUILDPLACE"/etc/apt/apt.conf.d ]; then
	echo "  -> Create /etc/apt/apt.conf.d/ inside chroot"
	mkdir "$BUILDPLACE"/etc/apt/apt.conf.d
    fi

    # configure /etc/apt.conf.d/15pbuilder
    cat > "$BUILDPLACE"/etc/apt/apt.conf.d/15pbuilder <<EOF
APT::Install-Recommends "false";
EOF
    if [ -n "$EXPERIMENTAL" ]; then
	echo "  -> Installing apt-lines and pinning for experimental"
	if [ -n "$MIRRORSITE" ] ; then
	    echo "deb $MIRRORSITE experimental main" >> "$BUILDPLACE"/etc/apt/sources.list
	    echo "#deb-src $MIRRORSITE experimental main" >> "$BUILDPLACE"/etc/apt/sources.list
	fi
	cat >> "$BUILDPLACE"/etc/apt/apt.conf.d/15pbuilder <<EOF
APT::Default-Release "experimental";
EOF
    fi
}

function copy_local_configuration () {
    echo " -> copying local configuration"
    for a in hosts hostname resolv.conf; do 
	if [ -f "/etc/$a" ]; then
		rm -f "$BUILDPLACE/etc/$a"
		cp $( readlink -f "/etc/$a" ) "$BUILDPLACE/etc/$a";
	else
		echo "W: No local /etc/$a to copy, relying on $BUILDPLACE/etc/$a to be correct" >&2
 	fi
    done
}

function extractbuildplace () {
    # after calling this function, umountproc, and cleanbuildplace
    # needs to be called. Please trap it after calling this function.

    if [ "${INTERNAL_BUILD_UML}" != "yes" -a ! \( "${PRESERVE_BUILDPLACE}" = "yes" -a -d "$BUILDPLACE" \) ]; then
	cleanbuildplace
	echo "Building the build Environment"
	if ! mkdir -p "$BUILDPLACE"; then
	    echo "E: failed to build the directory to chroot" >&2
	    exit 1
	fi
	echo " -> extracting base tarball [${BASETGZ}]"
	if [ ! -f "$BASETGZ" ]; then
	    echo "E: failed to find $BASETGZ, have you done <pbuilder create> to create your base tarball yet?" >&2
	    exit 1
	fi
	if ! (cd "$BUILDPLACE" && tar xfzp "$BASETGZ"); then
	    echo "E: failed to extract $BASETGZ to $BUILDPLACE" >&2 
	    exit 1
	fi
	echo " -> creating local configuration"
	hostname -f > "$BUILDPLACE/etc/mailname"
    fi
    copy_local_configuration

    # installaptlines may fail with exit 1, do it earlier than mountproc.
    if [ "$OVERRIDE_APTLINES" = "yes" ]; then
	installaptlines
    fi

    mountproc
    mkdir -p "$BUILDPLACE/tmp/buildd"
}


function recover_aptcache() {
    local doit
    # recover the aptcache archive
    if [ -n "$APTCACHE" ]; then
	if [ "$APTCACHEHARDLINK" = "yes" ]; then
	    doit=ln
	else
	    doit=cp
	fi
	echo "Obtaining the cached apt archive contents"
	find "$APTCACHE" -maxdepth 1 -name \*.deb | \
	    while read A ; do
	    $doit "$A" "$BUILDPLACE/var/cache/apt/archives/" || true
	done
    fi
}

function save_aptcache() {
    # save the current aptcache archive
    # it is safe to call this function several times.
    local doit
    if [ -n "$APTCACHE" ]; then
	echo "Copying back the cached apt archive contents"
	mkdir -p "$APTCACHE" ;
	if [ "$APTCACHEHARDLINK" = "yes" ]; then
	    doit=ln
	else
	    doit=cp
	fi
	find "$BUILDPLACE/var/cache/apt/archives/" -maxdepth 1 -name \*.deb | \
	    while read A ;do
	    if [ ! -f "$APTCACHE/"$(basename "$A") -a -f "$A" ]; then
		echo " -> new cache content "$(basename "$A")" added"
		$doit "$A" "$APTCACHE/" || true
	    fi
	done
    fi
}

function create_basetgz() {
    # create base.tgz
    ( 
	if ! cd "$BUILDPLACE"; then
	    echo "Error: unexpected error in chdir to $BUILDPLACE" >&2
	    exit 1;
	fi
	while test -f "${BASETGZ}.tmp"; do
	    echo "  -> Someone else has lock over ${BASETGZ}.tmp, waiting"
	    sleep 10s
	done
	echo " -> creating base tarball [${BASETGZ}]"
	if ! tar cfz "${BASETGZ}.tmp" * ; then
	    echo " -> failed building base tarball"
	    rm -f "${BASETGZ}.tmp"
	    exit 1;
	fi
	mv "${BASETGZ}.tmp" "${BASETGZ}"
    )
}

# all trap hooks that should lead to 'exit'; and error exit.
function cleanbuildplace_trap () {
    cleanbuildplace
    trap - exit sighup
    exit 1
}
function saveaptcache_umountproc_cleanbuildplace_trap () {
    saveaptcache_umountproc_cleanbuildplace
    trap - exit sighup
    exit 1
}
function umountproc_cleanbuildplace_trap () {
    umountproc_cleanbuildplace
    trap - exit sighup
    exit 1
}
function umountproc_trap () {
    umountproc
    trap - exit sighup
    exit 1
}

#Setting environmental variables that are really required:
#required for some packages to install...
export LANG=C
export LC_ALL=C
