#!/bin/bash # TODO: distcc masquerade dir, pump mode # TODO: fix interactive shell option # TODO: maybe change the cpufreq governor? # Configurables: TMP=${TMP:-/tmp/SBo} OUTPUT=${OUTPUT:-/tmp} BUILDLOG=${BUILDLOG:-build.log} DEFAULT_MAKEFLAGS="-j$(( $( nproc ) + 1 ))" # End of configurables. It's probably best not to configure TMP or # OUTPUT here (use the environment instead) anyway. Also it's probably # convenient to add build.log to .git/info/exclude. # If we're not running as root, re-exec as root, with args. # Anything sbrun expects to possibly inherit from the caller's environment # must be explicity set here as sudo will strip them from the environment # before executing anything. if [ "$(id -u)" != "0" ]; then exec sudo \ TMP="$TMP" \ OUTPUT="$OUTPUT" \ MAKEFLAGS="$MAKEFLAGS" \ BUILDLOG="$BUILDLOG" \ DISTCC_HOSTS="$DISTCC_HOSTS" \ "$0" "$@" fi # This is a bit of a hack: I keep my tools in my user's ~/bin, # and sourcing /etc/profile blows away PATH... OLDPATH=$PATH source /etc/profile PATH=$OLDPATH:$PATH [ -e "$BUILDLOG" ] && mv "$BUILDLOG" "$BUILDLOG".old # Inherit MAKEFLAGS from env, if present. MAKEFLAGS="${MAKEFLAGS:-$DEFAULT_MAKEFLAGS}" # Defaults, changed by -options. NETWORK="no" TRACK="yes" STRACE="" CLEANUP="no" SRCSH="no" PKGSH="no" LOGDIR="" NSENTER="" TRACKFS="" SELF=$(basename $0) # unshare and nsenter use this. It's theoretically better to use # an unpredictable filename (not one based on the PID), but anyone # able to mess with /mnt already has root access. NONET_PATH=/mnt/nonet.$SELF.$$ long_help() { # note: root's pager is used, not the user's, since we use sudo. # not going to care about this one. cat <&1 fi cat </dev/null to these, but for now I wanna know if they fail. cleanup_nonet() { if [ "$NETWORK" = "no" ]; then umount $NONET_PATH rm -f $NONET_PATH fi } cleanup_log() { [ -n "$LOGDIR" ] && rm -rf "$LOGDIR" } cleanup_privdir() { [ "$PRIVDIR" = "" ] && return umount $FAKEROOT/$OUTPUT umount $FAKEROOT/$TMP umount $FAKEROOT if [ "$?" != "0" ]; then cat <= 1 hour). This function could almost be replaced # by: TZ=GMT printf '%(%H:%M:%S)T\n' "$1" # ...except print_hms doesn't display the hours if they're 00, and using # printf that way won't handle durations longer than 23:59:59 because # it's trying to print a time of day (24:00:00 would be 00:00:00 of the # next day). Hopefully no SlackBuild takes over a day to run, but you # never know... print_hms() { local sec="$1" hrs min hrs=$(( $sec / 3600 )) sec=$(( $sec % 3600 )) min=$(( $sec / 60 )) sec=$(( $sec % 60 )) if [ "$hrs" -gt "0" ]; then printf '%02d:%02d:%02d\n' $hrs $min $sec else printf '%02d:%02d\n' $min $sec fi } # perl-flavoured error messenger warn() { echo "$SELF:" "$@" 1>&2 echo "$SELF:" "$@" >> $BUILDLOG } # Suicide squad, attack! die() { warn "$@" exit 1 } # -q and -Q run_queue() { exec sbodeps $1 . | sbqrun - } ### main() # if these are in $PATH, 99.99% of all SBo builds will run # correctly under sudo. Or maybe even 100%. At least, I can't # remember running into problems, for quite a few years now. ensure_path /sbin ensure_path /usr/sbin ensure_path /usr/share/texmf/bin # parse -options while printf -- "$1" | grep -q ^-; do case "$1" in -l) LINTPKG="yes" ;; -j*) MAKEFLAGS="$1" ;; -n) NETWORK=yes ;; -s) STRACE=-f ;; -x) X="-x" ;; -c) CLEANUP="yes" ;; -I) SRCSH="yes" ;; -p) PKGSH="yes" ;; -D) CC="distcc gcc" CXX="distcc g++" NETWORK=yes export CC CXX ;; -i) UPKG=yes ;; -d) SBODL=yes ;; -q) run_queue ;; -Q) run_queue -i ;; -h|-help|--help) show_help ; exit 0 ;; -H) long_help ; exit 0 ;; -*) show_help "$1"; exit 1 ;; esac shift done [ "$SBODL" = "yes" ] && sbodl # warn and die append to the log, make sure it starts out empty. # This is the only place we use tee $BUILDLOG (everything else appends). { echo -n "== $SELF starting up at " date echo -n "== directory: " pwd echo "== command: $0" "$@" echo } | tee $BUILDLOG # set the build log's ownership to the calling user, or at least the # user that owns the current directory. chown "$( stat -c %U:%G . )" $BUILDLOG # rest of arg parsing can use warn or die. if echo "$1" | grep -qv '='; then SCRIPT="$1" shift #[ ! -e "$SCRIPT" ] && warn "$SCRIPT not found, I hope you know what you're doing!" fi # $ENV is only for showing to the user ENV="MAKEFLAGS=$MAKEFLAGS" export MAKEFLAGS TMP OUTPUT # Add rest of args to environment. The echo|cut and eval stuff allows # spaces to occur in the values. There is probably a better modern-bash # way to do this, but (to me anyway) it'll be less readable. for arg; do if echo "$arg" | grep -qv '='; then die "invalid/unknown argument '$1', try -h for help or -H for long help." else ENV="$ENV $arg" #eval export "$arg" # works but doesn't allow spaces var="$( echo "$arg" | cut -d= -f1 )" val="$( echo "$arg" | cut -d= -f2 )" eval "export $var='$val'" fi done # The easy way to remove the source and PKG dirs after the # script runs is to guarantee they'll be the only things in # $TMP. Normally, we don't create the $TMP dir, so we can # catch 'script fails to create $TMP dir' errors. But with -c, # we don't care about troubleshooting so much, and mktemp is # the way to go. if [ "$CLEANUP" = "yes" ]; then TMP="$( mktemp -d /tmp/sbrun.build.XXXXXX )" if [ -z "$TMP" ] || [ ! -d "$TMP" ]; then die "Can't create temp build dir in /tmp, bailing" fi fi # I wasn't gonna trap signals, but I can't break myself of the habit # of hitting ^C. # TODO: we should be trapping more signals here... # TODO: find out why trackfs sometimes segfaults when I hit ^C. No # harm done (it was already killed by SIGINT), just irritating. trap signal_handler INT TERM if [ "$STRACE" != "" ]; then PRECMD="strace $STRACE" fi # Used to do this, but it doesn't allow for cases where the directory # has been renamed (foo.SlackBuild in a dir called foo.testing or foo.old). #SCRIPT="./$( pwd | sed 's,.*/,,' ).SlackBuild" # This is better, but during development, a user might have copies of the # script named foo.old.SlackBuild and foo.new.SlackBuild, so not perfect. # To allow for this, we now take an optional script name on the command line. SCRIPT="${SCRIPT:-$( /bin/ls ./*.SlackBuild | head -1 )}" if [ ! -e "$SCRIPT" ]; then die "$SCRIPT not found, bailing" fi { echo "Running $SCRIPT, logging to $BUILDLOG" echo "Environment: $ENV" echo "File tracking: $TRACK" echo "Network access: $NETWORK" if [ "$STRACE" != "" ]; then echo "strace log: strace.out" fi echo } | tee -a $BUILDLOG # Set up no-network namespace. This isn't foolproof, there are probably # ways for a script being run by root to escape the namespace, but # a script that did that would hopefully never get approved by our # beloved moderators. if [ "$NETWORK" = "no" ]; then touch $NONET_PATH unshare --net=$NONET_PATH ifconfig lo 127.0.0.1 up NSENTER="nsenter --net=$NONET_PATH" fi START_TIME="$( date +%s )" # Actually run the script. # The rigmarole with $? and RET might not be the # best way to get the exit status, TODO: see if I can do this cleaner. # Also, using { } instead of ( ) utterly fails. ### 20260923 bkw: mount the overlay here! PRIVDIR="$( mktemp -td sbrun.priv.XXXXXXXXXX )" UPPERDIR=$PRIVDIR/upperdir WORKDIR=$PRIVDIR/workdir FAKEROOT=$PRIVDIR/fake_root mkdir -p $UPPERDIR $WORKDIR $FAKEROOT echo "Private dir for overlay FS: $PRIVDIR" | tee -a $BUILDLOG # Force-load the module modprobe overlay &> /dev/null mount -t overlay overlay \ -olowerdir=/,upperdir=$UPPERDIR,workdir=$WORKDIR \ $FAKEROOT # writes to $FAKEROOT/$TMP will pass through to the real $TMP mount --bind $TMP $FAKEROOT/$TMP mount --bind $OUTPUT $FAKEROOT/$OUTPUT echo cd "'$( pwd )'" > $PRIVDIR/runme echo $PRECMD bash $X $SCRIPT >> $PRIVDIR/runme cat $PRIVDIR/runme ( $NSENTER chroot $FAKEROOT bash $PRIVDIR/runme 2>&1; echo "$?" > $PRIVDIR/ret ) | tee -a $BUILDLOG RET="$( cat $PRIVDIR/ret )" END_TIME="$( date +%s )" echo "$SCRIPT exit status: $RET" | tee -a $BUILDLOG { echo -n "Elapsed time: " print_hms $(( $END_TIME - $START_TIME )) } | tee -a $BUILDLOG cleanup_nonet ### 20260923 bkw: # Anything in $UPPERDIR after umount was written there by the # SlackBuild. Not everything there is worth bitching about. # After overlay umount, exclude these dirs from complaints: # /tmp /proc /var/tmp /root/.ccache /root/.cache /dev/pts /dev/shm # also $TMP and $OUTPUT. # For some reason, "root" gets created, too. # Have to specify each dir by itself *without* trailing slash, # then again with \/* to catch files/dirs under that dir. ( cd $PRIVDIR/upperdir find * \ \! -path root \ \! -path root/.cache/\* \ \! -path root/.ccache/\* \ \! -path tmp/\* \ \! -path var/tmp/\* \ \! -path proc/\* \ \! -path dev/pts/\* \ \! -path dev/shm/\* \ \! -path $TMP/\* \ \! -path $OUTPUT/\* \ \! -path root/.cache \ \! -path root/.ccache \ \! -path tmp \ \! -path var/tmp \ \! -path proc \ \! -path dev/pts \ \! -path dev/shm \ \! -path $TMP \ \! -path $OUTPUT \ -print0 | xargs -r0 ls -bld > $TMP/sbrun.turds.$$ ) 2>/dev/null cleanup_privdir if [ -s $TMP/sbrun.turds.$$ ]; then warn "WARNING: files altered outside the sandbox:" cat $TMP/sbrun.turds.$$ 1>&2 cat $TMP/sbrun.turds.$$ >> $BUILDLOG fi rm -f $TMP/sbrun.turds.$$ # If linting + installation were both requested, don't install # the package if it fails to lint. if [ "$LINTPKG" = "yes" ] && sbopkglint; then RET=$? else UPKG="" fi # spawn shell(s) if requested. -i and -p are not mutually exclusive. # TODO: do this cleaner? if [ "$SRCSH" = "yes" ]; then if [ "$RET" != "0" ]; then warn "Script failed (status $RET), ignoring -i option" else SRCDIR="$( /bin/ls -td $TMP/*/ | grep -v /package- | head -1 )" ( cd $SRCDIR && bash -login ) fi fi # PRGNAM is problematic. We don't want to use $( basename $( pwd ) ) # because the directory might have been renamed (foo => foo.testing or # foo.old). Reading the .info file is no good (this might not be an SBo # build). For now, extract it from the script name, but eventually this # won't work because I want to support passing a script name someday # instead of hard-coding .SlackBuild. if [ "$PKGSH" = "yes" ]; then PRGNAM="$( echo $SCRIPT | sed 's,^\./\(.*\)\.SlackBuild$,\1,' )" PKG=$TMP/package-$PRGNAM if [ -d "$PKG" ]; then ( cd $PKG && bash -login ) else warn "$PKG not found, ignoring -p option" fi fi cleanup_build # Install the package if -i. [ "$RET" = "0" ] && [ "$UPKG" = "yes" ] && upkg # Our return status is that of the SlackBuild. exit $RET