diff options
Diffstat (limited to 'src/chroot-tools/distcc-tool')
-rwxr-xr-x | src/chroot-tools/distcc-tool | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/src/chroot-tools/distcc-tool b/src/chroot-tools/distcc-tool new file mode 100755 index 0000000..7633029 --- /dev/null +++ b/src/chroot-tools/distcc-tool @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# -*- tab-width: 4; sh-basic-offset: 4 -*- +# distcc-tool + +# Copyright 2013 Luke Shumaker +# +# 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 3 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/>. + +# This program has very few dependencies: +# - bash: I don't know what version, I use fairly modern features +# - socat +# - cat: any version +# - rm: any version +# - sed: any version +# On Parabola, this means the packages: +# bash, coreutils, sed, socat + +if ! type gettext &>/dev/null; then + gettext() { echo "$@"; } +fi + +panic() { + echo "$(gettext 'panic: malformed call to internal function')" >&2 + exit 1 +} + +error() { + mesg="$(gettext "$1")"; shift + printf "$(gettext 'ERROR:') $mesg\n" "$@" >&2 + exit 1 +} + +print() { + local mesg=$1 + shift + printf -- "$(gettext "$mesg")\n" "$@" +} + +usage() { + print "Usage: $0 COMMAND [COMMAND-ARGS]" + print "Tool for using distcc within a networkless chroot" + echo + print "Commands:" + print ' help print this message' + print ' odaemon CHROOTPATH daemon to run outside of the chroot' + print ' idaemon DISTCC_HOSTS daemon to run inside of the chroot' + print ' rewrite DISTCC_HOSTS prints a rewritten version of DISTCC_HOSTS' + print ' client HOST PORT connects stdio to TCP:$HOST:$PORT' + print 'Commands: for internal use' + print ' server counterpart to client; spawned by odaemon' +} + +errusage() { + if [[ $# -gt 0 ]]; then + fmt="$(gettext "$1")"; shift + printf "$(gettext 'ERROR:') $fmt\n" "$@" >&2 + fi + usage >&2 + exit 1 +} + +main() { + local cmd=$1 + shift + case "$cmd" in + help) + [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd" + usage;; + odaemon|idaemon|rewrite) + [[ $# -eq 1 ]] || errusage '%s: invalid number of arguments' "$cmd" + $cmd "$@";; + client) + [[ $# -eq 2 ]] || errusage '%s: invalid number of arguments' "$cmd" + $cmd "$@";; + server) + [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd" + $cmd "$@";; + *) errusage 'unknown subcommand: %s' "$cmd";; + esac +} + +################################################################################ +# DISTCC_HOSTS parser # +################################################################################ + +# usage: parse_DISTCC_HOSTS true|false DISTCC_HOSTS +# parses DISTCC_HOSTS and: +# $1==true : It sets up port forwarding for inside the choot, sleep forever +# $1==false: Prints a modified version of DISTCC_HOSTS that uses the forwarded +# ports that were set up when $1==true. +parse_DISTCC_HOSTS() { + { [[ $# -eq 2 ]] && { [[ $1 == true ]] || [[ $1 == false ]]; }; } || panic + local forward_ports=$1 + local DISTCC_HOSTS=$2 + + local pids=() # child pids + local newhosts=() + local newport=8000 # next port to be used for port forwarding + + # This is based on the grammar specified in distcc(1) + local HOSTSPEC + for HOSTSPEC in $(sed 's/#.*//' <<<"$DISTCC_HOSTS"); do + case "$HOSTSPEC" in + # LOCAL_HOST + localhost|localhost/*|--localslots=*|--localslots_cpp=*) + # "localhost" runs commands directly, not talking to distccd at + # localhost, use an IP or real hostname for that. + # So, just pass these through. + newhosts+=("$HOSTSPEC") + ;; + # SSH_HOST + *@*) + # SSH_HOST doesn't allow custom port numbers, and even if it + # did, ssh would complain about MITM. Instead, we'll count on + # ssh ProxyCommand being configured to used `client`. + newhosts+=("$HOSTSPEC") + ;; + # GLOBAL_OPTION + --*) + # pass these through + newhosts+=("$HOSTSPEC") + ;; + # ZEROCONF + +zeroconf) + error "%s does not support the +zeroconf option" "$0" + exit 1 + ;; + # TCP_HOST or OLDSTYLE_TCP_HOST + *) + declare HOSTID= PORT= LIMIT= OPTIONS= + if [[ $HOSTSPEC =~ ^([^:/]+)(:([0-9]+))?(/([0-9]+))?(,.*)?$ ]]; then + # TCP_HOST + HOSTID=${BASH_REMATCH[1]} + PORT=${BASH_REMATCH[3]} + LIMIT=${BASH_REMATCH[5]} + OPTIONS=${BASH_REMATCH[6]} + elif [[ $HOSTSPEC =~ ^([^:/]+)(/([0-9]+))?(:([0-9]+))?(,.*)?$ ]]; then + # OLDSTYLE_TCP_HOST + HOSTID=${BASH_REMATCH[1]} + LIMIT=${BASH_REMATCH[3]} + PORT=${BASH_REMATCH[5]} + OPTIONS=${BASH_REMATCH[6]} + else + error "Could not parse HOSTSPEC: %s" "$HOSTSPEC" + fi + + # set up port forwaring + if $forward_ports; then + socat TCP-LISTEN:${newport},fork SYSTEM:"$0 client $HOSTID ${PORT:-3632}" & + pids+=($!) + fi + + # add the forwarded port + local newhost="127.0.0.1:$newport" + [[ -z $LIMIT ]] || newhost+="/$LIMIT" + [[ -z $OPTIONS ]] || newhost+="$OPTIONS" + newhosts+=("$newhost") + : $((newport++)) + ;; + esac + done + if $forward_ports; then + if [[ -z "${pids[*]}" ]]; then + # listen on port 8000, but immediatly close, just so that we are + # listening on something + socat TCP-LISTEN:${newport},fork SYSTEM:true & + pids+=($!) + fi + trap "kill -- ${pids[*]}" EXIT + wait "${pids[@]}" + else + printf '%s\n' "${newhosts[*]}" + fi +} + +################################################################################ +# Port forwarding primitives # +################################################################################ + +# Usage: server +# Reads "host port" from the first line of stdin, then connects stdio to the +# specified TCP socket. +server() { + [[ $# -eq 0 ]] || panic + while read -r host port; do + socat STDIO TCP:"$host:$port" + break + done +} + +# Usage: client HOST PORT +# For usage inside of a chroot. It talks through the UNIX-domain socket to an +# instance of `server` outside, in order to connect stdio to the specified TCP +# socket. +client() { + [[ $# -eq 2 ]] || panic + local file=/socket + { printf '%s\n' "$*"; cat; } | socat UNIX-CONNECT:"$file" STDIO +} + +################################################################################ +# High-level routines # +################################################################################ + +# Usage: odaemon CHROOTPATH +# Listens on "$CHROOTPATH/socket" and spawns a `server` for each new connection. +odaemon() { + [[ $# -eq 1 ]] || panic + local chrootpath=$1 + + umask 111 + socat UNIX-LISTEN:"$chrootpath/socket",fork SYSTEM:"$0 server" & + trap "kill -- $!; rm -f '$chrootpath/socket'" EXIT + wait +} + +# Usage: idaemon DISTCC_HOSTS +# Sets things up inside of the chroot to forward distcc hosts out. +idaemon() { + [[ $# -eq 1 ]] || panic + parse_DISTCC_HOSTS true "$1" +} + +# Usage: rewrite DISTCC_HOSTS +# Prints a modified version of DISTCC_HOSTS for inside the chroot. +rewrite() { + [[ $# -eq 1 ]] || panic + parse_DISTCC_HOSTS false "$1" +} + +main "$@" |