index : devtools32 | |
Archlinux32 fork of devtools | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | makechrootpkg.in | 234 |
diff --git a/makechrootpkg.in b/makechrootpkg.in index c6ef240..7589737 100644 --- a/makechrootpkg.in +++ b/makechrootpkg.in @@ -11,6 +11,7 @@ # GNU General Public License for more details. m4_include(lib/common.sh) +m4_include(lib/archroot.sh) shopt -s nullglob @@ -20,13 +21,13 @@ init_variables() { repack=false update_first=false clean_first=false - install_pkg= run_namcap=false temp_chroot=false chrootdir= passeddir= - declare -a install_pkgs - declare -i ret=0 + makepkg_user= + declare -ga install_pkgs + declare -gi ret=0 bindmounts_ro=() bindmounts_rw=() @@ -51,6 +52,10 @@ usage() { echo 'command:' echo ' mkarchroot <chrootdir>/root base-devel' echo '' + echo 'This script reads {SRC,SRCPKG,PKG,LOG}DEST, MAKEFLAGS and PACKAGER' + echo 'from makepkg.conf(5), if those variables are not part of the' + echo 'environment.' + echo '' echo "Default makepkg args: ${default_makepkg_args[*]}" echo '' echo 'Flags:' @@ -68,6 +73,7 @@ usage() { echo " Default: $copy" echo '-n Run namcap on the package' echo '-T Build in a temporary directory' + echo '-U Run makepkg as a specified user' exit 1 } @@ -86,136 +92,43 @@ load_vars() { [[ -f $makepkg_conf ]] || return 1 for var in {SRC,SRCPKG,PKG,LOG}DEST MAKEFLAGS PACKAGER; do - [[ -z ${!var:-} ]] && eval $(grep "^${var}=" "$makepkg_conf") + [[ -z ${!var:-} ]] && eval "$(grep "^${var}=" "$makepkg_conf")" done return 0 } -# Usage: btrfs_subvolume_id $SUBVOLUME -btrfs_subvolume_id() ( - set -o pipefail - LC_ALL=C btrfs subvolume show "$1" | sed -n 's/^\tSubvolume ID:\s*//p' -) - -# Usage: btrfs_subvolume_list_all $FILEPATH -# -# Given $FILEPATH somewhere on a mounted btrfs filesystem, print the -# ID and full path of every subvolume on the filesystem, one per line -# in the format "$ID $PATH", where $PATH is relative to the top-level -# subvolume (which might not be what is mounted). -# -# BUG: Due to limitations in the `btrfs` tool, this will not correctly -# list subvolumes whose path contains a space. -btrfs_subvolume_list_all() ( - set -o pipefail - - local mountpoint all - mountpoint="$(df --output=target "$1" | sed 1d)" || return - # The output of `btrfs subvolume list -a` is a space-separated - # sequence of "key value key value...". Unfortunately both - # keys and values can contain space, and there's no escaping - # or indication of when this happens. So we assume - # 1. ID is the first column - # 2. That no key or value will contain " path" - # 3. That the "path" value does not contain a space. - all="$(LC_ALL=C btrfs subvolume list -a "$mountpoint" | sed -r 's|^ID ([0-9]+) .* path (<FS_TREE>/)?(\S*).*|\1 \3|')" || return - - # Sanity check the output - local id path - while read -r id path; do - # ID should be numeric - [[ "$id" =~ ^-?[0-9]+$ ]] || return - # While a path could countain a space, the above code - # doesn't support it; if there is space, then it means - # we got a line not matching the expected format. - [[ "$path" != *' '* ]] || return - done <<<"$all" - - printf '%s\n' "$all" -) - -# Usage: btrfs_subvolume_list $SUBVOLUME -# -# Assuming that $SUBVOLUME is a btrfs subvolume, list all child -# subvolumes; from most deeply nested to most shallowly nested. -# -# This is intended to be a sane version of `btrfs subvolume list`. -btrfs_subvolume_list() { - local subvolume=$1 - - local id all path subpath - id="$(btrfs_subvolume_id "$subvolume")" || return - all="$(btrfs_subvolume_list_all "$subvolume")" || return - path=$(awk -v id="$id" '$1 == id { sub($1 FS, ""); print }' <<<"$all") - while read -r id subpath; do - if [[ "$subpath" = "$path"/* ]]; then - printf '%s\n' "${subpath#"${path}/"}" - fi - done <<<"$all" | LC_ALL=C sort --reverse -} - -# Usage: btrfs_subvolume_delete $SUBVOLUME -# -# Assuming that $SUBVOLUME is a btrfs subvolume, delete it and all -# subvolumes below it. -# -# This is intended to be a recursive version of -# `btrfs subvolume delete`. -btrfs_subvolume_delete() { - local dir="$1" - - # We store the result as a variable because we want to see if - # btrfs_subvolume_list fails or succeeds before we start - # deleting things. (Then we have to work around the subshell - # trimming the trailing newlines.) - local subvolumes - subvolumes="$(btrfs_subvolume_list "$dir")" || return - [[ -z "$subvolumes" ]] || subvolumes+=$'\n' - - local subvolume - while read -r subvolume; do - btrfs subvolume delete "$dir/$subvolume" || return - done < <(printf '%s' "$subvolumes") - - btrfs subvolume delete "$dir" -} - -# Usage: sync_chroot $CHROOTDIR/$CHROOT <$CHROOTCOPY|$copydir> +# Usage: sync_chroot $rootdir $copydir [$copy] sync_chroot() { - local chrootdir=$1 - local copy=$2 - local copydir='' - if [[ ${copy:0:1} = / ]]; then - copydir=$copy - else - copydir="$chrootdir/$copy" - fi + local rootdir=$1 + local copydir=$2 + local copy=${3:-$2} - if [[ "$chrootdir/root" -ef "$copydir" ]]; then + if [[ "$rootdir" -ef "$copydir" ]]; then error 'Cannot sync copy with itself: %s' "$copydir" return 1 fi - # Detect chrootdir filesystem type - local chroottype=$(stat -f -c %T "$chrootdir") - # 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 [%s]" "$chrootdir/root" + slock 8 "$rootdir.lock" \ + "Locking clean chroot [%s]" "$rootdir" - stat_busy "Synchronizing chroot copy [%s] -> [%s]" "$chrootdir/root" "$copydir" - if [[ "$chroottype" == btrfs ]] && ! mountpoint -q "$copydir"; then - if [[ -d $copydir ]]; then - btrfs_subvolume_delete "$copydir" >/dev/null || + stat_busy "Synchronizing chroot copy [%s] -> [%s]" "$rootdir" "$copy" + if is_subvolume "$rootdir" && is_same_fs "$rootdir" "$(dirname -- "$copydir")" && ! mountpoint -q "$copydir"; then + if is_subvolume "$copydir"; then + subvolume_delete_recursive "$copydir" || die "Unable to delete subvolume %s" "$copydir" + else + # avoid change of filesystem in case of an umount failure + rm --recursive --force --one-file-system "$copydir" || + die "Unable to delete %s" "$copydir" fi - btrfs subvolume snapshot "$chrootdir/root" "$copydir" >/dev/null || + btrfs subvolume snapshot "$rootdir" "$copydir" >/dev/null || die "Unable to create subvolume %s" "$copydir" else mkdir -p "$copydir" - rsync -a --delete -q -W -x "$chrootdir/root/" "$copydir" + rsync -a --delete -q -W -x "$rootdir/" "$copydir" fi stat_done @@ -226,15 +139,14 @@ sync_chroot() { touch "$copydir" } -# Usage: delete_chroot $copydir +# Usage: delete_chroot $copydir [$copy] delete_chroot() { local copydir=$1 - # Detect chrootdir filesystem type - local chroottype=$(stat -f -c %T "$copydir") + local copy=${1:-$2} - stat_busy "Removing chroot copy [%s]" "$copydir" - if [[ "$chroottype" == btrfs ]] && ! mountpoint -q "$copydir"; then - btrfs_subvolume_delete "$copydir" >/dev/null || + stat_busy "Removing chroot copy [%s]" "$copy" + if is_subvolume "$copydir" && ! mountpoint -q "$copydir"; then + subvolume_delete_recursive "$copydir" || die "Unable to delete subvolume %s" "$copydir" else # avoid change of filesystem in case of an umount failure @@ -278,8 +190,9 @@ prepare_chroot() { $repack || rm -rf "$copydir/build" - local builduser_uid="${SUDO_UID:-$UID}" - local builduser_gid="$(id -g "$builduser_uid")" + local builduser_uid builduser_gid + builduser_uid="${SUDO_UID:-$UID}" + builduser_gid="$(id -g "$builduser_uid")" local install="install -o $builduser_uid -g $builduser_gid" local x @@ -287,8 +200,8 @@ prepare_chroot() { # which we might not be able to load (i.e. when building i686 packages on # an x86_64 host). sed -e '/^builduser:/d' -i "$copydir"/etc/{passwd,group} - printf >>"$copydir/etc/group" 'builduser:x:%d:\n' $builduser_gid - printf >>"$copydir/etc/passwd" 'builduser:x:%d:%d:builduser:/build:/bin/bash\n' $builduser_uid $builduser_gid + printf >>"$copydir/etc/group" 'builduser:x:%d:\n' "$builduser_gid" + printf >>"$copydir/etc/passwd" 'builduser:x:%d:%d:builduser:/build:/bin/bash\n' "$builduser_uid" "$builduser_gid" $install -d "$copydir"/{build,build/.gnupg,startdir,{pkg,srcpkg,src,log}dest} @@ -306,13 +219,13 @@ prepare_chroot() { done cat > "$copydir/etc/sudoers.d/builduser-pacman" <<EOF -Defaults env_keep += "HOME" builduser ALL = NOPASSWD: /usr/bin/pacman EOF chmod 440 "$copydir/etc/sudoers.d/builduser-pacman" if ! grep -q '^\[repo\]' "$copydir/etc/pacman.conf"; then - local line=$(grep -n '^\[' "$copydir/etc/pacman.conf" |grep -Fv ':[options]'|sed 's/:.*//;1q') + local line + line=$(grep -n '^\[' "$copydir/etc/pacman.conf" |grep -Fv ':[options]'|sed 's/:.*//;1q') local ins='[repo] SigLevel = Optional TrustAll Server = file:///repo @@ -347,12 +260,36 @@ Server = file:///repo # These functions aren't run in makechrootpkg, # so no global variables _chrootprepare() { + # shellcheck source=/dev/null . /etc/profile + # Beware, there are some stupid arbitrary rules on how you can + # use "$" in arguments to commands with "sudo -i". ${foo} or + # ${1} is OK, but $foo or $1 isn't. + # https://bugzilla.sudo.ws/show_bug.cgi?id=765 sudo -iu builduser bash -c 'cd /startdir; makepkg "$@" --nobuild' -bash "$@" } _chrootbuild() { + # shellcheck source=/dev/null . /etc/profile + local srcext + srcext="$( + # shellcheck source=makepkg-x86_64.conf + . /etc/makepkg.conf || exit + # shellcheck source=PKGBUILD.proto + . /startdir/PKGBUILD || exit + if [ "$arch" = any ]; then + pkgarch=any + else + pkgarch=$CARCH + fi + printf '%s\n' "-$pkgarch$SRCEXT" + )" || return + # Beware, there are some stupid arbitrary rules on how you can + # use "$" in arguments to commands with "sudo -i". ${foo} or + # ${1} is OK, but $foo or $1 isn't. + # https://bugzilla.sudo.ws/show_bug.cgi?id=765 + sudo -iu builduser bash -c 'cd /startdir; SRCEXT="${1}" makepkg "${@:2}" --allsource' -bash "$srcext" "$@" || return sudo -iu builduser bash -c 'cd /startdir; makepkg "$@" --noextract --noprepare' -bash "$@" } @@ -364,27 +301,27 @@ _chrootnamcap() { done } -# Usage: download_sources $copydir $src_owner +# Usage: download_sources $copydir $makepkg_user # Globals: # - SRCDEST # - USER download_sources() { local copydir=$1 - local src_owner=$2 + local makepkg_user=$2 - local builddir="$(mktemp -d)" + local builddir + builddir="$(mktemp -d)" chmod 1777 "$builddir" # Ensure sources are downloaded - if [[ $USER != $src_owner ]]; then - sudo -u $src_owner env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \ - makepkg --config="$copydir/etc/makepkg.conf" --verifysource -o + if [[ "$(id -u "$makepkg_user")" != 0 ]]; then + sudo -u "$makepkg_user" env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \ + makepkg --config="$copydir/etc/makepkg.conf" --verifysource -o || + die "Could not download sources." else - ( export SRCDEST BUILDDIR="$builddir" - makepkg --asroot --config="$copydir/etc/makepkg.conf" --verifysource -o - ) + error "Running makepkg as root is not allowed." + exit 1 fi - (( $? != 0 )) && die "Could not download sources." # Clean up garbage from verifysource rm -rf "$builddir" @@ -402,8 +339,10 @@ move_products() { for pkgfile in "$copydir"/pkgdest/*; do chown "$src_owner" "$pkgfile" mv "$pkgfile" "$PKGDEST" - if [[ $PKGDEST != $PWD ]]; then - ln -sf "$PKGDEST/${pkgfile##*/}" . + + # Fix broken symlink because of temporary chroot PKGDEST /pkgdest + if [[ "$PWD" != "$PKGDEST" && -L "$PWD/${pkgfile##*/}" ]]; then + ln -sf "$PKGDEST/${pkgfile##*/}" fi done @@ -424,26 +363,27 @@ move_products() { main() { init_variables - orig_argv=("$@") - - while getopts 'hcur:I:l:nTD:d:' arg; do + while getopts 'hcur:I:l:nTD:d:U:' arg; do case "$arg" in c) clean_first=true ;; - D) bindmounts_ro+=(--bind-ro="$OPTARG") ;; - d) bindmounts_rw+=(--bind="$OPTARG") ;; + D) bindmounts_ro+=("--bind-ro=$OPTARG") ;; + d) bindmounts_rw+=("--bind=$OPTARG") ;; u) update_first=true ;; r) passeddir="$OPTARG" ;; I) install_pkgs+=("$OPTARG") ;; l) copy="$OPTARG" ;; n) run_namcap=true; makepkg_args+=(-i) ;; T) temp_chroot=true; copy+="-$$" ;; + U) makepkg_user="$OPTARG" ;; h|*) usage ;; esac done [[ ! -f PKGBUILD && -z "${install_pkgs[*]}" ]] && die 'This must be run in a directory containing a PKGBUILD.' + [[ -n $makepkg_user && -z $(id -u "$makepkg_user") ]] && die 'Invalid makepkg user.' + makepkg_user=${makepkg_user:-${SUDO_USER:-$USER}} - check_root "$0" "${orig_argv[@]}" + check_root # Canonicalize chrootdir, getting rid of trailing / chrootdir=$(readlink -e "$passeddir") @@ -490,7 +430,7 @@ main() { lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy" if [[ ! -d $copydir ]] || $clean_first; then - sync_chroot "$chrootdir" "$copy" + sync_chroot "$chrootdir/root" "$copydir" "$copy" fi $update_first && arch-nspawn "$copydir" \ @@ -504,7 +444,7 @@ main() { [[ -f PKGBUILD ]] || return $ret fi - download_sources "$copydir" "$src_owner" + download_sources "$copydir" "$makepkg_user" prepare_chroot "$copydir" "$USER_HOME" "$repack" @@ -524,7 +464,7 @@ main() { (( ret += 1 )) fi - $temp_chroot && delete_chroot "$copydir" + $temp_chroot && delete_chroot "$copydir" "$copy" if (( ret != 0 )); then if $temp_chroot; then |