diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/chroot-tools/distcc-tool | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/chroot-tools/distcc-tool b/src/chroot-tools/distcc-tool new file mode 100755 index 0000000..840a5aa --- /dev/null +++ b/src/chroot-tools/distcc-tool @@ -0,0 +1,252 @@ +#!/bin/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 +# - grep: any version +# - mkdir: any version +# - rm: any version +# - sed: any version +# - sleep: any version +# On Parabola, this means the packages: +# bash, coreutils, grep, sed, socat + +panic() { + echo 'panic: malformed call to internal function' >&2 + exit 1 +} + +error() { + fmt=$1; shift + printf "ERROR: $fmt\n" "$@" >&2 + exit 1 +} + +usage() { + echo "Usage: $0 COMMAND [COMMAND-ARGS]" + echo "Tool for using distcc within a networkless chroot" + echo + echo "Commands:" + echo ' help print this message' + echo ' odaemon CHROOTPATH daemon to run outside of the chroot' + echo ' idaemon DISTCC_HOSTS daemon to run inside of the chroot' + echo ' rewrite DISTCC_HOSTS prints a rewritten version of DISTCC_HOSTS' + echo ' client HOST PORT connects stdio to TCP:$HOST:$PORT' + echo 'Commands: for internal use' + echo ' server counterpart to client; spawned by odaemon' +} + +errusage() { + if [[ $# -gt 0 ]]; then + fmt=$1; shift + printf "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 configure an + # ssh ProxyCommand so that ssh works fine. + newhosts+=("$HOSTSPEC") + ;; + # GLOBAL_OPTION + --*) + # pass these through + newhosts+=("$HOSTSPEC") + ;; + # ZEROCONF + +zeroconf) + error "$0 does not support the +zeroconf option" + 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: configure_ssh +# Edits ~/.ssh/config to use `client` as a ProxyCommand so that ssh can work +# inside of the chroot. +configure_ssh() { + [[ $# -eq 0 ]] || panic + if ! grep "^\s*ProxyCommand $0 client" ~/.ssh/config &>/dev/null; then + [[ -d ~/.ssh ]] || mkdir ~/.ssh + echo 'Host *' >> ~/.ssh/config + echo " ProxyCommand $0 client %h %p" >> ~/.ssh/config + fi +} + +# Usage: idaemon DISTCC_HOSTS +# Sets things up inside of the chroot to forward distcc hosts out. +# It configures an ssh ProxyCommand. +idaemon() { + [[ $# -eq 1 ]] || panic + local DISTCC_HOSTS=$1 + + configure_ssh + + parse_DISTCC_HOSTS true "$DISTCC_HOSTS" +} + +# Usage: odaemon CHROOTPATH +# Listens on "$CHROOTPATH/socket" and spawns a `server` for each new connection. +odaemon() { + [[ $# -eq 1 ]] || panic + local chrootpath=$1 + + trap "rm -f '$chrootpath/socket'" EXIT + socat UNIX-LISTEN:"$chrootpath/socket",fork SYSTEM:"$0 server" +} + +# 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 "$@" |