1

I'm running a gameserver program (SRCDS) that completely ignores SIGTERM. The only way to cleanly shut down the server is to type "quit" interactively.

Is there any way that I can wrap this program in a bash script that will catch SIGTERM and send "quit" to the STDIN of the program? Otherwise in normal operation the wrapper should forward STDIN and STDOUT as if wasn't there at all.

Diagram of what I'm trying to achieve:

Normal operation:

--- STDIN --->  |             |  --- STDIN --->  |                |
                | Bash Script |                  | Server Program |
<-- STDOUT ---  |             |  <-- STDOUT ---  |                |

SIGTERM sent:

                                     "quit"
--- STDIN --->  |             |  --- STDIN --->  |                |
-- SIGTERM -->  | Bash Script |                  | Server Program |
<-- STDOUT ---  |             |  <-- STDOUT ---  |                |
1

It depends on whether that program expects to be run from a terminal. If yes, I don't think you can do in bash; you should use something like expect. Example:

wrapper.expect:

puts "<<< pid=[pid] >>>"
spawn {*}$argv
trap {send "quit\n"} SIGTERM
interact

example with bc:

expect wrapper.expect bc -q

<<< pid=4771 >>>
spawn bc -q

A kill 4771 from another window will cause expect to send a quit line to bc. Notice that 4771 is the pid of the expect process, not of the bc subprocess.

Another option would be to use the TIOCSTI ioctl to inject the quit line in the current terminal; but again, you won't be able to do it from the shell; you still need some small C (or perl, python, etc) program just to call the ioctl() (and TIOCSTI has already been gutted in systems like OpenBSD).

  • Works perfectly, thank you very much! – JShorthouse Mar 25 at 21:48
1

Try this:

#!/bin/bash

coproc cat -n  # replace 'cat -n' with actual server program to be launched

# first some necessary file-descriptors fiddling
exec {srv_input}>&${COPROC[1]}-
exec {srv_output}<&${COPROC[0]}-

# background commands to relay normal stdin/stdout activity
cat <&0 >&${srv_input} &
cat <&${srv_output} >&1 &

# set signal handler up
term_received=false ; trap 'term_received=true' SIGTERM

# endless loop waiting for events
while true; do
    # wait for server to exit or sigterm received
    wait ${COPROC_PID}
    exit_status=$?
    # if sigterm received:
    if [ $exit_status -gt 128 ] && $term_received ; then
        # kill proxy command relaying stdin to server
        kill %2
        # send quit to server's stdin
        echo $'\n'quit >&${srv_input}
        # close server's stdin
        exec {srv_input}<&-
        # wait for actual server to exit
        wait ${COPROC_PID}
        exit $?
    # something else happened: kill proxy commands and exit with server's own exit status
    else
        kill %2
        kill %3
    fi
    exit $exit_status
done

I will add a fuller explanation if this script answers your question

  • Thank you, I will try this out when I get home. If the expect solution in the other answer works for me I will probably end up using that as it is much more succinct, but having never used coproc this is a very interesting answer and I will definitely have a play around with it later to learn how everything works. At a first glance the only confusing part to me is the wait ${COPROC_PID} line after the start of the while loop. To me it looks like this would halt execution until the server program exited. Can you explain to me how execution would continue from this point on a SIGTERM? – JShorthouse Mar 25 at 15:26
  • @JShorthouse Having installed a trap for SIGTERM, wait will proceed either on waited-process’s exit (as usual) or on SIGTERM caught and respective handler executed – LL3 Mar 25 at 16:13
  • @LL3Thanks, I've just found a further explanation here: How Bash interprets traps. "When Bash is waiting for an asynchronous command via the wait built-in, the reception of a signal for which a trap has been set will cause the wait built-in to return immediately with an exit status greater than 128, immediately after which the trap is executed." That makes the purpose of [ $exit_status -gt 128 ] clear, which is the other part I was unsure about. – JShorthouse Mar 25 at 16:25
  • @JShorthouse Exactly. The overall purpose of that if-block is to handle the case when the server process just exits on its own accord for whatever reason, and thus the script relays whatever exit status the server process exited with – LL3 Mar 25 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.