From 16df1e6e52cb3a414475ea609f57864675f8e02a Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Mon, 13 Mar 2017 21:33:48 -0400 Subject: add sbrun --- sbrun | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100755 sbrun 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 </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 -- cgit v1.2.3