aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2010-11-30 15:24:26 -0600
committerAnthony Liguori <aliguori@us.ibm.com>2010-11-30 15:24:26 -0600
commit9233da785f55c924c5850cd1ce1b7f5f200d631b (patch)
tree3fade99ba46241e37c8ec7cb459989f0ec142fe0
parentfd5d5c566af040be15f2cae1b14a47919974453d (diff)
parenta6f9dd02f75685934ca52f038b6fcb38b661c283 (diff)
Merge remote branch 'qmp/for-anthony' into staging
-rw-r--r--QMP/README5
-rwxr-xr-xQMP/qmp-shell254
-rw-r--r--QMP/qmp.py157
-rwxr-xr-xQMP/vm-info33
-rw-r--r--monitor.c38
-rw-r--r--qemu-char.c64
-rw-r--r--qemu-char.h7
-rw-r--r--qmp-commands.hx45
8 files changed, 481 insertions, 122 deletions
diff --git a/QMP/README b/QMP/README
index 80503f2d7a..c95a08c234 100644
--- a/QMP/README
+++ b/QMP/README
@@ -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.
diff --git a/QMP/qmp-shell b/QMP/qmp-shell
index a5b72d15d1..42dabc8c6d 100755
--- a/QMP/qmp-shell
+++ b/QMP/qmp-shell
@@ -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
+
+class QMPShellBadPort(QMPShellError):
+ pass
+
+# 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
+
+ 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('')
- qemu = qmp.QEMUMonitorProtocol(argv[1])
- qemu.connect()
- qemu.send("qmp_capabilities")
+ 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:
+ 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()
- print 'Connected!'
+ 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'])
- while True:
+ def read_exec_command(self, prompt):
+ """
+ Read and execute a command.
+
+ @return True if execution was ok, return False if disconnected.
+ """
try:
- cmd = raw_input('(QEMU) ')
+ 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()
diff --git a/QMP/qmp.py b/QMP/qmp.py
index 4062f84f36..14ce8b0d05 100644
--- a/QMP/qmp.py
+++ b/QMP/qmp.py
@@ -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 __init__(self, address):
+ """
+ Create a QEMUMonitorProtocol class.
+
+ @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 __get_sock(self):
+ if isinstance(self.__address, tuple):
+ family = socket.AF_INET
+ else:
+ family = socket.AF_UNIX
+ return socket.socket(family, socket.SOCK_STREAM)
+
+ def __json_read(self):
+ 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
+
+ error = socket.error
+
def connect(self):
- self.sock.connect(self.filename)
- data = self.__json_read()
- if data == None:
- raise QMPConnectError
- if not data.has_key('QMP'):
+ """
+ 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
- return data['QMP']['capabilities']
+ # Greeting seems ok, negotiate capabilities
+ resp = self.cmd('qmp_capabilities')
+ if "return" in resp:
+ return greeting
+ raise QMPCapabilitiesError
- def close(self):
- self.sock.close()
+ def cmd_obj(self, qmp_cmd):
+ """
+ Send a QMP command to the QMP Monitor.
- def send_raw(self, line):
- self.sock.send(str(line))
+ @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 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']
- 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) + ' ')
+ def cmd(self, name, args=None, id=None):
+ """
+ Build a QMP command and send it to the QMP Monitor.
- def __json_read(self):
+ @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:
- while True:
- line = json.loads(self.sockfile.readline())
- if not 'event' in line:
- return line
- except ValueError:
- return
-
- def __init__(self, filename):
- self.filename = filename
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self.sockfile = self.sock.makefile()
+ 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()
diff --git a/QMP/vm-info b/QMP/vm-info
deleted file mode 100755
index be5b03843c..0000000000
--- a/QMP/vm-info
+++ /dev/null
@@ -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()
diff --git a/monitor.c b/monitor.c
index 8cee35d77a..ec31eac8c1 100644
--- a/monitor.c
+++ b/monitor.c
@@ -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;
diff --git a/qemu-char.c b/qemu-char.c
index 88997f9d5a..edc9ad6851 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -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];
diff --git a/qemu-char.h b/qemu-char.h
index 18ad12bb5d..e6ee6c4bc9 100644
--- a/qemu-char.h
+++ b/qemu-char.h
@@ -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,
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 793cf1c0f9..e5f157fe10 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -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
=================