aboutsummaryrefslogtreecommitdiff
path: root/sbrun
diff options
context:
space:
mode:
Diffstat (limited to 'sbrun')
-rwxr-xr-xsbrun422
1 files changed, 422 insertions, 0 deletions
diff --git a/sbrun b/sbrun
new file mode 100755
index 0000000..407bd64
--- /dev/null
+++ b/sbrun
@@ -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