summaryrefslogtreecommitdiff
path: root/src/chroot-tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/chroot-tools')
-rw-r--r--src/chroot-tools/.gitignore5
-rw-r--r--src/chroot-tools/Makefile44
-rwxr-xr-xsrc/chroot-tools/chcleanup102
-rw-r--r--src/chroot-tools/chroot.conf13
-rwxr-xr-xsrc/chroot-tools/distcc-tool244
-rw-r--r--src/chroot-tools/hooks-chcleanup.sh16
-rw-r--r--src/chroot-tools/hooks-check.sh39
-rw-r--r--src/chroot-tools/hooks-distcc.sh87
-rwxr-xr-xsrc/chroot-tools/librechroot325
-rwxr-xr-xsrc/chroot-tools/libremakepkg249
-rw-r--r--src/chroot-tools/makechrootpkg.sh.patch303
11 files changed, 1427 insertions, 0 deletions
diff --git a/src/chroot-tools/.gitignore b/src/chroot-tools/.gitignore
new file mode 100644
index 0000000..80e1000
--- /dev/null
+++ b/src/chroot-tools/.gitignore
@@ -0,0 +1,5 @@
+makechrootpkg.sh*
+!makechrootpkg.sh.patch
+
+arch-nspawn*
+mkarchroot*
diff --git a/src/chroot-tools/Makefile b/src/chroot-tools/Makefile
new file mode 100644
index 0000000..0540636
--- /dev/null
+++ b/src/chroot-tools/Makefile
@@ -0,0 +1,44 @@
+# These files are coming from devtools
+copy_files = makechrootpkg.sh.in mkarchroot.in arch-nspawn.in
+# These are programs that we will use internally, but shouldn't be in PATH
+libexecs = mkarchroot arch-nspawn distcc-tool chcleanup
+no-progs = $(libexecs)
+# These are the shell libraries we will use
+libs = makechrootpkg.sh $(wildcard hooks-*.sh)
+
+pkglibexecdir = $(libexecdir)/libretools/chroot
+clean_files = makechrootpkg.sh.ugly* *~
+include ../../common.mk
+
+# Usage: $(call indent,FILENAME)
+# Command to auto-indent a file.
+indent = emacs --batch $1 \
+ --eval '(setq sh-basic-offset 8)' \
+ --eval '(indent-region (point-min) (point-max) nil)' \
+ -f save-buffer &>/dev/null
+
+# makechrootpkg.sh is special, we patch it and do fancy stuff
+# The flow is:
+# $(devtoolsdir)/*.in -> *.sh.in + *.sh.patch -> *.sh.ugly -> *.sh
+
+makechrootpkg.sh.in: %.sh.in: $(devtoolsdir)/%.in
+ cp $< $@
+makechrootpkg.sh.ugly: %.ugly: %.in %.patch Makefile
+ cp $*.in $@
+ @echo 'PATCH $@ $*.patch'; patch $@ $*.patch || { rm -f -- '$@'; false; }
+makechrootpkg.sh: %: %.ugly Makefile
+ @echo 'EDIT < $< > $@'; $(edit) <'$<' >'$@' || { rm -f -- '$@'; false; }
+ @echo 'INDENT $@'; $(call indent,$@) || { rm -f -- '$@'; false; }
+
+mkarchroot: mkarchroot.in Makefile
+ @echo '< $< M4_EDIT | SED > $@'
+ @<'$<' $(edit) | sed 's|arch-nspawn|$$(librelib chroot/&)|' >'$@' || { rm -f -- '$@'; false; }
+ @echo 'CHMOD $<'; chmod 755 "$@" || { rm -f -- '$@'; false; }
+
+archroot: %: %.in Makefile
+ @echo "GEN $@"
+ @$(edit) <"$<" >"$@" || { rm -f -- '$@'; false; }
+ @chmod 755 "$@" || { rm -f -- '$@'; false; }
+
+distcc-tool.pot: distcc-tool
+ xgettext --omit-header -i --from-code=UTF-8 -L shell --keyword={error,errusage} -o $@ $<
diff --git a/src/chroot-tools/chcleanup b/src/chroot-tools/chcleanup
new file mode 100755
index 0000000..a43065b
--- /dev/null
+++ b/src/chroot-tools/chcleanup
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+set -eE
+# (c) Nicolás Reynolds <fauno@parabola.nu>
+# Released under GPLv3
+#
+# Performs chroot cleanup smartly, it only removes the unneeded packages or
+# leaves you with a cleansystem
+#
+# See: HOOKPREBUILD
+
+DRYRUN=${DRYRUN:-false}
+
+################################################################################
+# Define these here to avoid having dependencies on outside files
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+
+plain() {
+ local mesg="$(_ "$1")"; shift
+ printf "${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+msg() {
+ local mesg="$(_ "$1")"; shift
+ printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+msg2() {
+ local mesg="$(_ "$1")"; shift
+ printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+warning() {
+ local mesg="$(_ "$1")"; shift
+ printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+error() {
+ local mesg="$(_ "$1")"; shift
+ printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+################################################################################
+
+if [[ ! -f /.arch-chroot ]] && ! ${DRYRUN}; then
+ error "(chcleanup): Must be run inside of a chroot"
+ exit 1
+fi
+
+source /etc/libretools.d/chroot.conf
+# If we're running makepkg
+if [ -f PKGBUILD ]; then
+ export CARCH="$(. /etc/makepkg.conf; printf '%s' "$CARCH")"
+ source PKGBUILD
+ CHROOTEXTRAPKG+=("${depends[@]}"
+ "${makedepends[@]}"
+ "${checkdepends[@]}")
+fi
+
+msg "Cleaning chroot..."
+msg2 "Fetching updated package lists..."
+pacman -Sy || {
+ warning "There was an error updating package lists, ignoring."
+}
+
+# Setup the temporary directory
+TEMPDIR="$(mktemp --tmpdir -d $(basename $0).XXXXX)"
+trap "rm -rf '$TEMPDIR'" EXIT
+
+cp -a /var/lib/pacman/sync "${TEMPDIR}/"
+pkglist="${TEMPDIR}"/pkglist.txt
+
+# Get the full list of packages needed by dependencies, including the base system
+msg2 "Creating a full list of packages..."
+pacman -b "${TEMPDIR}" \
+ -Sp --print-format "%n" base-devel "${CHROOTEXTRAPKG[@]}" >"$pkglist" || {
+ ret=$?
+ error "Could not create a full list of packages, exiting."
+ plain "This is likely caused by a dependency that could not be found."
+ exit $ret
+}
+
+# Diff installed packages against a clean chroot then remove leftovers
+packages=($(comm -23 <(pacman -Qq | sort -u) \
+ <(sort -u "${pkglist}")))
+
+if [[ ${#packages[@]} = 0 ]]; then
+ msg2 "No packages to remove"
+else
+ msg2 "Removing %d packages" ${#packages[@]}
+
+ if ${DRYRUN}; then
+ echo "${packages[*]}"
+ else
+ # Only remove leftovers, -Rcs removes too much
+ pacman --noconfirm -Rn "${packages[@]}"
+ fi
+fi
diff --git a/src/chroot-tools/chroot.conf b/src/chroot-tools/chroot.conf
new file mode 100644
index 0000000..3b3c445
--- /dev/null
+++ b/src/chroot-tools/chroot.conf
@@ -0,0 +1,13 @@
+# The full path to the chroot is
+# $CHROOTDIR/$CHROOT/$COPY
+# where $COPY is set at runtime.
+# See `librechroot help` for details.
+CHROOTDIR=/var/lib/archbuild
+CHROOT=default
+
+# Extra packages to have installed on the chroot.
+# This is in addition to CHROOTPKG=(base-devel)
+CHROOTEXTRAPKG=()
+#CHROOTEXTRAPKG+=(distcc-nozeroconf socat) # for BUILDENV+=(distcc)
+#CHROOTEXTRAPKG+=(ccache) # for BUILDENV+=(ccache)
+#CHROOTEXTRAPKG+=(libretools) # for running libremakepkg inside the chroot
diff --git a/src/chroot-tools/distcc-tool b/src/chroot-tools/distcc-tool
new file mode 100755
index 0000000..7633029
--- /dev/null
+++ b/src/chroot-tools/distcc-tool
@@ -0,0 +1,244 @@
+#!/usr/bin/env bash
+# -*- tab-width: 4; sh-basic-offset: 4 -*-
+# distcc-tool
+
+# Copyright 2013 Luke Shumaker
+#
+# This file is part of Parabola.
+#
+# Parabola 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 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola 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 Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# This program has very few dependencies:
+# - bash: I don't know what version, I use fairly modern features
+# - socat
+# - cat: any version
+# - rm: any version
+# - sed: any version
+# On Parabola, this means the packages:
+# bash, coreutils, sed, socat
+
+if ! type gettext &>/dev/null; then
+ gettext() { echo "$@"; }
+fi
+
+panic() {
+ echo "$(gettext 'panic: malformed call to internal function')" >&2
+ exit 1
+}
+
+error() {
+ mesg="$(gettext "$1")"; shift
+ printf "$(gettext 'ERROR:') $mesg\n" "$@" >&2
+ exit 1
+}
+
+print() {
+ local mesg=$1
+ shift
+ printf -- "$(gettext "$mesg")\n" "$@"
+}
+
+usage() {
+ print "Usage: $0 COMMAND [COMMAND-ARGS]"
+ print "Tool for using distcc within a networkless chroot"
+ echo
+ print "Commands:"
+ print ' help print this message'
+ print ' odaemon CHROOTPATH daemon to run outside of the chroot'
+ print ' idaemon DISTCC_HOSTS daemon to run inside of the chroot'
+ print ' rewrite DISTCC_HOSTS prints a rewritten version of DISTCC_HOSTS'
+ print ' client HOST PORT connects stdio to TCP:$HOST:$PORT'
+ print 'Commands: for internal use'
+ print ' server counterpart to client; spawned by odaemon'
+}
+
+errusage() {
+ if [[ $# -gt 0 ]]; then
+ fmt="$(gettext "$1")"; shift
+ printf "$(gettext 'ERROR:') $fmt\n" "$@" >&2
+ fi
+ usage >&2
+ exit 1
+}
+
+main() {
+ local cmd=$1
+ shift
+ case "$cmd" in
+ help)
+ [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ usage;;
+ odaemon|idaemon|rewrite)
+ [[ $# -eq 1 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ client)
+ [[ $# -eq 2 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ server)
+ [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ *) errusage 'unknown subcommand: %s' "$cmd";;
+ esac
+}
+
+################################################################################
+# DISTCC_HOSTS parser #
+################################################################################
+
+# usage: parse_DISTCC_HOSTS true|false DISTCC_HOSTS
+# parses DISTCC_HOSTS and:
+# $1==true : It sets up port forwarding for inside the choot, sleep forever
+# $1==false: Prints a modified version of DISTCC_HOSTS that uses the forwarded
+# ports that were set up when $1==true.
+parse_DISTCC_HOSTS() {
+ { [[ $# -eq 2 ]] && { [[ $1 == true ]] || [[ $1 == false ]]; }; } || panic
+ local forward_ports=$1
+ local DISTCC_HOSTS=$2
+
+ local pids=() # child pids
+ local newhosts=()
+ local newport=8000 # next port to be used for port forwarding
+
+ # This is based on the grammar specified in distcc(1)
+ local HOSTSPEC
+ for HOSTSPEC in $(sed 's/#.*//' <<<"$DISTCC_HOSTS"); do
+ case "$HOSTSPEC" in
+ # LOCAL_HOST
+ localhost|localhost/*|--localslots=*|--localslots_cpp=*)
+ # "localhost" runs commands directly, not talking to distccd at
+ # localhost, use an IP or real hostname for that.
+ # So, just pass these through.
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # SSH_HOST
+ *@*)
+ # SSH_HOST doesn't allow custom port numbers, and even if it
+ # did, ssh would complain about MITM. Instead, we'll count on
+ # ssh ProxyCommand being configured to used `client`.
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # GLOBAL_OPTION
+ --*)
+ # pass these through
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # ZEROCONF
+ +zeroconf)
+ error "%s does not support the +zeroconf option" "$0"
+ exit 1
+ ;;
+ # TCP_HOST or OLDSTYLE_TCP_HOST
+ *)
+ declare HOSTID= PORT= LIMIT= OPTIONS=
+ if [[ $HOSTSPEC =~ ^([^:/]+)(:([0-9]+))?(/([0-9]+))?(,.*)?$ ]]; then
+ # TCP_HOST
+ HOSTID=${BASH_REMATCH[1]}
+ PORT=${BASH_REMATCH[3]}
+ LIMIT=${BASH_REMATCH[5]}
+ OPTIONS=${BASH_REMATCH[6]}
+ elif [[ $HOSTSPEC =~ ^([^:/]+)(/([0-9]+))?(:([0-9]+))?(,.*)?$ ]]; then
+ # OLDSTYLE_TCP_HOST
+ HOSTID=${BASH_REMATCH[1]}
+ LIMIT=${BASH_REMATCH[3]}
+ PORT=${BASH_REMATCH[5]}
+ OPTIONS=${BASH_REMATCH[6]}
+ else
+ error "Could not parse HOSTSPEC: %s" "$HOSTSPEC"
+ fi
+
+ # set up port forwaring
+ if $forward_ports; then
+ socat TCP-LISTEN:${newport},fork SYSTEM:"$0 client $HOSTID ${PORT:-3632}" &
+ pids+=($!)
+ fi
+
+ # add the forwarded port
+ local newhost="127.0.0.1:$newport"
+ [[ -z $LIMIT ]] || newhost+="/$LIMIT"
+ [[ -z $OPTIONS ]] || newhost+="$OPTIONS"
+ newhosts+=("$newhost")
+ : $((newport++))
+ ;;
+ esac
+ done
+ if $forward_ports; then
+ if [[ -z "${pids[*]}" ]]; then
+ # listen on port 8000, but immediatly close, just so that we are
+ # listening on something
+ socat TCP-LISTEN:${newport},fork SYSTEM:true &
+ pids+=($!)
+ fi
+ trap "kill -- ${pids[*]}" EXIT
+ wait "${pids[@]}"
+ else
+ printf '%s\n' "${newhosts[*]}"
+ fi
+}
+
+################################################################################
+# Port forwarding primitives #
+################################################################################
+
+# Usage: server
+# Reads "host port" from the first line of stdin, then connects stdio to the
+# specified TCP socket.
+server() {
+ [[ $# -eq 0 ]] || panic
+ while read -r host port; do
+ socat STDIO TCP:"$host:$port"
+ break
+ done
+}
+
+# Usage: client HOST PORT
+# For usage inside of a chroot. It talks through the UNIX-domain socket to an
+# instance of `server` outside, in order to connect stdio to the specified TCP
+# socket.
+client() {
+ [[ $# -eq 2 ]] || panic
+ local file=/socket
+ { printf '%s\n' "$*"; cat; } | socat UNIX-CONNECT:"$file" STDIO
+}
+
+################################################################################
+# High-level routines #
+################################################################################
+
+# Usage: odaemon CHROOTPATH
+# Listens on "$CHROOTPATH/socket" and spawns a `server` for each new connection.
+odaemon() {
+ [[ $# -eq 1 ]] || panic
+ local chrootpath=$1
+
+ umask 111
+ socat UNIX-LISTEN:"$chrootpath/socket",fork SYSTEM:"$0 server" &
+ trap "kill -- $!; rm -f '$chrootpath/socket'" EXIT
+ wait
+}
+
+# Usage: idaemon DISTCC_HOSTS
+# Sets things up inside of the chroot to forward distcc hosts out.
+idaemon() {
+ [[ $# -eq 1 ]] || panic
+ parse_DISTCC_HOSTS true "$1"
+}
+
+# Usage: rewrite DISTCC_HOSTS
+# Prints a modified version of DISTCC_HOSTS for inside the chroot.
+rewrite() {
+ [[ $# -eq 1 ]] || panic
+ parse_DISTCC_HOSTS false "$1"
+}
+
+main "$@"
diff --git a/src/chroot-tools/hooks-chcleanup.sh b/src/chroot-tools/hooks-chcleanup.sh
new file mode 100644
index 0000000..09e6dd9
--- /dev/null
+++ b/src/chroot-tools/hooks-chcleanup.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+set -euE
+
+hooks_pre_build+=("clean_chroot")
+
+clean_chroot() (
+ set +x
+ local copydir=$1
+ if $INCHROOT; then
+ cd /build
+ sudo -u nobody "$(librelib chroot/chcleanup)"
+ else
+ librechroot -l "$copydir" clean-pkgs
+ fi
+ r=$?; echo clean_chroot returning $r; return $r
+)
diff --git a/src/chroot-tools/hooks-check.sh b/src/chroot-tools/hooks-check.sh
new file mode 100644
index 0000000..e8120b8
--- /dev/null
+++ b/src/chroot-tools/hooks-check.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+set -euE
+
+hook_check_pkgbuild+=("check_pkgbuild_dependencies")
+check_pkgbuild_dependencies() {
+ local s=0
+ sudo -EH -u "$LIBREUSER" pkgbuild-check-nonfree -f || s=$?
+ case $s in
+ 0) :;;
+ 15) error "This PKGBUILD links to known unfree packages"; return 1;;
+ *) warning "pkgbuild-check-nonfree failed to run";;
+ esac
+}
+
+hook_check_pkgbuild+=("check_pkgbuild_license")
+check_pkgbuild_license() {
+ local s=0
+ sudo -EH -u "$LIBREUSER" pkgbuild-check-licenses -f || s=$?
+ for i in 1 2 4; do
+ if [[ $i -eq $(($s & $i)) ]]; then
+ case $i in
+ 1) warning "pkgbuild-check-licenses encountered an error";;
+ 2) warning "This PKGBUILD has an uncommon license";;
+ 4) error "This PKGBUILD has a known nonfree license"; ret=1;;
+ esac
+ fi
+ done
+}
+
+#hook_check_pkgbuild+=("check_pkgbuild_namcap")
+check_pkgbuild_namcap() {
+ sudo -EH -u "$LIBREUSER" namcap PKGBUILD
+}
+
+#hook_check_pkg+=("check_pkg")
+check_pkg() {
+ # TODO
+ :
+}
diff --git a/src/chroot-tools/hooks-distcc.sh b/src/chroot-tools/hooks-distcc.sh
new file mode 100644
index 0000000..9e42242
--- /dev/null
+++ b/src/chroot-tools/hooks-distcc.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+set -euE
+
+hook_pre_build+=("distcc_start")
+hook_post_build+=("distcc_stop")
+
+_distcc_check() {
+ local copydir=$1
+ local home=$2
+
+ local files=(
+ "$copydir/bin/distcc-tool"
+ "$copydir/run/distcc-tool.pid"
+ "$home/.makepkg.conf"
+ "$home/.ssh/config"
+ )
+
+ local file_err=false
+ for files in "${files[@]}"; do
+ if [[ -f $file ]]; then
+ file_err=true
+ error "Auto-generated file already exists, remove it: %s" "$file"
+ fi
+ done
+ if $file_err; then
+ exit 1
+ fi
+}
+
+distcc_start() {
+ local copydir=$1
+
+ # Because /{,usr/}{,s}bin are all symlinked together for
+ # fileystem 2013.05-2 and up, I can take shortcuts when checking for
+ # existance of programs.
+ if $NONET && [[ -f "$copydir/bin/socat" && -f "$copydir/bin/distcc" ]]; then
+ local home
+ if $INCHROOT; then
+ home=$LIBREHOME
+ else
+ home="$copydir/build"
+ fi
+
+ _distcc_check
+
+ local _distcc_tool=$(librelib chroot/distcc-tool)
+ install -m755 "$_distcc_tool" "$copydir/bin/distcc-tool"
+
+ mkdir -p "$home/.ssh"
+
+ printf '%s\n' \
+ '/bin/distcc-tool idaemon "$DISTCC_HOSTS" &' \
+ 'DISTCC_HOSTS="$(/bin/distcc-tool rewrite "$DISTCC_HOSTS")"' \
+ > "$home/.makepkg.conf"
+
+ printf '%s\n' \
+ 'Host *' \
+ ' ProxyCommand /bin/distcc-tool client %h %p' \
+ > "$home/.ssh/config"
+
+ "$_distcc_tool" odaemon "$copydir" &
+ echo $! > "$copydir/run/distcc-tool.pid"
+ fi
+}
+
+distcc_stop() {
+ local copydir=$1
+
+ local home
+ if $INCHROOT; then
+ home=$LIBREHOME
+ else
+ home="$copydir/build"
+ fi
+
+ if [[ -f "$copydir/run/distcc-tool.pid" ]]; then
+
+ odaemon=$(cat "$copydir/distcc-tool.pid")
+ kill -- "$odaemon"
+
+ rm -f -- \
+ "$home/.makepkg.conf" \
+ "$home/.ssh/config" \
+ "$copydir/bin/distcc-tool" \
+ "$copydir/run/distcc-tool.pid"
+ fi
+}
diff --git a/src/chroot-tools/librechroot b/src/chroot-tools/librechroot
new file mode 100755
index 0000000..0b3ad43
--- /dev/null
+++ b/src/chroot-tools/librechroot
@@ -0,0 +1,325 @@
+#!/usr/bin/env bash
+set -euE
+# librechroot
+
+# Copyright 2010 Nicolás Reynolds
+# Copyright 2011 Joshua Haase
+# Copyright 2012-2013 Luke Shumaker
+#
+# This file is part of Parabola.
+#
+# Parabola 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 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola 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 Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# HACKING: if a command is added or removed, it must be changed in 3 places:
+# - the usage() text
+# - the commands=() array
+# - the case statement in main()
+
+. $(librelib conf)
+load_files chroot
+
+. libremessages
+
+shopt -s nullglob
+umask 0022
+
+readonly _arch_nspawn=$(librelib chroot/arch-nspawn)
+readonly _mkarchroot=$(librelib chroot/mkarchroot)
+readonly _makechrootpkg=$(librelib chroot/makechrootpkg.sh)
+
+# Because the makechrootpkg.sh library functions don't work with -euE
+_makechrootpkg() (
+ set +euE
+ . "$_makechrootpkg"
+ "$@"
+)
+
+# Usage: make_empty_repo $copydir
+make_empty_repo() {
+ local copydir=$1
+ mkdir -p "${copydir}/repo"
+ bsdtar -czf "${copydir}/repo/repo.db.tar.gz" -T /dev/null
+ ln -s "repo.db.tar.gz" "${copydir}/repo/repo.db"
+}
+
+# Usage: chroot_add_to_local_repo $copydir $pkgfiles...
+chroot_add_to_local_repo() {
+ local copydir=$1; shift
+ mkdir -p "$copydir/repo"
+ local pkgfile
+ for pkgfile in "$@"; do
+ cp "$pkgfile" "$copydir/repo"
+ pushd "$copydir/repo" >/dev/null
+ repo-add repo.db.tar.gz "${pkgfile##*/}"
+ popd >/dev/null
+ done
+}
+
+usage() {
+ eval "$(calculate_directories)"
+ print "Usage: %s [OPTIONS] COMMAND [ARGS...]" "${0##*/}"
+ print 'Interacts with an archroot (arch chroot).'
+ echo
+ prose 'This is configured with `chroot.conf`, either in
+ `/etc/libretools.d/`, or `$XDG_CONFIG_HOME/libretools/`.
+ The variables you may set are $CHROOTDIR, $CHROOT, and
+ $CHROOTEXTRAPKG.'
+ echo
+ prose 'There may be multiple chroots; they are stored in $CHROOTDIR.'
+ echo
+ prose 'Each chroot is named; the default is configured with $CHROOT.'
+ echo
+ prose 'Each named chroot has a master clean copy (named `root`), and any
+ number of other named copies; the copy used by default is the
+ current username (or $SUDO_USER, or `copy` if root).'
+ echo
+ prose 'The full path to the chroot copy is "$CHROOTDIR/$CHROOT/$COPY",
+ unless the copy name is manually specified as an absolute path,
+ in which case, that path is used.'
+ echo
+ prose 'The current settings for the above varibles are:'
+ printf ' CHROOTDIR : %s\n' "${CHROOTDIR:-$(_ 'ERROR: NO SETTING')}"
+ printf ' CHROOT : %s\n' "${CHROOT:-$(_ 'ERROR: NO SETTING')}"
+ printf ' COPY : %s\n' "$COPY"
+ printf ' rootdir : %s\n' "${rootdir:-$(_ 'ERROR')}"
+ printf ' copydir : %s\n' "${copydir:-$(_ 'ERROR')}"
+ echo
+ prose 'If the chroot, or copy does not exist, it will be created
+ automatically. A chroot by default contains the packages in the
+ group "base-devel", and any packages named in $CHROOTEXTRAPKG.
+ Unless the `-C` or `-M` flags are used, the configuration files
+ that this program installs are the stock versions supplied in the
+ packages, not the versions from your host system. Other tools
+ (such as libremakepkg) may alter the configuration.'
+ echo
+ prose 'This command will make the following configuration changes in the
+ chroot:'
+ bullet 'overwrite `/etc/libretools.d/chroot.conf`'
+ bullet 'overwrite `/etc/pacman.d/mirrorlist`'
+ bullet 'set `CacheDir` in `/etc/pacman.conf`'
+ prose 'If a new `pacman.conf` is inserted with the `-C` flag, the change
+ is made after the file is copied in; the `-C` flag doesn'\''t
+ stop the change from being effective.'
+ echo
+ prose 'Creating a copy, deleting a copy, or syncing a copy can be fairly
+ slow; but are very fast if $CHROOTDIR is on a btrfs partition.'
+ echo
+ print 'Options:'
+ flag "-n <$(_ CHROOT)>" 'Name of the chroot to use'
+ flag "-l <$(_ COPY)>" 'Name of, or absolute path to, the copy to use'
+ flag '-N' 'Disable networking in the chroot'
+ flag "-C <$(_ FILE)>" 'Copy this file to `$copydir/etc/pacman.conf`'
+ flag "-M <$(_ FILE)>" 'Copy this file to `$copydir/etc/makepkg.conf`'
+ flag "-w <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read/write'
+ flag "-r <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read-only'
+ echo
+ print 'Commands:'
+ print ' Create/copy/delete:'
+ flag 'noop|make' 'Do not do anything, but still creates the chroot
+ copy if it does not exist'
+ flag 'sync' 'Sync the copy with the clean (`root`) copy'
+ flag 'delete' 'Delete the chroot copy'
+ print ' Dealing with packages:'
+ flag "install-file $(_ FILES...)" 'Like `pacman -U FILES...`'
+ flag "install-name $(_ NAMES...)" 'Like `pacman -S NAMES...`'
+ flag 'update' 'Like `pacman -Syu`'
+ flag 'clean-pkgs' 'Remove all packages from the chroot copy that
+ are not in base-devel, $CHROOTEXTRAPKG, or named
+ as a dependency in the file `/build/PKGBUILD` in
+ the chroot copy'
+ print ' Other:'
+ flag "run $(_ CMD...)" 'Run CMD in the chroot copy'
+ flag 'enter' 'Enter an interactive shell in the chroot copy'
+ flag 'clean-repo' 'Clean /repo in the chroot copy'
+ flag 'help' 'Show this message'
+}
+readonly commands=(
+ noop make sync delete
+ install-file install-name update clean-pkgs
+ run enter clean-repo help
+)
+
+# set $rootdir and $copydir; blank them on error
+calculate_directories() {
+ # Don't assume that CHROOTDIR or CHROOT are set,
+ # but assume that COPY is set.
+ local rootdir copydir
+
+ if [[ -n ${CHROOTDIR:-} ]] && [[ -n ${CHROOT:-} ]]; then
+ rootdir="${CHROOTDIR}/${CHROOT}/root"
+ else
+ rootdir=''
+ fi
+
+ if [[ ${COPY:0:1} = / ]]; then
+ copydir=$COPY
+ elif [[ -n ${CHROOTDIR:-} ]] && [[ -n ${CHROOT:-} ]]; then
+ copydir="${CHROOTDIR}/${CHROOT}/${COPY}"
+ else
+ copydir=''
+ fi
+
+ declare -p rootdir
+ declare -p copydir
+}
+
+arch_nspawn_flags=()
+sysd_nspawn_flags=()
+arch-nspawn() {
+ local copydir=$1; shift
+ set +u # if an array is empty, it counts as unbound
+ "$_arch_nspawn" "${arch_nspawn_flags[@]}" "$copydir" "${sysd_nspawn_flags[@]}" -- "$@"
+ set -u
+}
+
+# Globals: $CHROOTDIR, $CHROOT, $COPY, $rootdir and $copydir
+main() {
+ COPY=$LIBREUSER
+ [[ $COPY != root ]] || COPY=copy
+
+ local mode=enter
+ while getopts 'n:l:NC:M:w:r:' opt; do
+ case $opt in
+ n) CHROOT=$OPTARG;;
+ l) COPY=$OPTARG;;
+ N) sysd_nspawn_flags+=(--private-network);;
+ C|M) arch_nspawn_flags+=(-$opt "$OPTARG");;
+ w) sysd_nspawn_flags+=("--bind=$OPTARG");;
+ r) sysd_nspawn_flags+=("--bind-ro=$OPTARG");;
+ *) usage >/dev/stderr; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# -lt 1 ]]; then
+ error "Must specify a command"
+ usage >/dev/stderr
+ return 1
+ fi
+ mode=$1
+ if ! in_array "$mode" "${commands[@]}"; then
+ error "Unrecognized command: %s" "$mode"
+ usage >/dev/stderr
+ return 1
+ fi
+ shift
+
+ if [[ $mode == help ]]; then
+ usage
+ return 0
+ fi
+
+ check_vars chroot CHROOTDIR CHROOT
+ eval "$(calculate_directories)"
+
+ readonly LIBREUSER LIBREHOME
+ readonly CHROOTDIR CHROOT COPY
+ readonly rootdir copydir
+ readonly mode
+
+ ########################################################################
+
+ if (( EUID )); then
+ error "This program must be run as root."
+ return 1
+ fi
+
+ umask 0022
+
+ # Keep this lock as long as we are running
+ # Note that '9' is the same FD number as in mkarchroot et al.
+ lock 9 "$copydir.lock" \
+ "Waiting for existing lock on chroot copy to be released: [%s]" "$COPY"
+
+ if [[ ! -d $rootdir ]]; then
+ msg "Creating 'root' copy for chroot [%s]" "$CHROOT"
+ set +u # if an array is empty, it counts as unbound
+ "$_mkarchroot" "${arch_nspawn_flags[@]}" "$rootdir" base-devel
+ set -u
+ make_empty_repo "$rootdir"
+ fi
+
+ if [[ ! -d $copydir ]] || [[ $mode == sync ]]; then
+ msg "Syncing copy [%s] with root copy" "$COPY"
+ _makechrootpkg sync_chroot "$CHROOTDIR/$CHROOT" "$COPY"
+ fi
+
+ mkdir -p "$copydir/etc/libretools.d"
+ {
+ if [[ -n ${CHROOTEXTRAPKG[*]:-} ]]; then
+ declare -p CHROOTEXTRAPKG | sed -r 's/declare( -.)* //'
+ else
+ printf 'CHROOTEXTRAPKG=()\n'
+ fi
+ } > "$copydir"/etc/libretools.d/chroot.conf
+
+ if [[ $mode != delete ]]; then
+ # "touch" the chroot first
+ # this will
+ # - overwrite \`/etc/pacman.d/mirrorlist'"
+ # - set \`CacheDir' in \`/etc/pacman.conf'"
+ # - apply -C or -M flags
+ arch-nspawn "$copydir" true
+ arch_nspawn_flags=() # XXX dirty hack, don't apply -C or -M again
+ fi
+
+ ########################################################################
+
+ case "$mode" in
+ # Creat/copy/delete
+ noop|make|sync) :;;
+ delete)
+ if [[ -d $copydir ]]; then
+ _makechrootpkg delete_chroot "$copydir" "$COPY"
+ fi
+ ;;
+
+ # Dealing with packages
+ install-file)
+ _makechrootpkg install_packages "$copydir" "$@"
+ chroot_add_to_local_repo "$copydir" "$@"
+ ;;
+ install-name)
+ arch-nspawn "$copydir" pacman -Sy "$@"
+ ;;
+ update)
+ arch-nspawn "$copydir" pacman -Syu --noconfirm
+ ;;
+ clean-pkgs)
+ trap "rm -f '$copydir'/bin/chcleanup '$copydir'/chrootexec" EXIT
+ install -m755 "$(librelib chroot/chcleanup)" "$copydir/bin/chcleanup"
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'mkdir -p /build' \
+ 'cd /build' \
+ '/bin/chcleanup' \
+ > "$copydir/chrootexec"
+ chmod 755 "$copydir/chrootexec"
+ arch-nspawn "$copydir" /chrootexec
+ ;;
+
+ # Other
+ run)
+ arch-nspawn "$copydir" "$@"
+ ;;
+ enter)
+ arch-nspawn "$copydir" bash
+ ;;
+ clean-repo)
+ rm -rf "${copydir}"/repo/*
+ make_empty_repo "$copydir"
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/src/chroot-tools/libremakepkg b/src/chroot-tools/libremakepkg
new file mode 100755
index 0000000..a59315b
--- /dev/null
+++ b/src/chroot-tools/libremakepkg
@@ -0,0 +1,249 @@
+#!/usr/bin/env bash
+set -euE
+# libremakepkg
+
+# Copyright 2010-2011 Nicolás Reynolds
+# Copyright 2011 Joshua Ismael Haase Hernández
+# Copyright 2012-2013 Luke Shumaker
+#
+# This file is part of Parabola.
+#
+# Parabola 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 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola 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 Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. $(librelib conf)
+. $(librelib messages)
+. $(librelib chroot/makechrootpkg.sh)
+
+shopt -s nullglob
+umask 0022
+
+# Global variables:
+readonly INCHROOT=$([[ -f /.arch-chroot ]] && echo true || echo false)
+NONET=true # can be changed with the -N flag
+# {SRC,LOG,PKG}DEST set at runtime by makepkg.conf
+# MAKEFLAGS, PACKAGER set at runtime by makepkg.conf
+# LIBREUSER, LIBREHOME are set by conf.sh
+
+# Hooks ########################################################################
+
+hook_pre_build=(:)
+hook_post_build=(:)
+hook_check_pkgbuild=(:)
+hook_check_pkg=(:)
+. $(librelib chroot/hooks-chcleanup.sh)
+. $(librelib chroot/hooks-check.sh)
+. $(librelib chroot/hooks-distcc.sh)
+
+# Boring/mundane functions #####################################################
+
+# Usage: exit_copy $copydir $src_owner
+# End immediately, but copy log files out
+exit_copy() {
+ local copydir=$1
+ local src_owner=$2
+ if ! $INCHROOT; then
+ msg "Copying log and package files out of the chroot..."
+ move_products "$copydir" "$src_owner"
+ fi
+}
+
+# Usage; run_hook $hookname $args...
+run_hook() {
+ local hookname=$1; shift
+ local hookvar="hook_${hookname}[@]"
+ local fails=()
+ msg "Running hook: %s" "$hookname"
+ for hook in "${!hookvar}"; do
+ msg2 'hook: %s' "$hook"
+ "$hook" "$@" || { error "result: %s" $?; fails+=("$hook"); }
+ done
+ if [[ ${#fails[@]} -gt 0 ]]; then
+ error "Failure(s) in %s: %s" "$hookname" "${fails[*]}"
+ return 1
+ fi
+ return 0
+}
+
+# Usage: add_to_local_repo $copydir $pkgfiles...
+add_to_local_repo() {
+ local copydir=$1; shift
+ mkdir -p "$copydir/repo"
+ local pkgfile
+ for pkgfile in "$@"; do
+ cp "$pkgfile" "$copydir/repo"
+ pushd "$copydir/repo" >/dev/null
+ repo-add repo.db.tar.gz "${pkgfile##*/}"
+ popd >/dev/null
+ done
+}
+
+build() (
+ local copydir=$1; shift
+ local cmd=(/chrootbuild "$@")
+
+ run_hook pre_build "$copydir"
+ trap "run_hook post_build '$copydir'" EXIT
+
+ local netflag=''
+ if $INCHROOT; then
+ ! $NONET || netflag='-n'
+ unshare $netflag -- "${cmd[@]}"
+ else
+ ! $NONET || netflag='-N'
+ librechroot $netflag \
+ -r "$PWD:/startdir_host" \
+ -r "$SRCDEST:/srcdest_host" \
+ -l "$copydir" \
+ run "${cmd[@]}"
+ fi
+)
+
+# The main program #############################################################
+
+usage() {
+ print "Usage: %s [options] [-- makepkg args]" "${0##*/}"
+ print 'This program will build your package.'
+ echo
+ prose 'If run from outside of a chroot, command will make the following
+ configuration changes in the chroot:'
+ bullet 'whatever changes `librechroot` makes.'
+ bullet 'set `PKGDEST` and `SRCDEST` in `/etc/makepkg.conf`'
+ bullet 'set `PACKAGER` in `/etc/makepkg.conf` to reflect the value
+ outside of the chroot.'
+ bullet '(maybe) delete `/build/.makepkg.conf`'
+ bullet '(maybe) delete `/build/.ssh/config`'
+ prose 'If run from inside of a chroot, this command will:'
+ bullet '(maybe) delete `~/.makepkg.conf`'
+ bullet '(maybe) delete `~/.ssh/config`'
+ prose 'The above "maybe"s happen as part of the workarounds to make
+ distcc work in a network-less environment. They will happen if
+ both `socat` and `distcc` are installed in the chroot.'
+ echo
+ prose 'The `-n` and `-l` options behave identically to librechroot, see
+ the documentation there.'
+ echo
+ print 'Options:'
+ flag "-n <$(_ CHROOT)>" 'Name of the chroot to use'
+ flag "-l <$(_ COPY)>" 'Name of, or absolute path to, the chroot copy to use'
+ flag '-N' "Don't disable networking during build() and
+ package(). PLEASE don't use this unless you
+ have a special reason, its use is a violation
+ of Parabola policy."
+ flag '-R' 'Repackage contents of the package without rebuilding'
+ flag '-h' 'Show this message'
+}
+
+# Convenience method for use in option parsing
+err_chflag() {
+ local flag=$1
+ error 'The -%s flag does not make sense inside of a chroot' "$flag"
+ return 1
+}
+
+main() {
+ # Initial variable values ##############################################
+ local copy=$([[ $LIBREUSER == root ]] && echo copy || echo "$LIBREUSER")
+ local makepkg_args=(-s --noconfirm -L)
+ local repack=false
+ local chroot=''
+
+ # Parse command line options ###########################################
+ while getopts 'n:l:NRh' flag ; do
+ case "${flag}" in
+ n) if $INCHROOT; then err_chflag "$flag"; else chroot=$OPTARG; fi;;
+ l) if $INCHROOT; then err_chflag "$flag"; else copy=$OPTARG; fi;;
+ N) NONET=false;;
+ R) repack=true; makepkg_args+=(-R);;
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ # Pass all arguments after -- right to makepkg
+ makepkg_args+=("$@")
+
+ # Resolve the chroot path ##############################################
+ local copydir
+ if $INCHROOT; then
+ copydir='/'
+ else
+ load_files chroot
+ check_vars chroot CHROOTDIR CHROOT
+ [[ -z ${chroot} ]] || CHROOT=$chroot
+ if [[ ${copy:0:1} = / ]]; then
+ copydir=$copy
+ else
+ copydir="${CHROOTDIR}/${CHROOT}/${copy}"
+ fi
+ unset CHROOTDIR CHROOTEXTRAPKG
+ fi
+ unset chroot
+
+ # Quick sanity check ###################################################
+
+ if (( EUID )); then
+ error "This program must be run as root"
+ exit 1
+ fi
+
+ if [[ ! -f PKGBUILD ]]; then
+ # This is the message used by makepkg
+ error "PKGBUILD does not exist."
+ exit 1
+ fi
+
+ # Load makepkg configuration ###########################################
+ # Note that all of these are globals
+ SRCDEST="$(get_conf_makepkg SRCDEST "$PWD")"
+ PKGDEST="$(get_conf_makepkg PKGDEST "$PWD")"
+ LOGDEST="$(get_conf_makepkg LOGDEST "$PWD")"
+ mkdir -p "$SRCDEST" "$PKGDEST" "$LOGDEST"
+ MAKEFLAGS="$(get_conf_makepkg MAKEFLAGS '')"
+ PACKAGER="$(get_conf_makepkg PACKAGER '')"
+
+ # OK, we are starting now ##############################################
+
+ if $INCHROOT; then
+ lock 9 "/build/.lock" \
+ "Waiting for existing lock on build directory to be released"
+ else
+ # Obtain a lock on the chroot
+ lock 9 "$copydir.lock" \
+ "Waiting for existing lock on chroot copy to be released: [%s]" "$copy"
+ # Create the chroot if it does not exist
+ librechroot -n "$CHROOT" -l "$copy" make
+ fi
+
+ # Set target CARCH
+ # note that we waited until after locking/creating the chroot to do this
+ export CARCH="$(MAKEPKG_CONF=$copydir/etc/makepkg.conf get_conf_makepkg CARCH)"
+
+ # Pre-build
+ run_hook check_pkgbuild
+ download_sources "$copydir" "$LIBREUSER"
+ prepare_chroot "$copydir" "$LIBREHOME" "$repack" false
+ clean_chroot "$copydir"
+
+ # Build
+ trap "exit_copy '$copydir' '$LIBREUSER'" EXIT
+ warning 'Entering build...'
+ build "$copydir" "${makepkg_args[@]}"
+ # Post-build
+ warning 'Entering hook check_pkg...'
+ run_hook check_pkg
+ warning 'Entering add_to_local_repo ...'
+ add_to_local_repo "$copydir" "$copydir"/pkgdest/*.pkg.tar*
+}
+
+main "$@"
diff --git a/src/chroot-tools/makechrootpkg.sh.patch b/src/chroot-tools/makechrootpkg.sh.patch
new file mode 100644
index 0000000..f5b8ed7
--- /dev/null
+++ b/src/chroot-tools/makechrootpkg.sh.patch
@@ -0,0 +1,303 @@
+--- makechrootpkg.sh.in 2013-09-08 23:01:20.000000000 -0400
++++ makechrootpkg.sh.ugly 2013-09-09 15:43:06.000000000 -0400
+@@ -12,6 +12,7 @@
+
+ shopt -s nullglob
+
++init_variables() {
+ _makepkg_args=(-s --noconfirm -L --holdver)
+ makepkg_args=("${_makepkg_args[@]}")
+ repack=false
+@@ -26,9 +27,10 @@
+ declare -i ret=0
+
+ copy=$USER
+-[[ -n $SUDO_USER ]] && copy=$SUDO_USER
++[[ -n ${SUDO_USER:-} ]] && copy=$SUDO_USER
+ [[ -z "$copy" || $copy = root ]] && copy=copy
+ src_owner=${SUDO_USER:-$USER}
++}
+
+ usage() {
+ echo "Usage: ${0##*/} [options] -r <chrootdir> [--] [makepkg args]"
+@@ -62,6 +64,7 @@
+ exit 1
+ }
+
++parse_options_init() {
+ while getopts 'hcur:I:l:nT' arg; do
+ case "$arg" in
+ h) usage ;;
+@@ -86,9 +89,6 @@
+ [[ ! -d $chrootdir ]] && die "No chroot dir defined, or invalid path '%s'" "$passeddir"
+ [[ ! -d $chrootdir/root ]] && die "Missing chroot dir root directory. Try using: mkarchroot %s/root base-devel" "$chrootdir"
+
+-# Detect chrootdir filesystem type
+-chroottype=$(stat -f -c %T "$chrootdir")
+-
+ if [[ ${copy:0:1} = / ]]; then
+ copydir=$copy
+ else
+@@ -103,30 +103,47 @@
+ repack=true
+ fi
+
+-if [[ -n $SUDO_USER ]]; then
++if [[ -n ${SUDO_USER:-} ]]; then
+ USER_HOME=$(eval echo ~$SUDO_USER)
+ else
+ USER_HOME=$HOME
+ fi
++}
+
+ # {{{ functions
++# Usage: load_vars $makepkg_conf
++# Globals:
++# - SRCDEST
++# - LOGDEST
++# - PKGDEST
++# - MAKEFLAGS
++# - PACKAGER
+ load_vars() {
+ local makepkg_conf="$1" var
+
+ [[ -f $makepkg_conf ]] || return 1
+
+ for var in {SRC,PKG,LOG}DEST MAKEFLAGS PACKAGER; do
+- [[ -z ${!var} ]] && eval $(grep "^${var}=" "$makepkg_conf")
++ [[ -z ${!var:-} ]] && eval $(grep "^${var}=" "$makepkg_conf")
+ done
+
+ return 0
+ }
+
+-create_chroot() {
+- # Lock the chroot we want to use. We'll keep this lock until we exit.
+- lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy"
++# Usage: sync_chroot $CHROOTDIR/$CHROOT <$CHROOTCOPY|$copydir>
++sync_chroot() {
++ local chrootdir=$1
++ local copy=$2
++ local copydir=''
++ if [[ ${copy:0:1} = / ]]; then
++ copydir=$copy
++ else
++ copydir="$chrootdir/$copy"
++ fi
++
++ # Detect chrootdir filesystem type
++ local chroottype=$(stat -f -c %T "$chrootdir")
+
+- if [[ ! -d $copydir ]] || $clean_first; then
+ # Get a read lock on the root chroot to make
+ # sure we don't clone a half-updated chroot
+ slock 8 "$chrootdir/root.lock" "Locking clean chroot"
+@@ -147,10 +164,15 @@
+
+ # Drop the read lock again
+ lock_close 8
+- fi
+ }
+
+-clean_temporary() {
++# Usage: delete_chroot $copydir [$copy]
++delete_chroot() {
++ local copydir=$1
++ local copy=${2:-$copydir}
++ # Detect chrootdir filesystem type
++ local chroottype=$(stat -f -c %T "$copydir")
++
+ stat_busy "Removing temporary copy [%s]" "$copy"
+ if [[ "$chroottype" == btrfs ]]; then
+ btrfs subvolume delete "$copydir" >/dev/null ||
+@@ -166,9 +188,14 @@
+ stat_done
+ }
+
++# Usage: install_packages $copydir $pkgs...
+ install_packages() {
++ local copydir=$1
++ local install_pkgs=("${@:2}")
++ declare -i ret=0
+ local pkgname
+
++ local install_pkg
+ for install_pkg in "${install_pkgs[@]}"; do
+ pkgname="${install_pkg##*/}"
+ cp "$install_pkg" "$copydir/$pkgname"
+@@ -179,11 +206,19 @@
+ rm "$copydir/$pkgname"
+ done
+
+- # If there is no PKGBUILD we are done
+- [[ -f PKGBUILD ]] || exit $ret
++ return $ret
+ }
+
++# Usage: prepare_chroot $copydir $HOME $repack $run_namcap
++# Globals:
++# - MAKEFLAGS
++# - PACKAGER
+ prepare_chroot() {
++ local copydir=$1
++ local USER_HOME=$2
++ local repack=$3
++ local run_namcap=$4
++
+ $repack || rm -rf "$copydir/build"
+
+ mkdir -p "$copydir/build"
+@@ -217,12 +252,12 @@
+
+ chown -R nobody "$copydir"/{build,pkgdest,logdest,srcdest,startdir}
+
+- if [[ -n $MAKEFLAGS ]]; then
++ if [[ -n ${MAKEFLAGS:-} ]]; then
+ sed -i '/^MAKEFLAGS=/d' "$copydir/etc/makepkg.conf"
+ echo "MAKEFLAGS='${MAKEFLAGS}'" >> "$copydir/etc/makepkg.conf"
+ fi
+
+- if [[ -n $PACKAGER ]]; then
++ if [[ -n ${PACKAGER:-} ]]; then
+ sed -i '/^PACKAGER=/d' "$copydir/etc/makepkg.conf"
+ echo "PACKAGER='${PACKAGER}'" >> "$copydir/etc/makepkg.conf"
+ fi
+@@ -235,6 +270,14 @@
+ chmod 440 "$copydir/etc/sudoers.d/nobody-pacman"
+ fi
+
++ if ! grep -q '^\[repo\]' "$copydir/etc/pacman.conf"; then
++ cat >> "$copydir/etc/pacman.conf" <<EOF
++[repo]
++SigLevel = Optional TrustAll
++Server = file:///repo
++EOF
++ fi
++
+ # This is a little gross, but this way the script is recreated every time in the
+ # working copy
+ printf $'#!/bin/bash\n%s\n_chrootbuild %q "$@"' "$(declare -f _chrootbuild)" \
+@@ -242,13 +285,19 @@
+ chmod +x "$copydir/chrootbuild"
+ }
+
++# Usage: download_sources $copydir $src_owner
++# Globals:
++# - SRCDEST
+ download_sources() {
++ local copydir=$1
++ local src_owner=$2
++
+ local builddir="$(mktemp -d)"
+ chmod 1777 "$builddir"
+
+ # Ensure sources are downloaded
+- if [[ -n $SUDO_USER ]]; then
+- sudo -u $SUDO_USER env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \
++ if [[ $USER != $src_owner ]]; then
++ sudo -u $src_owner env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \
+ makepkg --config="$copydir/etc/makepkg.conf" --verifysource -o
+ else
+ ( export SRCDEST BUILDDIR="$builddir"
+@@ -258,7 +307,7 @@
+ (( $? != 0 )) && die "Could not download sources."
+
+ # Clean up garbage from verifysource
+- rm -rf $builddir
++ rm -rf "$builddir"
+ }
+
+ _chrootbuild() {
+@@ -295,6 +344,7 @@
+
+ # Safety check
+ if [[ ! -w PKGBUILD ]]; then
++ # XXX: internationalize this message
+ echo "Can't write to PKGBUILD!"
+ exit 1
+ fi
+@@ -312,12 +362,24 @@
+ exit 0
+ }
+
++# Usage: move_products $copydir $owner
++# Globals:
++# - PKGDEST
++# - LOGDEST
+ move_products() {
++ local copydir=$1
++ local src_owner=$2
++
++ local pkgfile
+ for pkgfile in "$copydir"/pkgdest/*; do
+ chown "$src_owner" "$pkgfile"
+ mv "$pkgfile" "$PKGDEST"
++ if [[ $PKGDEST != $PWD ]]; then
++ ln -sf "$PKGDEST/${pkgfile##*/}" .
++ fi
+ done
+
++ local l
+ for l in "$copydir"/logdest/*; do
+ chown "$src_owner" "$l"
+ mv "$l" "$LOGDEST"
+@@ -325,6 +387,10 @@
+ }
+ # }}}
+
++main() {
++init_variables
++parse_options_init
++
+ umask 0022
+
+ load_vars /etc/makepkg.conf
+@@ -335,27 +401,37 @@
+ [[ -d $SRCDEST ]] || SRCDEST=$PWD
+ [[ -d $LOGDEST ]] || LOGDEST=$PWD
+
+-create_chroot
++# Lock the chroot we want to use. We'll keep this lock until we exit.
++lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy"
++
++if [[ ! -d $copydir ]] || $clean_first; then
++ sync_chroot "$chrootdir" "$copy"
++fi
+
+ $update_first && arch-nspawn "$copydir" pacman -Syu --noconfirm
+
+-[[ -n ${install_pkgs[*]} ]] && install_packages
++if [[ -n ${install_pkgs[*]:-} ]]; then
++ install_packages "$copydir" "${install_pkgs[@]}"
++ ret=$?
++ # If there is no PKGBUILD we have done
++ [[ -f PKGBUILD ]] || exit $ret
++fi
+
+-prepare_chroot
++prepare_chroot "$copydir" "$USER_HOME" "$repack"
+
+-download_sources
++download_sources "$copydir" "$src_owner"
+
+ if arch-nspawn "$copydir" \
+ --bind-ro="$PWD:/startdir_host" \
+ --bind-ro="$SRCDEST:/srcdest_host" \
+ /chrootbuild "${makepkg_args[@]}"
+ then
+- move_products
++ move_products "$copydir" "$src_owner"
+ else
+ (( ret += 1 ))
+ fi
+
+-$temp_chroot && clean_temporary
++$temp_chroot && delete_chroot "$copydir" "$copy"
+
+ if (( ret != 0 )); then
+ if $temp_chroot; then
+@@ -366,3 +442,4 @@
+ else
+ true
+ fi
++}