#!/bin/bash # Daemon for accepting TAME commands (compilers, linker, etc) # # Copyright (C) 2018 R-T Specialty, LLC. # # This program 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. # # This program 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 this program. If not, see . ## set -euo pipefail declare -r mypath=$( dirname "$( readlink -f "$0" )" ) declare -ri EX_RUNNING=1 declare -ri EX_USAGE=64 # incorrect usage; sysexits.h declare -ri EX_CANTCREAT=73 # cannot create file; sysexits.h # set by `main', global for `cleanup' declare root= # Create FIFOs for runner # # The FIFOs are intended to be attached to stderr and stdout # of the runner and will be created relative to the given # root path ROOT. # # If a FIFO cannot be created, exit with EX_CANTCREAT. mkfifos() { local -r root="${1?Missing root path}" mkdir -p "$root" # note that there's no stderr; see `add-runner' for n in 0 1; do rm -f "$root-$n" mkfifo -m 0600 "$root/$n" || { echo "fatal: failed to create FIFO at $in" exit $EX_CANTCREAT } done } # Spawn a runner # # A new runner is created by spawning dslc and attaching # new FIFOs under the given id ID relative to the given # run path ROOT. The PID of the runner will be stored # alongside the FIFOs in a pidfile `pid'. spawn-runner() { local -ri id="${1?Missing id}" local -r root="${2?Missing root run path}" local -r base="$root/$id" mkfifos "$base" # TODO: should we separate back out std{out,err}? # XXX: why does dslc quit (with a 0 exit code) occsionally? stdin? while true; do "$mypath/dslc" < <( persistent-cat "$base/0" ) \ >"$base/1" \ 2>&1 echo "warning: runner $id exited with code $?; restarting" done & echo "$!" > "$base/pid" echo "runner $id ($!): $base" } # Persistently read commands from FIFO IN # # This will continue to read from the FIFO as long as it is # readable. This is necessary since SIGPIPE gets sent to # processes reading/writing from/to the FIFO whenever a # process detaches from it. persistent-cat() { local -r in="${1?Missing input path}" while test -r "$in"; do read -r < "$in" || return echo "$REPLY" done } # Exit if tamed is already running at path ROOT # # If tamed is already running at ROOT, exit with status # EX_RUNNING; otherwise, do nothing except output a warning # if a stale pid file exists. abort-if-running() { local -r root="${1?Missing root rundir}" local -ri pid=$( cat "$root/pid" 2>/dev/null ) test "$pid" -gt 0 || return 0 ! ps "$pid" &>/dev/null || { echo "fatal: tamed is already running at $root (pid $pid)!" exit $EX_RUNNING } test -z "$pid" || { echo "warning: clearing stale tamed (pid $pid)" } } # Kill running tamed at path ROOT # # If no pidfile is found at ROOT, do nothing. This sends a # signal only to the parent tamed process, _not_ individual # runners; the target tamed is expected to clean up itself. # Consequently, if a tamed terminated abnormally without # cleaning up, this will not solve that problem. kill-running() { local -r root="${1?Missing root}" local -r pid=$( cat "$root"/pid 2>/dev/null ) test -n "$pid" || return 0 echo "killing tamed at $root ($pid)..." kill "$pid" } # Clean up child processes before exit # # This should be called before exit (perhaps by a trap). Kills # the entire process group. # # Do not attach this to a SIGTERM trap or it will infinitely # recurse. cleanup() { echo "killing remaining runners..." rm -rf "$root" kill 0 } # Output usage information and exit usage() { cat < "$root/pid" # only a single runner for now spawn-runner 0 "$root" wait -n } main "$@"