0

I'm running QEMU virtual machine in -daemonize mode and then spawn an arbitrary foreground (possibly interactive) process intended to interact with the QEMU instance. In general once foreground process completes I do a cleanup of QEMU instance via pidfile:

qemu-system ... -pidfile ./qemu.pid -daemonize

/my/custom/interactive/process

pkill -F ./qemu.pid

However in some scenarios QEMU could exit independently and my foreground process continues to run. But I would like to stop it in case. So the behavior of my custom interactive process should be like:

tail -f --pid=./qemu.pid /dev/null

How can I do that nicely? Maybe there is some kind of timeout-like wrapper, so I could run something like:

trackpid ./qemu.pid /my/custom/interactive/process
4
  • Would polling be acceptable - so periodically you would check to see if the child qemu process still existed?
    – roaima
    Sep 7 at 13:35
  • 1
    what does the foreground process do? If it communicates with the qemu instance, doesn't it notice qemu going away (and the socket/whatever it uses to communicate closing)?
    – ilkkachu
    Sep 7 at 13:40
  • @ilkkachu I'm developing kind of framework to run interacting scripts. In some cases it could be ssh or other means to connect to QEMU machine. Obviously in this case process got noticed about disconnect. But it could be any other process, i.e. sleep inf or /bin/sh, or there maybe a long sleep ... between ssh invocations.
    – reddot
    Sep 7 at 14:22
  • @roaima I'm looking for options and if there are no concise/sleeping options I have to choose other variant. So could you please share an example of code?
    – reddot
    Sep 7 at 14:24
1

You could poll for the qemu process disappearing, and terminate early when it does. This is quick-tested code, and notably not tested with qemu-system.

There's also quite a lot of it. You can remove the #DEBUG lines, but if you are interested in seeing how it all hangs together, uncomment them and compare the program output with the code.

#!/bin/bash

InvokeQemu()
{
    local i pid pidFile=qemu.pid

    # Start the qemu process, and return the PID if possible
    #
    (
        # qemu-system ... -pidFile "$pidFile" -daemonize
        ( sleep 30 & sleep 0.5 && echo $! >"$pidFile" )    # FAKE IT for half a minute
    ) >/dev/null 2>&1 </dev/null

    #echo "InvokeQemu: checking for successful daemonisation" >&2    #DEBUG
    for i in 1 2 3
    do
        # Does the PID file exist yet
        #echo "InvokeQemu: attempt $i" >&2    #DEBUG
        if [[ -s "$pidFile" ]] && pid=$(cat "$pidFile") && [[ -n "$pid" ]]
        then
            printf "%s\n" $pid
            #echo "InvokeQemu: pid=$pid" >&2    #DEBUG
            return 0
        fi

        # Pause a moment or so before trying again
        sleep 2
    done
    return 1
}

MonitorPIDs()
{
    local pid

    for pid in "$@"
    do
        #echo "MonitorPIDs: checking pid $pid" >&2    #DEBUG
        if err=$(kill -0 "$pid" 2>&1) || [[ "$err" == *permitted* || "$err" == *denied* ]]
        then
            # Process still exists
            :
            #echo "MonitorPIDs: pid $pid still alive" >&2    #DEBUG
        else
            #echo "MonitorPIDs: pid $pid has died" >&2    #DEBUG
            echo "$pid"
            return 1
        fi
    done
    #echo "MonitorPIDs: all good" >&2    #DEBUG
    return 0
}

########################################################################
# Go
myPid=$$

# Start the qemu emulator
echo "Starting qemu emulator"
qemuPid=$(InvokeQemu)

if [[ -z "$qemuPid" ]]
then
    echo "Could not start qemu" >&2
    exit 1
fi

# Start the monitor process
#
# Once any of them is no longer running it will fire SIGTERM to its
# remaining PIDs and then exit
echo "Starting monitor process"
(
    while MonitorPIDs $qemuPid $myPid >/dev/null
    do
        #echo "(Monitor): all good" >&2    #DEBUG
        sleep 2
    done
    kill $qemuPid $myPid 2>/dev/null
) &

# Start your interactive foreground process
#
# You will receive SIGTERM within a few seconds of the emulator exiting,
# so you may want to trap that
echo "Starting interactive process"
while read -p "What do you want to do? " x
do
    echo "OK"
    sleep 1
done
exit 0
1
  • Thank you for the detailed script. I've posted my own solution. It mostly resembles your one but being more concise and relying on tail utility as a pidfile monitor.
    – reddot
    Sep 8 at 10:34
0

Finally I've ended up with following code:

qemu-system ... -pidfile ./qemu.pid -daemonize

{
  tail -f --pidfile="$(cat ./qemu.pid)" /dev/null
  kill -INT 0
} &

/my/custom/interactive/process
kill $!

pkill -F ./qemu.pid

Script in curly braces is actually a pidfile monitor running in a background. Once pid is gone monitor kill current process group. I've used kill -INT 0 because it gives most reliable and clean result for me.

Other options were:

kill -- 0 (kill with TERM signal, doesn't correctly terminate interactive process)

kill -INT $$ (kill only shell process, doesn't correctly terminate interactive process)

kill -- -$$ (kill process group denoted by pid of shell, doesn't always work correctly, I assume due to invocation with sudo acting as process group leader)

pkill -P $$ (kill only child process, actually works but I preferred to use shell built-in and rely on Ctrl-C processing behavior).

Another point is that if my interactive process has completed on it's own I have to kill monitor process to avoid further inference on exit and cleanup of the script.

2
  • @roaima aren't you're confused with kill -0 ...?
    – reddot
    Sep 8 at 15:45
  • Reddot slightly, yes, sorry
    – roaima
    Sep 8 at 21:10

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.