#! /bin/bash # common modules for pbuilder. # pbuilder -- personal Debian package builder # Copyright (C) 2001-2009 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 . "${BASH_SOURCE%/*}/pbuilder-runhooks" 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 sid|experimental|...] Creates a base.tgz pbuilder --update [--basetgz base.tgz-path] [--distribution 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|experimental|...)] --architecture [architecture] --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 --twice --autocleanaptcache --compressprog [program] --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 --architecture --auto-debsign --debsign-k [keyid] EOF exit 1 } # Log a message # message is of a format # E: error message # W: warning message # I: informational message function log() { case "$*" in "E: "*) echo "$*" >&2 ;; "W: "*) echo "$*" >&2 ;; "I: "*) echo "$*" ;; *) echo "malformed log message: $*" exit 1 ;; esac } log.e() { log "E: $*" } log.w() { log "W: $*" } log.i() { log "I: $*" } # 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 log "W: $mountpoint doesn't exist" return 1 fi if ! [ -d "$BUILDPLACE/$mountpoint" ]; then log "W: $mountpoint isn't a directory" return 1 fi if [ -r "$BUILDPLACE/proc/mounts" ] && \ grep -q "^[^ ]* $mountpoint " "$BUILDPLACE/proc/mounts"; then log "W: $mountpoint is mounted according to build place's /proc/mounts" return 1 fi if [ -r "/proc/mounts" ] && \ grep -q "^[^ ]* $BUILDPLACE/$mountpoint " "/proc/mounts"; then log "W: $mountpoint is mounted according to system's /proc/mounts" return 1 fi if ! is_empty_dir "$BUILDPLACE/$mountpoint"; then log "W: $mountpoint not empty" return 1 fi return 0 } function umount_one () { DEB_BUILD_ARCH_OS=$(dpkg-architecture -qDEB_BUILD_ARCH_OS) if [ "${IGNORE_UMOUNT}" = "yes" ]; then # support ignore umount option. log.i "ignoring umount of $1 filesystem" return fi log.i "unmounting $1 filesystem" local UMOUNT_OUTPUT if ! UMOUNT_OUTPUT="$(LC_ALL=C umount "$BUILDPLACE/$1" 2>&1)"; then log "W: Could not unmount $1: $UMOUNT_OUTPUT" 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 log "W: Tried ignoring error in unmount, but sanity check failed: $1 might still be mounted" fi ;; *) : ;; esac if [ "$ignore_umount_error" != "yes" ]; then log "W: Retrying to unmount $1 in 5s" 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 log "W: Ignored error in unmount" fi fi } function umountproc () { # push arguments on a stack to reverse direction. local reversed DEB_BUILD_ARCH_OS=$(dpkg-architecture -qDEB_BUILD_ARCH_OS) 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 "$(grep -m 1 ^selinuxfs /proc/mounts | cut -d ' ' -f 2)" fi if [ "$DEB_BUILD_ARCH_OS" = "linux" ] && [ "$USEDEVPTS" = "yes" ]; then umount_one "dev/pts" fi if [ "$USEDEVFS" = "yes" ]; then umount_one "dev" fi if [ "$USERUNSHM" = "yes" ] && [ "$DEB_BUILD_ARCH_OS" != "hurd" ]; then umount_one "run/shm" fi if [ "$USEPROC" = "yes" ]; then if [ "$DEB_BUILD_ARCH_OS" = "linux" ] && [ -e "$BUILDPLACE/proc/sys/fs/binfmt_misc/status" ]; then umount_one "proc/sys/fs/binfmt_misc" fi umount_one "proc" fi if [ "$DEB_BUILD_ARCH_OS" = "kfreebsd" ]; then umount_one "sys" fi if [ "$DEB_BUILD_ARCH_OS" = "hurd" ]; then umount_one "servers" umount_one "dev" # Workaround to remove chroot on Hurd: once /dev firmlink is # removed, chroot removal either gets stuck or fails by removing # some devices. for dev in "$BUILDPLACE"/dev/{netdde,tty*,pty*,fd,vcs}; do settrans -fg "$dev" done 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 () { local -a mounted DEB_BUILD_ARCH_OS=$(dpkg-architecture -qDEB_BUILD_ARCH_OS) if [ "$USEPROC" = "yes" ]; then log.i "mounting /proc filesystem" mkdir -p "$BUILDPLACE/proc" case "$DEB_BUILD_ARCH_OS" in kfreebsd) PROCFS="linprocfs" ;; hurd) settrans -fg "$BUILDPLACE/proc" PROCFS="firmlink" ;; *) PROCFS="proc" ;; esac mount -t "$PROCFS" /proc "$BUILDPLACE/proc" || [ "$DEB_BUILD_ARCH_OS" = hurd ] ln -s ../proc/mounts "$BUILDPLACE/etc/mtab" 2> /dev/null || true mounted[${#mounted[@]}]="$BUILDPLACE/proc" fi if [ "$USEDEVFS" = "yes" ]; then log.i "mounting /dev filesystem" mkdir -p "$BUILDPLACE/dev" || true mount -t devfs /dev "$BUILDPLACE/dev" mounted[${#mounted[@]}]="$BUILDPLACE/dev" fi if [ "$DEB_BUILD_ARCH_OS" = "linux" ] && [ "$USERUNSHM" = "yes" ]; then log.i "mounting /run/shm filesystem" mkdir -p "$BUILDPLACE/run/shm" || true mount -t tmpfs tmpfs "$BUILDPLACE/run/shm" mounted[${#mounted[@]}]="$BUILDPLACE/run/shm" fi if [ "$DEB_BUILD_ARCH_OS" = "linux" ] && [ "$USEDEVPTS" = "yes" ]; then log.i "mounting /dev/pts filesystem" mkdir -p "$BUILDPLACE/dev/pts" || true TTYGRP=5 TTYMODE=620 [ -f /etc/default/devpts ] && . /etc/default/devpts mount -t devpts none "$BUILDPLACE/dev/pts" -onoexec,nosuid,gid=$TTYGRP,mode=$TTYMODE mounted[${#mounted[@]}]="$BUILDPLACE/dev/pts" fi if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then log.i "mounting selinux filesystem" local SELINUX="$(grep -m 1 ^selinuxfs /proc/mounts | cut -d ' ' -f 2)" mkdir -p "$BUILDPLACE/$SELINUX" mount --bind "$SELINUX" "$BUILDPLACE/$SELINUX" mount -o remount,ro,bind "$BUILDPLACE/$SELINUX" mounted[${#mounted[@]}]="$BUILDPLACE/$SELINUX" fi if [ "$DEB_BUILD_ARCH_OS" = "hurd" ]; then # /dev and /servers might have already been mounted at # debootstrap chroot creation mount -t firmlink /dev "$BUILDPLACE/dev" || true mounted[${#mounted[@]}]="$BUILDPLACE/dev" mount -t firmlink /servers "$BUILDPLACE/servers" || true mounted[${#mounted[@]}]="$BUILDPLACE/servers" fi MOUNTPARAMS="-obind" [ "$DEB_BUILD_ARCH_OS" = "kfreebsd" ] && MOUNTPARAMS="-t nullfs" for mnt in $BINDMOUNTS; do log.i "Mounting $mnt" if mkdir -p "$BUILDPLACE/$mnt" && mount $MOUNTPARAMS "$mnt" "$BUILDPLACE/$mnt"; then # successful. mounted[${#mounted[@]}]="$mnt" else # this part of code is the only part which is supposed to fail. # When unsuccessful, backtrack / umount and abort. if [ -n "${mounted[*]}" ]; then log.i "error recovery: umount successfully mounted mount-points: ${mounted[@]}" for umnt in "${mounted[@]}"; do log.i "umounting $umnt" umount "$umnt" done fi exit 1 fi done if [ -f "$BUILDPLACE/usr/sbin/policy-rc.d" ]; then log.i "policy-rc.d already exists" else log.i "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 log "E: Fatal internal error in clean_subdirectories" exit 1; fi if [ ! -d "$1" ]; then log "W: directory $1 does not exist in clean_subdirectories" return; fi log.i "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 log "W: Aborting with an error"; fi unloadhooks if [ "${INTERNAL_BUILD_UML}" != "yes" ]; then if [ -d "$BUILDPLACE" ]; then # A directory on the same partition as $BUILDPLACE, bind-mounted # into $BUILDPLACE, will be cleaned out by clean_subdirectories # (because -xdev doesn't know about bind mounts). To avoid that # potential disaster (and also to avoid ugly error messages from # rmdir otherwise), we want to make sure that there is *nothing* # mounted under the chroot before we do our bulldozer routine. # # The readlink -f is a simple way to canonicalize the path for # $BUILDPLACE (no dirty double slashes for *us*), so it matches # what will be in the output of mount. if mount |grep -q -F " $(readlink -f "$BUILDPLACE")/"; then log "E: Something is still mounted under ${BUILDPLACE}; unmount and remove ${BUILDPLACE} manually" else log.i "cleaning the build env " clean_subdirectories "$BUILDPLACE" fi fi; fi } function umountproc_cleanbuildplace () { # rolling back to abort. if [ "$?" -ne 0 ]; then log "W: 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 (){ log.i "Installing apt-lines" rm -f "$BUILDPLACE"/etc/apt/sources.list if [ -z "$DISTRIBUTION" ]; then log "E: Distribution not specified, please specify" exit 1 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 log.i "Copy " "$APTCONFDIR"/* " to chroot" cp -a "$APTCONFDIR/"* "$BUILDPLACE"/etc/apt fi if [ ! -d "$BUILDPLACE"/etc/apt/apt.conf.d ]; then log.i "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"; Acquire::Languages none; EOF if [ -n "$EXPERIMENTAL" ]; then log.i "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 () { log.i "copying local configuration" if [ -n "$CONFDIR" ] && [ -d "$CONFDIR" ]; then log.i "copying files from $CONFDIR (if possible) instead of using the system ones" fi for a in hosts hostname resolv.conf mailname; do if [ -n "$CONFDIR" ] && [ -f "$CONFDIR/$a" ]; then rm -f "$BUILDPLACE/etc/$a" cp "$( readlink -f "$CONFDIR/$a" )" "$BUILDPLACE/etc/$a" elif [ -f "/etc/$a" ]; then rm -f "$BUILDPLACE/etc/$a" cp "$( readlink -f "/etc/$a" )" "$BUILDPLACE/etc/$a" else log "W: No local /etc/$a to copy, relying on $BUILDPLACE/etc/$a to be correct" 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 log.i "Building the build Environment" if ! mkdir -p "$BUILDPLACE"; then log "E: failed to build the directory to chroot" exit 1 fi log.i "extracting base tarball [${BASETGZ}]" if [ ! -f "$BASETGZ" ]; then log "E: failed to find $BASETGZ, have you done <pbuilder create> to create your base tarball yet?" exit 1 fi if ! (cd "$BUILDPLACE" && tar -x --use-compress-program "$COMPRESSPROG" -p -f "$BASETGZ"); then log "E: failed to extract $BASETGZ to $BUILDPLACE" exit 1 fi fi copy_local_configuration loadhooks # installaptlines may fail with exit 1, do it earlier than mountproc. if [ "$OVERRIDE_APTLINES" = "yes" ]; then installaptlines else # Warn if override config is not set if [ "$OVERRIDE_APTLINES_WARN" = "yes" ]; then log "W: --override-config is not set; not updating apt.conf Read the manpage for details." fi fi mountproc # FIXME maybe add more checks here? - actually it's not even really needed, # since it's created at chroot creation time too. mkdir -p "${BUILDPLACE}${BUILDDIR}" # XXX added in 0.216, to be deprecated in the future # Add a compatibility symlink from the old BUILDDIR (/tmp/buildd) to the new # one, if different. (Yes, people should just fix their scripts to use # BUILDDIR, please file a bug if you need that variable to also be available # elsewhere other than hook script; given that, I won't do too fancy checks) if [ "$BUILDDIR" != '/tmp/buildd' ]; then if [ -h "$BUILDPLACE/tmp/buildd" ] && [ "$(readlink -f "$BUILDPLACE/tmp/buildd")" = "${BUILDPLACE}$BUILDDIR" ]; then rm "$BUILDPLACE/tmp/buildd" fi if [ -d "$BUILDPLACE/tmp/buildd" ] && [ ! -h "$BUILDPLACE/tmp/buildd" ]; then if [ ! "$(ls -A "$BUILDPLACE/tmp/buildd" 2>&1)" ]; then # empty /tmp/buildd, let's change it to a symlink to BUILDDIR rmdir "$BUILDPLACE/tmp/buildd" ln -rs "${BUILDPLACE}$BUILDDIR" "$BUILDPLACE/tmp/buildd" else log "W: Could not create compatibility symlink because /tmp/buildd is not empty" fi elif [ ! -e "$BUILDPLACE/tmp/buildd" ]; then ln -rs "${BUILDPLACE}$BUILDDIR" "$BUILDPLACE/tmp/buildd" else log "W: Could not create compatibility symlink because /tmp/buildd exists and it is not a directory" fi fi executehooks "H" } function echobacktime () { log.i "Current time: $(date)" log.i "pbuilder-time-stamp: $(date +%s)" } function recover_aptcache() { local doit # recover the aptcache archive if [ -n "$APTCACHE" ]; then if [ "$APTCACHEHARDLINK" = "yes" ]; then doit=ln else doit=cp fi log.i "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 log.i "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 log.i "new cache content '$(basename "$A")' added" $doit "$A" "$APTCACHE/" || true fi done fi } function create_basetgz() { # don't pack the hooks in unloadhooks # create base.tgz ( if ! cd "$BUILDPLACE"; then log "E: unexpected error in chdir to $BUILDPLACE" exit 1; fi while test -f "${BASETGZ}.tmp"; do log.i "Someone else has lock over ${BASETGZ}.tmp, waiting" sleep 10s done log.i "creating base tarball [${BASETGZ}]" if [ -h "$BUILDPLACE/tmp/buildd" ] && [ "$(readlink -f "$BUILDPLACE/tmp/buildd")" = "${BUILDPLACE}$BUILDDIR" ]; then rm "$BUILDPLACE/tmp/buildd" fi if ! tar -c --use-compress-program "$COMPRESSPROG" -f "${BASETGZ}.tmp" ./* ; then log "E: failed building base tarball" rm -f "${BASETGZ}.tmp" exit 1; fi mv "${BASETGZ}.tmp" "${BASETGZ}" ) } function copyinputfile() { # copy files to inside chroot, copy all files specified by INPUTFILE[] parameter. TARGETDIR="$1" if [ -z "$TARGETDIR" ]; then log "E: Unexpected error in copyinputfile" exit 1x fi if [ -n "$INPUTFILE" ]; then log.i "copy ${INPUTFILE[*]} to target directory" cp "${INPUTFILE[@]}" "${TARGETDIR}" fi } # all trap hooks that should lead to 'exit'; and error exit. function cleanbuildplace_trap () { trap "" sigpipe sighup set +e cleanbuildplace trap - exit sighup sigpipe exit 1 } function saveaptcache_umountproc_cleanbuildplace_trap () { trap "" sigpipe sighup set +e saveaptcache_umountproc_cleanbuildplace trap - exit sighup sigpipe exit 1 } function umountproc_cleanbuildplace_trap () { trap "" sigpipe sighup set +e umountproc_cleanbuildplace trap - exit sighup sigpipe exit 1 } function umountproc_trap () { trap "" sigpipe sighup set +e umountproc trap - exit sighup sigpipe exit 1 } # copy to .. if target directory is not .. function conditional_cp_a() { local source_file="$1" # NOTE: target_dir must not end with /, which is usually the case # with 'readlink -f' result, which BUILDRESULT usually is. local target_dir="$2" # For testability, make cp overridable. local cp="${3:-cp}" # $PWD should end with non-'/', so dirname should give us the parent dir. local parent_dir=$(dirname "$PWD") if [ "${parent_dir}" != "${target_dir}" ]; then "$cp" -a "$source_file" "$target_dir" else log.i "file ${source_file} is already in target, not copying." fi } function add_additional_aptkeyrings() { # To support package verification inside the repository we may have to import # additional keys. for KEY in "${APTKEYRINGS[@]}"; do log.i "adding apt key file ${KEY}." $CHROOTEXEC /usr/bin/apt-key add - < "${KEY}" > /dev/null done } #Setting environmental variables that are really required: #required for some packages to install... export LANG=C export LC_ALL=C