Merge remote branch 'qmp/for-anthony' into staging

This commit is contained in:
Anthony Liguori 2010-11-30 15:24:26 -06:00
commit 9233da785f
8 changed files with 483 additions and 124 deletions

View File

@ -19,10 +19,7 @@ o qmp-spec.txt QEMU Monitor Protocol current specification
o qmp-commands.txt QMP supported commands (auto-generated at build-time)
o qmp-events.txt List of available asynchronous events
There are also two simple Python scripts available:
o qmp-shell A shell
o vm-info Show some information about the Virtual Machine
There is also a simple Python script called 'qmp-shell' available.
IMPORTANT: It's strongly recommended to read the 'Stability Considerations'
section in the qmp-commands.txt file before making any serious use of QMP.

View File

@ -1,8 +1,8 @@
#!/usr/bin/python
#
# Simple QEMU shell on top of QMP
# Low-level QEMU shell on top of QMP.
#
# Copyright (C) 2009 Red Hat Inc.
# Copyright (C) 2009, 2010 Red Hat Inc.
#
# Authors:
# Luiz Capitulino <lcapitulino@redhat.com>
@ -14,60 +14,246 @@
#
# Start QEMU with:
#
# $ qemu [...] -monitor control,unix:./qmp,server
# # qemu [...] -qmp unix:./qmp-sock,server
#
# Run the shell:
#
# $ qmp-shell ./qmp
# $ qmp-shell ./qmp-sock
#
# Commands have the following format:
#
# < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
# < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
#
# For example:
#
# (QEMU) info item=network
# (QEMU) device_add driver=e1000 id=net1
# {u'return': {}}
# (QEMU)
import qmp
import readline
from sys import argv,exit
import sys
def shell_help():
print 'bye exit from the shell'
class QMPCompleter(list):
def complete(self, text, state):
for cmd in self:
if cmd.startswith(text):
if not state:
return cmd
else:
state -= 1
def main():
if len(argv) != 2:
print 'qemu-shell <unix-socket>'
exit(1)
class QMPShellError(Exception):
pass
qemu = qmp.QEMUMonitorProtocol(argv[1])
qemu.connect()
qemu.send("qmp_capabilities")
class QMPShellBadPort(QMPShellError):
pass
print 'Connected!'
# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
# _execute_cmd()). Let's design a better one.
class QMPShell(qmp.QEMUMonitorProtocol):
def __init__(self, address):
qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address))
self._greeting = None
self._completer = None
while True:
def __get_address(self, arg):
"""
Figure out if the argument is in the port:host form, if it's not it's
probably a file path.
"""
addr = arg.split(':')
if len(addr) == 2:
try:
port = int(addr[1])
except ValueError:
raise QMPShellBadPort
return ( addr[0], port )
# socket path
return arg
def _fill_completion(self):
for cmd in self.cmd('query-commands')['return']:
self._completer.append(cmd['name'])
def __completer_setup(self):
self._completer = QMPCompleter()
self._fill_completion()
readline.set_completer(self._completer.complete)
readline.parse_and_bind("tab: complete")
# XXX: default delimiters conflict with some command names (eg. query-),
# clearing everything as it doesn't seem to matter
readline.set_completer_delims('')
def __build_cmd(self, cmdline):
"""
Build a QMP input object from a user provided command-line in the
following format:
< command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
"""
cmdargs = cmdline.split()
qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
for arg in cmdargs[1:]:
opt = arg.split('=')
try:
value = int(opt[1])
except ValueError:
value = opt[1]
qmpcmd['arguments'][opt[0]] = value
return qmpcmd
def _execute_cmd(self, cmdline):
try:
cmd = raw_input('(QEMU) ')
qmpcmd = self.__build_cmd(cmdline)
except:
print 'command format: <command-name> ',
print '[arg-name1=arg1] ... [arg-nameN=argN]'
return True
resp = self.cmd_obj(qmpcmd)
if resp is None:
print 'Disconnected'
return False
print resp
return True
def connect(self):
self._greeting = qmp.QEMUMonitorProtocol.connect(self)
self.__completer_setup()
def show_banner(self, msg='Welcome to the QMP low-level shell!'):
print msg
version = self._greeting['QMP']['version']['qemu']
print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
def read_exec_command(self, prompt):
"""
Read and execute a command.
@return True if execution was ok, return False if disconnected.
"""
try:
cmdline = raw_input(prompt)
except EOFError:
print
break
if cmd == '':
continue
elif cmd == 'bye':
break
elif cmd == 'help':
shell_help()
return False
if cmdline == '':
for ev in self.get_events():
print ev
self.clear_events()
return True
else:
return self._execute_cmd(cmdline)
class HMPShell(QMPShell):
def __init__(self, address):
QMPShell.__init__(self, address)
self.__cpu_index = 0
def __cmd_completion(self):
for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
if cmd and cmd[0] != '[' and cmd[0] != '\t':
name = cmd.split()[0] # drop help text
if name == 'info':
continue
if name.find('|') != -1:
# Command in the form 'foobar|f' or 'f|foobar', take the
# full name
opt = name.split('|')
if len(opt[0]) == 1:
name = opt[1]
else:
name = opt[0]
self._completer.append(name)
self._completer.append('help ' + name) # help completion
def __info_completion(self):
for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
if cmd:
self._completer.append('info ' + cmd.split()[1])
def __other_completion(self):
# special cases
self._completer.append('help info')
def _fill_completion(self):
self.__cmd_completion()
self.__info_completion()
self.__other_completion()
def __cmd_passthrough(self, cmdline, cpu_index = 0):
return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
{ 'command-line': cmdline,
'cpu-index': cpu_index } })
def _execute_cmd(self, cmdline):
if cmdline.split()[0] == "cpu":
# trap the cpu command, it requires special setting
try:
resp = qemu.send(cmd)
if resp == None:
print 'Disconnected'
break
print resp
except IndexError:
print '-> command format: <command-name> ',
print '[arg-name1=arg1] ... [arg-nameN=argN]'
idx = int(cmdline.split()[1])
if not 'return' in self.__cmd_passthrough('info version', idx):
print 'bad CPU index'
return True
self.__cpu_index = idx
except ValueError:
print 'cpu command takes an integer argument'
return True
resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
if resp is None:
print 'Disconnected'
return False
assert 'return' in resp or 'error' in resp
if 'return' in resp:
# Success
if len(resp['return']) > 0:
print resp['return'],
else:
# Error
print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
return True
def show_banner(self):
QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
def die(msg):
sys.stderr.write('ERROR: %s\n' % msg)
sys.exit(1)
def fail_cmdline(option=None):
if option:
sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
sys.stderr.write('qemu-shell [ -H ] < UNIX socket path> | < TCP address:port >\n')
sys.exit(1)
def main():
addr = ''
try:
if len(sys.argv) == 2:
qemu = QMPShell(sys.argv[1])
addr = sys.argv[1]
elif len(sys.argv) == 3:
if sys.argv[1] != '-H':
fail_cmdline(sys.argv[1])
qemu = HMPShell(sys.argv[2])
addr = sys.argv[2]
else:
fail_cmdline()
except QMPShellBadPort:
die('bad port number in command-line')
try:
qemu.connect()
except qmp.QMPConnectError:
die('Didn\'t get QMP greeting message')
except qmp.QMPCapabilitiesError:
die('Could not negotiate capabilities')
except qemu.error:
die('Could not connect to %s' % addr)
qemu.show_banner()
while qemu.read_exec_command('(QEMU) '):
pass
qemu.close()
if __name__ == '__main__':
main()

View File

@ -1,6 +1,6 @@
# QEMU Monitor Protocol Python class
#
# Copyright (C) 2009 Red Hat Inc.
# Copyright (C) 2009, 2010 Red Hat Inc.
#
# Authors:
# Luiz Capitulino <lcapitulino@redhat.com>
@ -8,7 +8,9 @@
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
import socket, json
import json
import errno
import socket
class QMPError(Exception):
pass
@ -16,61 +18,114 @@ class QMPError(Exception):
class QMPConnectError(QMPError):
pass
class QMPCapabilitiesError(QMPError):
pass
class QEMUMonitorProtocol:
def connect(self):
self.sock.connect(self.filename)
data = self.__json_read()
if data == None:
raise QMPConnectError
if not data.has_key('QMP'):
raise QMPConnectError
return data['QMP']['capabilities']
def __init__(self, address):
"""
Create a QEMUMonitorProtocol class.
def close(self):
self.sock.close()
@param address: QEMU address, can be either a unix socket path (string)
or a tuple in the form ( address, port ) for a TCP
connection
@note No connection is established, this is done by the connect() method
"""
self.__events = []
self.__address = address
self.__sock = self.__get_sock()
self.__sockfile = self.__sock.makefile()
def send_raw(self, line):
self.sock.send(str(line))
return self.__json_read()
def send(self, cmdline):
cmd = self.__build_cmd(cmdline)
self.__json_send(cmd)
resp = self.__json_read()
if resp == None:
return
elif resp.has_key('error'):
return resp['error']
def __get_sock(self):
if isinstance(self.__address, tuple):
family = socket.AF_INET
else:
return resp['return']
def __build_cmd(self, cmdline):
cmdargs = cmdline.split()
qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
for arg in cmdargs[1:]:
opt = arg.split('=')
try:
value = int(opt[1])
except ValueError:
value = opt[1]
qmpcmd['arguments'][opt[0]] = value
return qmpcmd
def __json_send(self, cmd):
# XXX: We have to send any additional char, otherwise
# the Server won't read our input
self.sock.send(json.dumps(cmd) + ' ')
family = socket.AF_UNIX
return socket.socket(family, socket.SOCK_STREAM)
def __json_read(self):
try:
while True:
line = json.loads(self.sockfile.readline())
if not 'event' in line:
return line
except ValueError:
return
while True:
data = self.__sockfile.readline()
if not data:
return
resp = json.loads(data)
if 'event' in resp:
self.__events.append(resp)
continue
return resp
def __init__(self, filename):
self.filename = filename
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sockfile = self.sock.makefile()
error = socket.error
def connect(self):
"""
Connect to the QMP Monitor and perform capabilities negotiation.
@return QMP greeting dict
@raise socket.error on socket connection errors
@raise QMPConnectError if the greeting is not received
@raise QMPCapabilitiesError if fails to negotiate capabilities
"""
self.__sock.connect(self.__address)
greeting = self.__json_read()
if greeting is None or not greeting.has_key('QMP'):
raise QMPConnectError
# Greeting seems ok, negotiate capabilities
resp = self.cmd('qmp_capabilities')
if "return" in resp:
return greeting
raise QMPCapabilitiesError
def cmd_obj(self, qmp_cmd):
"""
Send a QMP command to the QMP Monitor.
@param qmp_cmd: QMP command to be sent as a Python dict
@return QMP response as a Python dict or None if the connection has
been closed
"""
try:
self.__sock.sendall(json.dumps(qmp_cmd))
except socket.error, err:
if err[0] == errno.EPIPE:
return
raise socket.error(err)
return self.__json_read()
def cmd(self, name, args=None, id=None):
"""
Build a QMP command and send it to the QMP Monitor.
@param name: command name (string)
@param args: command arguments (dict)
@param id: command id (dict, list, string or int)
"""
qmp_cmd = { 'execute': name }
if args:
qmp_cmd['arguments'] = args
if id:
qmp_cmd['id'] = id
return self.cmd_obj(qmp_cmd)
def get_events(self):
"""
Get a list of available QMP events.
"""
self.__sock.setblocking(0)
try:
self.__json_read()
except socket.error, err:
if err[0] == errno.EAGAIN:
# No data available
pass
self.__sock.setblocking(1)
return self.__events
def clear_events(self):
"""
Clear current list of pending events.
"""
self.__events = []
def close(self):
self.__sock.close()
self.__sockfile.close()

View File

@ -1,33 +0,0 @@
#!/usr/bin/python
#
# Print Virtual Machine information
#
# Usage:
#
# Start QEMU with:
#
# $ qemu [...] -monitor control,unix:./qmp,server
#
# Run vm-info:
#
# $ vm-info ./qmp
#
# Luiz Capitulino <lcapitulino@redhat.com>
import qmp
from sys import argv,exit
def main():
if len(argv) != 2:
print 'vm-info <unix-socket>'
exit(1)
qemu = qmp.QEMUMonitorProtocol(argv[1])
qemu.connect()
qemu.send("qmp_capabilities")
for cmd in [ 'version', 'kvm', 'status', 'uuid', 'balloon' ]:
print cmd + ': ' + str(qemu.send('query-' + cmd))
if __name__ == '__main__':
main()

View File

@ -491,6 +491,44 @@ static int do_qmp_capabilities(Monitor *mon, const QDict *params,
return 0;
}
static int mon_set_cpu(int cpu_index);
static void handle_user_command(Monitor *mon, const char *cmdline);
static int do_hmp_passthrough(Monitor *mon, const QDict *params,
QObject **ret_data)
{
int ret = 0;
Monitor *old_mon, hmp;
CharDriverState mchar;
memset(&hmp, 0, sizeof(hmp));
qemu_chr_init_mem(&mchar);
hmp.chr = &mchar;
old_mon = cur_mon;
cur_mon = &hmp;
if (qdict_haskey(params, "cpu-index")) {
ret = mon_set_cpu(qdict_get_int(params, "cpu-index"));
if (ret < 0) {
cur_mon = old_mon;
qerror_report(QERR_INVALID_PARAMETER_VALUE, "cpu-index", "a CPU number");
goto out;
}
}
handle_user_command(&hmp, qdict_get_str(params, "command-line"));
cur_mon = old_mon;
if (qemu_chr_mem_osize(hmp.chr) > 0) {
*ret_data = QOBJECT(qemu_chr_mem_to_qs(hmp.chr));
}
out:
qemu_chr_close_mem(hmp.chr);
return ret;
}
static int compare_cmd(const char *name, const char *list)
{
const char *p, *pstart;

View File

@ -2275,6 +2275,70 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
return NULL;
}
/***********************************************************/
/* Memory chardev */
typedef struct {
size_t outbuf_size;
size_t outbuf_capacity;
uint8_t *outbuf;
} MemoryDriver;
static int mem_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
{
MemoryDriver *d = chr->opaque;
/* TODO: the QString implementation has the same code, we should
* introduce a generic way to do this in cutils.c */
if (d->outbuf_capacity < d->outbuf_size + len) {
/* grow outbuf */
d->outbuf_capacity += len;
d->outbuf_capacity *= 2;
d->outbuf = qemu_realloc(d->outbuf, d->outbuf_capacity);
}
memcpy(d->outbuf + d->outbuf_size, buf, len);
d->outbuf_size += len;
return len;
}
void qemu_chr_init_mem(CharDriverState *chr)
{
MemoryDriver *d;
d = qemu_malloc(sizeof(*d));
d->outbuf_size = 0;
d->outbuf_capacity = 4096;
d->outbuf = qemu_mallocz(d->outbuf_capacity);
memset(chr, 0, sizeof(*chr));
chr->opaque = d;
chr->chr_write = mem_chr_write;
}
QString *qemu_chr_mem_to_qs(CharDriverState *chr)
{
MemoryDriver *d = chr->opaque;
return qstring_from_substr((char *) d->outbuf, 0, d->outbuf_size - 1);
}
/* NOTE: this driver can not be closed with qemu_chr_close()! */
void qemu_chr_close_mem(CharDriverState *chr)
{
MemoryDriver *d = chr->opaque;
qemu_free(d->outbuf);
qemu_free(chr->opaque);
chr->opaque = NULL;
chr->chr_write = NULL;
}
size_t qemu_chr_mem_osize(const CharDriverState *chr)
{
const MemoryDriver *d = chr->opaque;
return d->outbuf_size;
}
QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename)
{
char host[65], port[33], width[8], height[8];

View File

@ -6,6 +6,7 @@
#include "qemu-option.h"
#include "qemu-config.h"
#include "qobject.h"
#include "qstring.h"
/* character device */
@ -100,6 +101,12 @@ CharDriverState *qemu_chr_open_eventfd(int eventfd);
extern int term_escape_char;
/* memory chardev */
void qemu_chr_init_mem(CharDriverState *chr);
void qemu_chr_close_mem(CharDriverState *chr);
QString *qemu_chr_mem_to_qs(CharDriverState *chr);
size_t qemu_chr_mem_osize(const CharDriverState *chr);
/* async I/O support */
int qemu_set_fd_handler2(int fd,

View File

@ -761,6 +761,51 @@ Example:
Note: This command must be issued before issuing any other command.
EQMP
{
.name = "human-monitor-command",
.args_type = "command-line:s,cpu-index:i?",
.params = "",
.help = "",
.user_print = monitor_user_noop,
.mhandler.cmd_new = do_hmp_passthrough,
},
SQMP
human-monitor-command
---------------------
Execute a Human Monitor command.
Arguments:
- command-line: the command name and its arguments, just like the
Human Monitor's shell (json-string)
- cpu-index: select the CPU number to be used by commands which access CPU
data, like 'info registers'. The Monitor selects CPU 0 if this
argument is not provided (json-int, optional)
Example:
-> { "execute": "human-monitor-command", "arguments": { "command-line": "info kvm" } }
<- { "return": "kvm support: enabled\r\n" }
Notes:
(1) The Human Monitor is NOT an stable interface, this means that command
names, arguments and responses can change or be removed at ANY time.
Applications that rely on long term stability guarantees should NOT
use this command
(2) Limitations:
o This command is stateless, this means that commands that depend
on state information (such as getfd) might not work
o Commands that prompt the user for data (eg. 'cont' when the block
device is encrypted) don't currently work
3. Query Commands
=================