summaryrefslogtreecommitdiff
path: root/src/chroot-tools/librechroot
diff options
context:
space:
mode:
Diffstat (limited to 'src/chroot-tools/librechroot')
-rwxr-xr-xsrc/chroot-tools/librechroot509
1 files changed, 509 insertions, 0 deletions
diff --git a/src/chroot-tools/librechroot b/src/chroot-tools/librechroot
new file mode 100755
index 0000000..cf564ed
--- /dev/null
+++ b/src/chroot-tools/librechroot
@@ -0,0 +1,509 @@
+#!/usr/bin/env bash
+set -euE
+# librechroot
+
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2012 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2012-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# 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 2 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 4 places:
+# - the usage() text
+# - the commands=() array
+# - the case statement in main() that checks the number of arguments
+# - the case statement in main() that runs them
+
+. "$(librelib conf)"
+load_files chroot
+
+. libremessages
+
+shopt -s nullglob
+umask 0022
+
+################################################################################
+# Wrappers for files in ${pkglibexecdir}/chroot/ #
+################################################################################
+
+readonly _arch_nspawn="$(librelib chroot/arch-nspawn)"
+readonly _mkarchroot="$(librelib chroot/mkarchroot)"
+readonly _makechrootpkg="$(librelib chroot/makechrootpkg.sh)"
+
+arch_nspawn_flags=()
+sysd_nspawn_flags=()
+
+hack_arch_nspawn_flags() {
+ local copydir="$1"
+
+ local makepkg_conf="$copydir/etc/makepkg.conf"
+
+ OPTIND=1
+ set -- ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"}
+ while getopts 'hC:M:c:f:s' arg; do
+ case "$arg" in
+ M) makepkg_conf="$OPTARG" ;;
+ *) :;;
+ esac
+ done
+
+ # Detect the architecture of the chroot
+ local CARCH
+ if [[ -f "$makepkg_conf" ]]; then
+ eval $(grep '^CARCH=' "$makepkg_conf")
+ else
+ CARCH="$(uname -m)"
+ fi
+
+ if [[ "$CARCH" == armv7h ]] && ! setarch armv7l 2>/dev/null; then
+ # We're running an ARM chroot on a non-ARM processor
+
+ # Make sure that qemu-static is set up with binfmt_misc
+ if [[ $(grep -xF \
+ -e 'enabled'\
+ -e 'interpreter /usr/bin/qemu-arm-static' \
+ /proc/sys/fs/binfmt_misc/arm 2>/dev/null |wc -l) -lt 2 ]]; then
+ error 'Cannot cross-compile for ARM on x86'
+ plain 'This requires a binfmt_misc entry for qemu-arm-static,'
+ plain 'which is provided by the %s package.' binfmt-qemu-static
+ plain 'If you have this, you may need to restart %s.' systemd-binfmt.service
+ return 1
+ fi
+
+ # Let qemu/binfmt_misc do its thing
+ arch_nspawn_flags+=(-f /usr/bin/qemu-arm-static -s)
+
+ # The -any packages are built separately for ARM from
+ # x86, so if we use the same CacheDir as the x86 host,
+ # then there will be PGP errors.
+ mkdir -p /var/cache/pacman/pkg-arm
+ arch_nspawn_flags+=(-c /var/cache/pacman/pkg-arm)
+ fi
+}
+
+# Usage: arch-nspawn $copydir $cmd...
+arch-nspawn() {
+ local copydir=$1; shift
+ local cmd=("$@")
+
+ local arch_nspawn_flags=(${arch_nspawn_flags+"${arch_nspawn_flags[@]}"})
+ hack_arch_nspawn_flags "$copydir"
+
+ "$_arch_nspawn" \
+ ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"} \
+ "$copydir" \
+ ${sysd_nspawn_flags+"${sysd_nspawn_flags[@]}"} \
+ -- \
+ "${cmd[@]}"
+}
+
+# Usage: mkarchroot $copydir $pkgs...
+mkarchroot() {
+ local copydir=$1; shift
+ local pkgs=("$@")
+
+ local arch_nspawn_flags=(${arch_nspawn_flags+"${arch_nspawn_flags[@]}"})
+ hack_arch_nspawn_flags "$copydir"
+
+ unshare -m "$_mkarchroot" \
+ ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"} \
+ "$copydir" \
+ "${pkgs[@]}"
+}
+
+# Usage: _makechrootpkg $function $arguments...
+# Don't load $_makechrootpkg directly because it doesn't work with -euE
+_makechrootpkg() (
+ set +euE
+ . "$_makechrootpkg"
+ "$@"
+)
+
+################################################################################
+# Utility functions #
+################################################################################
+
+# 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
+}
+
+# Print code to 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
+}
+
+check_mountpoint() {
+ local file=$1
+ local mountpoint="$(df -P "$file"|sed '1d;s/.*\s//')"
+ local mountopts=($(LC_ALL=C mount|awk "{ if (\$3==\"$mountpoint\") { gsub(/[(,)]/, \" \", \$6); print \$6 } }"))
+ ! in_array nosuid "${mountopts[@]}" && ! in_array noexec "${mountopts[@]}"
+}
+
+################################################################################
+# Main program #
+################################################################################
+
+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 variables 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 'The processor architecture of the chroot is determined
+ by the by `CARCH` variable in the `/etc/makepkg.conf`
+ file inside of the chroot.'
+ echo
+ prose 'The `-A CARCH` flag is *almost* simply an alias for'
+ printf ' %s\n' \
+ '-C "/usr/share/pacman/defaults/pacman.conf.$CARCH" \' \
+ '-M "/usr/share/pacman/defaults/makepkg.conf.$CARCH"'
+ prose 'However, before doing that, it actually makes a temporary copy of
+ `pacman.conf`, and sets the `Architecture` line to match the
+ `CARCH` line in `makepkg.conf`.'
+ 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 "-A <$(_ CARCH)>" 'Set the architecture of the copy; simply an
+ alias for the `-C` and `-M` flags, see above.'
+ 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 `/startdir/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
+)
+
+# Globals: $CHROOTDIR, $CHROOT, $COPY, $rootdir and $copydir
+main() {
+ COPY=$LIBREUSER
+ [[ $COPY != root ]] || COPY=copy
+
+ local mode=enter
+ while getopts 'n:l:NC:M:A: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");;
+ A)
+ if ! [[ -f "/usr/share/pacman/defaults/pacman.conf.$OPTARG" && -f "/usr/share/pacman/defaults/makepkg.conf.$OPTARG" ]]; then
+ error 'Unsupported architecture: %s' "$OPTARG"
+ plain 'See the files in %q for valid architectures.' /usr/share/pacman/defaults/
+ return 1;
+ fi
+ trap 'rm -f -- "$tmppacmanconf"' EXIT
+ tmppacmanconf="$(mktemp --tmpdir librechroot-pacman.conf.XXXXXXXXXX)"
+ < "/usr/share/pacman/defaults/pacman.conf.$OPTARG" sed -r "s|^#?\\s*Architecture.+|Architecture = ${OPTARG}|g" > "$tmppacmanconf"
+ arch_nspawn_flags+=(
+ -C "$tmppacmanconf"
+ -M "/usr/share/pacman/defaults/makepkg.conf.$OPTARG"
+ );;
+ w) sysd_nspawn_flags+=("--bind=$OPTARG");;
+ r) sysd_nspawn_flags+=("--bind-ro=$OPTARG");;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# -lt 1 ]]; then
+ error "Must specify a command"
+ usage >&2
+ return 1
+ fi
+ mode=$1
+ if ! in_array "$mode" "${commands[@]}"; then
+ error "Unrecognized command: %s" "$mode"
+ usage >&2
+ return 1
+ fi
+ shift
+ case "$mode" in
+ noop|make|sync|delete|update|enter|clean-pkgs|clean-repo)
+ if [[ $# -gt 0 ]]; then
+ error 'Command `%s` does not take any arguments: %s' "$mode" "$*"
+ usage >&2
+ return 1
+ fi
+ :;;
+ install-file)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one file' "$mode"
+ usage >&2
+ return 1
+ else
+ local missing=()
+ local file
+ for file in "$@"; do
+ if ! [[ -f $file ]]; then
+ missing+=("$file")
+ fi
+ done
+ if [[ ${#missing[@]} -gt 0 ]]; then
+ error "%s: file(s) not found: %s" "$mode" "${missing[*]}"
+ return 1
+ fi
+ fi
+ :;;
+ install-name)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one package name' "$mode"
+ usage >&2
+ return 1
+ fi
+ :;;
+ run)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one argument' "$mode"
+ usage >&2
+ return 1
+ fi
+ :;;
+ esac
+
+
+ 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
+
+ # XXX: SYSTEMD-STDIN HACK
+ if ! [[ -t 0 ]]; then
+ error "Input is not a TTY"
+ plain "https://labs.parabola.nu/issues/431"
+ plain "https://bugs.freedesktop.org/show_bug.cgi?id=70290"
+ prose "Due to a bug in systemd-nspawn, redirecting stdin is not
+ supported." >&2
+ return 1
+ fi
+
+ # Keep this lock for 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 [[ $mode != delete ]]; then
+ if ! check_mountpoint "$copydir.lock"; then
+ error "Chroot copy is mounted with nosuid or noexec options: [%s]" "$COPY"
+ return 1
+ fi
+
+ if [[ ! -d $rootdir ]]; then
+ msg "Creating 'root' copy for chroot [%s]" "$CHROOT"
+ mkarchroot "$rootdir" base-devel
+ 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
+
+ # Note: the in-chroot pkgconfdir is non-configurable, this is
+ # intentionally hard-coded.
+ mkdir -p "$copydir/etc/libretools.d"
+ {
+ if [[ ${#CHROOTEXTRAPKG[*]} -eq 0 ]]; then
+ echo 'CHROOTEXTRAPKG=()'
+ else
+ printf 'CHROOTEXTRAPKG=('
+ printf '%q ' "${CHROOTEXTRAPKG[@]}"
+ printf ')\n'
+ fi
+ } > "$copydir"/etc/libretools.d/chroot.conf
+
+ # "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
+ trap EXIT # clear the trap to remove the tmp pacman.conf from -A
+ 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"
+ 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 -- $(printf '%q ' "$copydir"/{bin/chcleanup,chrootexec})" EXIT
+ install -m755 "$(librelib chroot/chcleanup)" "$copydir/bin/chcleanup"
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'mkdir -p /startdir' \
+ 'cd /startdir' \
+ '/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 "$@"