tame/bin/tamed

218 lines
4.8 KiB
Bash
Executable File

#!/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 <http://www.gnu.org/licenses/>.
##
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 <<EOF
Usage: $0 [--kill] [runpath]
Start tamed and runners. Do not fork into background process.
The default value of RUNPATH is \`/run/user/$UID/tamed'.
Only one runner is currently supported.
Options:
--kill kill a runing tamed at path RUNPATH
--help show this message
EOF
exit $EX_USAGE
}
# Run tamed
main()
{
local kill=
case "${1:-}" in
--kill) kill=1; shift;;
--help) usage;;
esac
root="${1:-/run/user/$UID/tamed}"
# kill if requested
test -z "$kill" || {
kill-running "$root"
exit
}
abort-if-running "$root"
# clean up background processes before we exit
trap exit TERM
trap cleanup EXIT
# start fresh
rm -rf "$root"; mkdir -p "$root"
echo $$ > "$root/pid"
# only a single runner for now
spawn-runner 0 "$root"
wait -n
}
main "$@"