diff options
-rwxr-xr-x | sbrun | 422 |
1 files changed, 422 insertions, 0 deletions
@@ -0,0 +1,422 @@ +#!/bin/bash + +# Configurables: + +TMP=${TMP:-/tmp/SBo} +OUTPUT=${OUTPUT:-/tmp} +BUILDLOG=${BUILDLOG:-build.log} +DEFAULT_MAKEFLAGS="-j8" + +# 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 .gitignore. + +# 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" \ + "$0" "$@" +fi + +# Inherit MAKEFLAGS from env, if present. +MAKEFLAGS="${MAKEFLAGS:-$DEFAULT_MAKEFLAGS}" + +# Defaults, changed by -options. +NETWORK="no" +TRACK="yes" +STRACE="no" +CLEANUP="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.$$ + +# Why does sbrun exist? Why not use sbopkg or sbotools? sbrun is targeted +# more towards a SlackBuild developer/maintainer than an end user. My +# workflow is to edit the script in one terminal and repeatedly execute it +# in another. If you're editing a SlackBuild, you keep running it over & +# over again. So I wrote a 1-liner shell script that did this: + +# sudo sh ./$( pwd | sed 's,.*/,,' ).SlackBuild + +# I have /sbin:/usr/sbin in my user's PATH so sudo works fine, but it's a +# PITA to pass environment variables using the above script (sudo strips +# them out of the env). So I made the script take arguments, and treat +# those as env vars (place them between 'sudo' and 'sh'). + +# Then I added MAKEFLAGS support to it (-jN option). Then I found out +# some of my builds were writing outside of $TMP (due to either my +# own mistakes, or upstream bugs) and decided I needed a way to +# reliably detect that, hence the trackfs stuff. Also someone on +# the mailing list posted output from running a SlackBuild under +# bubblewrap, which prevented network access the script was trying +# to do. Which seems like a nifty feature to have, but bubblewrap +# is overkill for just running a shell script. + +# The strace and sh -x options were added because those are things I do +# fairly often with buggy SlackBuilds. The elapsed time display is a nice +# convenience (I complain that "This thing takes 2 hours to build!" when +# really it's closer to 1 hour). + +# The -c option is the only "end user" option, really. I mostly use it +# for building dependencies maintained by other people, not my own stuff. + +# Basically, sbrun started out as a lazy typist's tool, and grew into +# something more generally useful. It's lower-level than sbotools or +# sbopkg, but more convenient than just executing ./*.SlackBuild. It +# requires no initial setup or configuration (it's a self-contained +# shell script). Since it's *not* intended to replace sbotools or sbopkg, +# sbrun doesn't do any of these things: + +# - download source files +# - check source file md5sums +# - allow building multiple packages at once (queue files) +# - install/upgrade/remove packages (it *just* builds them) +# - dependency resolution +# - sync the repo, or even have any concept of a repo (it only deals +# with a single SlackBuild script, in the current directory). +# - anything to do with .info files. Nothing about sbrun is SBo-specific, +# it'll work with Pat's or AlienBOB's or anyone else's scripts (which +# is why it's called sbrun and not sborun). + +# Possible future options: +# -f Run script with fakeroot. (still need root/sudo for unshare/nsenter, +# and trackfs won't track failed writes, maybe this isn't that useful?) +# -i Install package after it's built. Could count as scope creep. +# -H long help (the existing help is long already, -h would be an extract) + +long_help() { + # note: root's pager is used, not the user's, since we use sudo. + # not going to care about this one. + cat <<EOF | ${PAGER:-less} +$SELF: paranoid SlackBuild wrapper + +$SELF runs the SlackBuild script in the current directory, +with an optional custom environment. + +By default, the SlackBuild can't access the network, and filesystem +activity is tracked: writes to system directories are flagged and +reported. Also, a complete log of the build's standard output +and standard error is written to "$BUILDLOG". + +If $SELF is called as a non-root user, it re-executes itself via +sudo. If you hate sudo, just run $SELF as root. + +$SELF is designed for use with SBo scripts, but will work for any +SlackBuild (it doesn't refer to the SBo .info file). + +You'll want to install system/trackfs from SBo for filesystem tracking +to work. + +$SELF written by B. Watson (yalhcru@gmail.com) and released +under the WTFPL. See http://www.wtfpl.net/txt/copying/ for details. + +Usage: $SELF [-jN] [-n] [variable=value ...] + +-jN Run N make jobs in parallel. Default is to use MAKEFLAGS from + the environment if set, otherwise "$DEFAULT_MAKEFLAGS". If + a SlackBuild fails without -j1, this is a bug in the SlackBuild + and you should ask its maintainer to add -j1 to the make command + in the script. + +-n Allow the SlackBuild to access the network. If a SlackBuild + fails without this flag, that's a bug in the SlackBuild and + should be reported to its maintainer (EMAIL in the .info file). + +-t Don't use trackfs to watch for writes to system files. If a + SlackBuild fails without this flag, it's a bug in either + $SELF or trackfs, and should be reported to the maintainer + at yalhcru@gmail.com. + +-s Run the script with strace. This option implies -t (trackfs + will be disabled). The strace log will be written to the + current directory as "strace.out". + +-x Run the script with "sh -x", enables shell command tracing. + +-c Clean up (remove) source and package directories after the + build completes. This option overrides \$TMP from the environment. + +-h, --help + Show short usage message and exit. + +-H Show long help message (you're reading it now) and exit. + +All arguments not beginning with - are passed as part of the SlackBuild +script's environment. Options beginning with - must occur before +environment variables. Example: + + $SELF -j1 SDL2=no DOCS=yes + +Leave off the -j1 to use the default number of jobs. + +After the SlackBuild exits, any files written to outside of \$TMP, +\$OUTPUT, /tmp, /var/tmp, or /root/.ccache (collectively referred to +as "the sandbox") are logged to stdout. See trackfs(1) for details of +the log format, but any write outside the sandbox means a bug in the +SlackBuild and should be reported to its maintainer. + +The exit status of $SELF is the exit status of the SlackBuild. +EOF +} + +show_help() { + cat <<EOF +$SELF: paranoid SlackBuild wrapper. + +$SELF written by B. Watson (yalhcru@gmail.com) and released +under the WTFPL. See http://www.wtfpl.net/txt/copying/ for details. + +Usage: $SELF [-jN] [-n] [variable=value ...] + +-jN Run N make jobs in parallel. +-n Allow the SlackBuild to access the network. +-t Don't use trackfs to watch for writes to system files. +-s Run the script with strace, output in "strace.out". +-x Run the script with "sh -x", enables shell command tracing. +-c Clean up (remove) source and package dirs after build completes. +-h, --help + Show short usage message (you're reading it now) and exit. +-H Show long help message and exit. +variable=value + Passed to script as environment variables. +EOF +} + +# maybe add 2>/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_build() { + [ "$CLEANUP" = "yes" ] && ( rm -rf "$TMP" ; rm -f $BUILDLOG ) +} + +# Add a dir to $PATH, if not already present. This is actually kinda +# pointless, it would work just as well to always add dirs to PATH +# even if they're redundant. +ensure_path() { + if ! echo "$PATH" | sed 's,:,\n,g' | grep -q "^$1\$"; then + #echo "$1 not in PATH, adding" + export PATH="$1:$PATH" + fi +} + +# Handle ^C gracefully. TODO: the exit status should be 128 plus +# the number of the signal received. The hard-coded 130 means SIGINT, +# the ^C signal, but we trap other signals too. Maybe use: +# http://stackoverflow.com/questions/2175647/is-it-possible-to-detect-which-trap-signal-in-bash +signal_handler() { + cleanup_log + cleanup_nonet + cleanup_build + exit 130 +} + +# Print a number of seconds as either MM:SS (if less than 1 hour) +# or HH:MM:SS (if >= 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 +} + +### 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 + -j*) MAKEFLAGS="$1" ;; + -n) NETWORK=yes ;; + -t) TRACK=no ;; + -s) TRACK=no; STRACE=yes ;; + -x) X="-x" ;; + -c) CLEANUP="yes" ;; + -h|-help|--help) show_help ; exit 0 ;; + -H) long_help ; exit 0 ;; + -*) show_help ; exit 1 ;; + esac + shift +done + +# warn and die append to the log, make sure it starts out empty. +> $BUILDLOG + +# 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 + +# $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 + 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'" +done + +# 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 [ "$TRACK" = "yes" ]; then + if ! /bin/which trackfs &>/dev/null; then + warn "File tracking enabled, but trackfs not installed!" + die "Install system/trackfs or re-run $SELF with -t." + fi + + LOGDIR="$( mktemp -d /tmp/sbrun.XXXXXX )" + if [ -z "$LOGDIR" ] || [ ! -d "$LOGDIR" ]; then + die "Can't create temp log dir in /tmp, bailing" + fi + + LOG=$LOGDIR/log + + # Fun fact: trackfs uses GNU-style -- to mean "no more options", + # but it's undocumented in the man page and --help output. + # The readlink stuff is here in case $TMP or $OUTPUT has a symlink + # in its path: trackfs will log the real path, with the links resolved. + TRACKFS=\ +"trackfs -l $LOG \ + -I$( readlink -f "$TMP" )\\* \ + -I$( readlink -f "$OUTPUT")\\* \ + -I/tmp\\* \ + -I/var/tmp\* \ + -I/root/.ccache\\* \ + --" + +fi + +if [ "$STRACE" = "yes" ]; then + TRACKFS="strace -f -ostrace.out" +fi + +SCRIPT="./$( pwd | sed 's,.*/,,' ).SlackBuild" +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" = "yes" ]; 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. Note that the 'tee' command isn't being +# tracked by trackfs. +eval $NSENTER $TRACKFS sh $X $SCRIPT 2>&1 | tee -a $BUILDLOG +RET=$? +echo "$SCRIPT exit status: $RET" | tee -a $BUILDLOG + +END_TIME="$( date +%s )" + +{ + echo -n "Elapsed time: " + print_hms $(( $END_TIME - $START_TIME )) +} | tee -a $BUILDLOG + +cleanup_nonet + +if [ "$TRACK" = "yes" ]; then + if [ -s $LOG ]; then + warn "WARNING: files altered outside the sandbox:" + cat $LOG 1>&2 + cat $LOG >> $BUILDLOG + fi + + cleanup_log +fi + +cleanup_build + +# Our return status is that of the SlackBuild. +exit $RET |