| #!/usr/bin/env bash |
| # |
| # This allows for launching of multiple QEMU instances, with independent |
| # communication possible to each instance. |
| # |
| # Each instance can choose, at launch, to use either the QMP or the |
| # HMP (monitor) interface. |
| # |
| # All instances are cleaned up via _cleanup_qemu, including killing the |
| # running qemu instance. |
| # |
| # Copyright (C) 2014 Red Hat, Inc. |
| # |
| # 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 2 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/>. |
| # |
| |
| QEMU_COMM_TIMEOUT=10 |
| |
| QEMU_FIFO_IN="${QEMU_TEST_DIR}/qmp-in-$$" |
| QEMU_FIFO_OUT="${QEMU_TEST_DIR}/qmp-out-$$" |
| |
| QEMU_HANDLE=0 |
| export _QEMU_HANDLE=0 |
| |
| # If bash version is >= 4.1, these will be overwritten and dynamic |
| # file descriptor values assigned. |
| _out_fd=3 |
| _in_fd=4 |
| |
| # Wait for expected QMP response from QEMU. Will time out |
| # after 10 seconds, which counts as failure. |
| # |
| # Override QEMU_COMM_TIMEOUT for a timeout different than the |
| # default 10 seconds |
| # |
| # $1: The handle to use |
| # $2+ All remaining arguments comprise the string to search for |
| # in the response. |
| # |
| # If $silent is set to anything but an empty string, then |
| # response is not echoed out. |
| # If $mismatch_only is set, only non-matching responses will |
| # be echoed. |
| # |
| # If $capture_events is non-empty, then any QMP event names it lists |
| # will not be echoed out, but instead collected in the $QEMU_EVENTS |
| # variable. The _wait_event function can later be used to receive |
| # the cached events. |
| # |
| # If $only_capture_events is set to anything but an empty string, |
| # then an error will be raised if a QMP message is seen which is |
| # not an event listed in $capture_events. |
| # |
| # If $success_or_failure is set, the meaning of the arguments is |
| # changed as follows: |
| # $2: A string to search for in the response; if found, this indicates |
| # success and ${QEMU_STATUS[$1]} is set to 0. |
| # $3: A string to search for in the response; if found, this indicates |
| # failure and the test is either aborted (if $qemu_error_no_exit |
| # is not set) or ${QEMU_STATUS[$1]} is set to -1 (otherwise). |
| _timed_wait_for() |
| { |
| local h=${1} |
| shift |
| |
| if [ -z "${success_or_failure}" ]; then |
| success_match=${*} |
| failure_match= |
| else |
| success_match=${1} |
| failure_match=${2} |
| fi |
| |
| timeout=yes |
| |
| QEMU_STATUS[$h]=0 |
| while IFS= read -t ${QEMU_COMM_TIMEOUT} resp <&${QEMU_OUT[$h]} |
| do |
| if [ -n "$capture_events" ]; then |
| capture=0 |
| local evname |
| for evname in $capture_events |
| do |
| case ${resp} in |
| *\"event\":\ \"${evname}\"* ) capture=1 ;; |
| esac |
| done |
| if [ $capture = 1 ]; |
| then |
| ev=$(echo "${resp}" | tr -d '\r' | tr % .) |
| QEMU_EVENTS="${QEMU_EVENTS:+${QEMU_EVENTS}%}${ev}" |
| if [ -n "$only_capture_events" ]; then |
| return |
| else |
| continue |
| fi |
| fi |
| fi |
| if [ -n "$only_capture_events" ]; then |
| echo "Only expected $capture_events but got ${resp}" |
| exit 1 |
| fi |
| |
| if [ -z "${silent}" ] && [ -z "${mismatch_only}" ]; then |
| echo "${resp}" | _filter_testdir | _filter_qemu \ |
| | _filter_qemu_io | _filter_qmp | _filter_hmp |
| fi |
| if [ -n "${failure_match}" ]; then |
| grep -q "${failure_match}" < <(echo "${resp}") |
| if [ $? -eq 0 ]; then |
| timeout= |
| break |
| fi |
| fi |
| grep -q "${success_match}" < <(echo "${resp}") |
| if [ $? -eq 0 ]; then |
| return |
| fi |
| if [ -z "${silent}" ] && [ -n "${mismatch_only}" ]; then |
| echo "${resp}" | _filter_testdir | _filter_qemu \ |
| | _filter_qemu_io | _filter_qmp | _filter_hmp |
| fi |
| |
| done |
| QEMU_STATUS[$h]=-1 |
| if [ -z "${qemu_error_no_exit}" ]; then |
| if [ -n "${timeout}" ]; then |
| echo "Timeout waiting for ${success_match} on handle ${h}" |
| else |
| echo "Wrong response matching ${failure_match} on handle ${h}" |
| fi |
| exit 1 # Timeout or wrong match mean the test failed |
| fi |
| } |
| |
| |
| # Sends QMP or HMP command to QEMU, and waits for the expected response |
| # |
| # $1: QEMU handle to use |
| # $2: String of the QMP command to send |
| # ${@: -1} (Last string passed) |
| # String that the QEMU response should contain. If it is a null |
| # string, do not wait for a response |
| # |
| # Set qemu_cmd_repeat to the number of times to repeat the cmd |
| # until either timeout, or a response. If it is not set, or <=0, |
| # then the command is only sent once. |
| # |
| # If neither $silent nor $mismatch_only is set, and $cmd begins with '{', |
| # echo the command before sending it the first time. |
| # |
| # If $qemu_error_no_exit is set, then even if the expected response |
| # is not seen, we will not exit. $QEMU_STATUS[$1] will be set it -1 in |
| # that case. |
| # |
| # If $success_or_failure is set, then the last two strings are the |
| # strings the response will be scanned for. The first of the two |
| # indicates success, the latter indicates failure. Failure is handled |
| # like a timeout. |
| _send_qemu_cmd() |
| { |
| local h=${1} |
| local count=1 |
| local cmd= |
| local use_error=${qemu_error_no_exit} |
| shift |
| |
| if [ ${qemu_cmd_repeat} -gt 0 ] 2>/dev/null; then |
| count=${qemu_cmd_repeat} |
| use_error="no" |
| fi |
| |
| cmd=$1 |
| shift |
| |
| # Display QMP being sent, but not HMP (since HMP already echoes its |
| # input back to output); decide based on leading '{' |
| if [ -z "$silent" ] && [ -z "$mismatch_only" ] && |
| [ "$cmd" != "${cmd#\{}" ]; then |
| echo "${cmd}" | _filter_testdir | _filter_imgfmt |
| fi |
| while [ ${count} -gt 0 ] |
| do |
| echo "${cmd}" >&${QEMU_IN[${h}]} |
| if [ -n "${1}" ]; then |
| if [ -z "${success_or_failure}" ]; then |
| qemu_error_no_exit=${use_error} _timed_wait_for ${h} "${1}" |
| else |
| qemu_error_no_exit=${use_error} _timed_wait_for ${h} "${1}" "${2}" |
| fi |
| if [ ${QEMU_STATUS[$h]} -eq 0 ]; then |
| return |
| fi |
| fi |
| let count--; |
| done |
| if [ ${QEMU_STATUS[$h]} -ne 0 ] && [ -z "${qemu_error_no_exit}" ]; then |
| echo "Timeout waiting for command ${1} response on handle ${h}" |
| exit 1 #Timeout means the test failed |
| fi |
| } |
| |
| |
| # Check event cache for a named QMP event |
| # |
| # Input parameters: |
| # $1: Name of the QMP event to check for |
| # |
| # Checks if the named QMP event that was previously captured |
| # into $QEMU_EVENTS. When matched, the QMP event will be echoed |
| # and the $matched variable set to 1. |
| # |
| # _wait_event is more suitable for test usage in most cases |
| _check_cached_events() |
| { |
| local evname=${1} |
| |
| local match="\"event\": \"$evname\"" |
| |
| matched=0 |
| if [ -n "$QEMU_EVENTS" ]; then |
| CURRENT_QEMU_EVENTS=$QEMU_EVENTS |
| QEMU_EVENTS= |
| old_IFS=$IFS |
| IFS="%" |
| for ev in $CURRENT_QEMU_EVENTS |
| do |
| grep -q "$match" < <(echo "${ev}") |
| if [ $? -eq 0 ] && [ $matched = 0 ]; then |
| echo "${ev}" | _filter_testdir | _filter_qemu \ |
| | _filter_qemu_io | _filter_qmp | _filter_hmp |
| matched=1 |
| else |
| QEMU_EVENTS="${QEMU_EVENTS:+${QEMU_EVENTS}%}${ev}" |
| fi |
| done |
| IFS=$old_IFS |
| fi |
| } |
| |
| # Wait for a named QMP event |
| # |
| # Input parameters: |
| # $1: QEMU handle to use |
| # $2: Name of the QMP event to wait for |
| # |
| # Checks if the named QMP even was previously captured |
| # into $QEMU_EVENTS. If none are present, then waits for the |
| # event to arrive on the QMP channel. When matched, the QMP |
| # event will be echoed |
| _wait_event() |
| { |
| local h=${1} |
| local evname=${2} |
| |
| while true |
| do |
| _check_cached_events $evname |
| |
| if [ $matched = 1 ]; |
| then |
| return |
| fi |
| |
| only_capture_events=1 qemu_error_no_exit=1 _timed_wait_for ${h} |
| |
| if [ ${QEMU_STATUS[$h]} -ne 0 ] ; then |
| echo "Timeout waiting for event ${evname} on handle ${h}" |
| exit 1 #Timeout means the test failed |
| fi |
| done |
| } |
| |
| # Launch a QEMU process. |
| # |
| # Input parameters: |
| # $qemu_comm_method: set this variable to 'monitor' (case insensitive) |
| # to use the QEMU HMP monitor for communication. |
| # Otherwise, the default of QMP is used. |
| # $qmp_pretty: Set this variable to 'y' to enable QMP pretty printing. |
| # $keep_stderr: Set this variable to 'y' to keep QEMU's stderr output on stderr. |
| # If this variable is empty, stderr will be redirected to stdout. |
| # Returns: |
| # $QEMU_HANDLE: set to a handle value to communicate with this QEMU instance. |
| # |
| _launch_qemu() |
| { |
| local comm= |
| local fifo_out= |
| local fifo_in= |
| |
| if (shopt -s nocasematch; [[ "${qemu_comm_method}" == "monitor" ]]) |
| then |
| comm="-monitor stdio" |
| else |
| local qemu_comm_method="qmp" |
| if [ "$qmp_pretty" = "y" ]; then |
| comm="-monitor none -qmp-pretty stdio" |
| else |
| comm="-monitor none -qmp stdio" |
| fi |
| fi |
| |
| fifo_out=${QEMU_FIFO_OUT}_${_QEMU_HANDLE} |
| fifo_in=${QEMU_FIFO_IN}_${_QEMU_HANDLE} |
| mkfifo "${fifo_out}" |
| mkfifo "${fifo_in}" |
| |
| object_options= |
| if [ -n "$IMGKEYSECRET" ]; then |
| object_options="--object secret,id=keysec0,data=$IMGKEYSECRET" |
| fi |
| |
| if [ -z "$keep_stderr" ]; then |
| QEMU_NEED_PID='y'\ |
| ${QEMU} ${object_options} -nographic -serial none ${comm} "${@}" >"${fifo_out}" \ |
| 2>&1 \ |
| <"${fifo_in}" & |
| elif [ "$keep_stderr" = "y" ]; then |
| QEMU_NEED_PID='y'\ |
| ${QEMU} ${object_options} -nographic -serial none ${comm} "${@}" >"${fifo_out}" \ |
| <"${fifo_in}" & |
| else |
| exit 1 |
| fi |
| |
| if [[ "${BASH_VERSINFO[0]}" -ge "5" || |
| ("${BASH_VERSINFO[0]}" -ge "4" && "${BASH_VERSINFO[1]}" -ge "1") ]] |
| then |
| # bash >= 4.1 required for automatic fd |
| exec {_out_fd}<"${fifo_out}" |
| exec {_in_fd}>"${fifo_in}" |
| else |
| let _out_fd++ |
| let _in_fd++ |
| eval "exec ${_out_fd}<'${fifo_out}'" |
| eval "exec ${_in_fd}>'${fifo_in}'" |
| fi |
| |
| QEMU_OUT[${_QEMU_HANDLE}]=${_out_fd} |
| QEMU_IN[${_QEMU_HANDLE}]=${_in_fd} |
| QEMU_STATUS[${_QEMU_HANDLE}]=0 |
| |
| if [ "${qemu_comm_method}" == "qmp" ] |
| then |
| # Don't print response, since it has version information in it |
| silent=yes _timed_wait_for ${_QEMU_HANDLE} "capabilities" |
| if [ "$qmp_pretty" = "y" ]; then |
| silent=yes _timed_wait_for ${_QEMU_HANDLE} "^}" |
| fi |
| fi |
| QEMU_HANDLE=${_QEMU_HANDLE} |
| let _QEMU_HANDLE++ |
| } |
| |
| |
| # Silently kills the QEMU process |
| # |
| # If $wait is set to anything other than the empty string, the process will not |
| # be killed but only waited for, and any output will be forwarded to stdout. If |
| # $wait is empty, the process will be killed and all output will be suppressed. |
| _cleanup_qemu() |
| { |
| # QEMU_PID[], QEMU_IN[], QEMU_OUT[] all use same indices |
| for i in "${!QEMU_OUT[@]}" |
| do |
| local QEMU_PID |
| if [ -f "${QEMU_TEST_DIR}/qemu-${i}.pid" ]; then |
| read QEMU_PID < "${QEMU_TEST_DIR}/qemu-${i}.pid" |
| rm -f "${QEMU_TEST_DIR}/qemu-${i}.pid" |
| if [ -z "${wait}" ] && [ -n "${QEMU_PID}" ]; then |
| kill -KILL ${QEMU_PID} 2>/dev/null |
| fi |
| if [ -n "${QEMU_PID}" ]; then |
| wait ${QEMU_PID} 2>/dev/null # silent kill |
| fi |
| fi |
| |
| if [ -n "${wait}" ]; then |
| cat <&${QEMU_OUT[$i]} | _filter_testdir | _filter_qemu \ |
| | _filter_qemu_io | _filter_qmp | _filter_hmp |
| fi |
| rm -f "${QEMU_FIFO_IN}_${i}" "${QEMU_FIFO_OUT}_${i}" |
| eval "exec ${QEMU_IN[$i]}<&-" # close file descriptors |
| eval "exec ${QEMU_OUT[$i]}<&-" |
| |
| unset QEMU_IN[$i] |
| unset QEMU_OUT[$i] |
| done |
| } |