summaryrefslogtreecommitdiff
path: root/src/chroot-tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/chroot-tools')
-rwxr-xr-xsrc/chroot-tools/distcc-tool252
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 "$@"