xref: /qemu/scripts/qmp/qmp-shell (revision a7430a0badc59bd6295936e06c1869e8fe32649d)
1#!/usr/bin/python
2#
3# Low-level QEMU shell on top of QMP.
4#
5# Copyright (C) 2009, 2010 Red Hat Inc.
6#
7# Authors:
8#  Luiz Capitulino <lcapitulino@redhat.com>
9#
10# This work is licensed under the terms of the GNU GPL, version 2.  See
11# the COPYING file in the top-level directory.
12#
13# Usage:
14#
15# Start QEMU with:
16#
17# # qemu [...] -qmp unix:./qmp-sock,server
18#
19# Run the shell:
20#
21# $ qmp-shell ./qmp-sock
22#
23# Commands have the following format:
24#
25#    < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
26#
27# For example:
28#
29# (QEMU) device_add driver=e1000 id=net1
30# {u'return': {}}
31# (QEMU)
32
33import qmp
34import json
35import readline
36import sys
37import pprint
38
39class QMPCompleter(list):
40    def complete(self, text, state):
41        for cmd in self:
42            if cmd.startswith(text):
43                if not state:
44                    return cmd
45                else:
46                    state -= 1
47
48class QMPShellError(Exception):
49    pass
50
51class QMPShellBadPort(QMPShellError):
52    pass
53
54# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
55#       _execute_cmd()). Let's design a better one.
56class QMPShell(qmp.QEMUMonitorProtocol):
57    def __init__(self, address, pp=None):
58        qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address))
59        self._greeting = None
60        self._completer = None
61        self._pp = pp
62
63    def __get_address(self, arg):
64        """
65        Figure out if the argument is in the port:host form, if it's not it's
66        probably a file path.
67        """
68        addr = arg.split(':')
69        if len(addr) == 2:
70            try:
71                port = int(addr[1])
72            except ValueError:
73                raise QMPShellBadPort
74            return ( addr[0], port )
75        # socket path
76        return arg
77
78    def _fill_completion(self):
79        for cmd in self.cmd('query-commands')['return']:
80            self._completer.append(cmd['name'])
81
82    def __completer_setup(self):
83        self._completer = QMPCompleter()
84        self._fill_completion()
85        readline.set_completer(self._completer.complete)
86        readline.parse_and_bind("tab: complete")
87        # XXX: default delimiters conflict with some command names (eg. query-),
88        # clearing everything as it doesn't seem to matter
89        readline.set_completer_delims('')
90
91    def __cli_expr(self, tokens, parent):
92        for arg in tokens:
93            opt = arg.split('=')
94            try:
95                if(len(opt) > 2):
96                    opt[1] = '='.join(opt[1:])
97                value = int(opt[1])
98            except ValueError:
99                if opt[1] == 'true':
100                    value = True
101                elif opt[1] == 'false':
102                    value = False
103                elif opt[1].startswith('{'):
104                    value = json.loads(opt[1])
105                else:
106                    value = opt[1]
107            optpath = opt[0].split('.')
108            curpath = []
109            for p in optpath[:-1]:
110                curpath.append(p)
111                d = parent.get(p, {})
112                if type(d) is not dict:
113                    raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
114                parent[p] = d
115                parent = d
116            if optpath[-1] in parent:
117                if type(parent[optpath[-1]]) is dict:
118                    raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
119                else:
120                    raise QMPShellError('Cannot set "%s" multiple times' % opt[0])
121            parent[optpath[-1]] = value
122
123    def __build_cmd(self, cmdline):
124        """
125        Build a QMP input object from a user provided command-line in the
126        following format:
127
128            < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
129        """
130        cmdargs = cmdline.split()
131        qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
132        self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
133        return qmpcmd
134
135    def _execute_cmd(self, cmdline):
136        try:
137            qmpcmd = self.__build_cmd(cmdline)
138        except Exception, e:
139            print 'Error while parsing command line: %s' % e
140            print 'command format: <command-name> ',
141            print '[arg-name1=arg1] ... [arg-nameN=argN]'
142            return True
143        resp = self.cmd_obj(qmpcmd)
144        if resp is None:
145            print 'Disconnected'
146            return False
147
148        if self._pp is not None:
149            self._pp.pprint(resp)
150        else:
151            print resp
152        return True
153
154    def connect(self):
155        self._greeting = qmp.QEMUMonitorProtocol.connect(self)
156        self.__completer_setup()
157
158    def show_banner(self, msg='Welcome to the QMP low-level shell!'):
159        print msg
160        version = self._greeting['QMP']['version']['qemu']
161        print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
162
163    def read_exec_command(self, prompt):
164        """
165        Read and execute a command.
166
167        @return True if execution was ok, return False if disconnected.
168        """
169        try:
170            cmdline = raw_input(prompt)
171        except EOFError:
172            print
173            return False
174        if cmdline == '':
175            for ev in self.get_events():
176                print ev
177            self.clear_events()
178            return True
179        else:
180            return self._execute_cmd(cmdline)
181
182class HMPShell(QMPShell):
183    def __init__(self, address):
184        QMPShell.__init__(self, address)
185        self.__cpu_index = 0
186
187    def __cmd_completion(self):
188        for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
189            if cmd and cmd[0] != '[' and cmd[0] != '\t':
190                name = cmd.split()[0] # drop help text
191                if name == 'info':
192                    continue
193                if name.find('|') != -1:
194                    # Command in the form 'foobar|f' or 'f|foobar', take the
195                    # full name
196                    opt = name.split('|')
197                    if len(opt[0]) == 1:
198                        name = opt[1]
199                    else:
200                        name = opt[0]
201                self._completer.append(name)
202                self._completer.append('help ' + name) # help completion
203
204    def __info_completion(self):
205        for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
206            if cmd:
207                self._completer.append('info ' + cmd.split()[1])
208
209    def __other_completion(self):
210        # special cases
211        self._completer.append('help info')
212
213    def _fill_completion(self):
214        self.__cmd_completion()
215        self.__info_completion()
216        self.__other_completion()
217
218    def __cmd_passthrough(self, cmdline, cpu_index = 0):
219        return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
220                              { 'command-line': cmdline,
221                                'cpu-index': cpu_index } })
222
223    def _execute_cmd(self, cmdline):
224        if cmdline.split()[0] == "cpu":
225            # trap the cpu command, it requires special setting
226            try:
227                idx = int(cmdline.split()[1])
228                if not 'return' in self.__cmd_passthrough('info version', idx):
229                    print 'bad CPU index'
230                    return True
231                self.__cpu_index = idx
232            except ValueError:
233                print 'cpu command takes an integer argument'
234                return True
235        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
236        if resp is None:
237            print 'Disconnected'
238            return False
239        assert 'return' in resp or 'error' in resp
240        if 'return' in resp:
241            # Success
242            if len(resp['return']) > 0:
243                print resp['return'],
244        else:
245            # Error
246            print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
247        return True
248
249    def show_banner(self):
250        QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
251
252def die(msg):
253    sys.stderr.write('ERROR: %s\n' % msg)
254    sys.exit(1)
255
256def fail_cmdline(option=None):
257    if option:
258        sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
259    sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n')
260    sys.exit(1)
261
262def main():
263    addr = ''
264    qemu = None
265    hmp = False
266    pp = None
267
268    try:
269        for arg in sys.argv[1:]:
270            if arg == "-H":
271                if qemu is not None:
272                    fail_cmdline(arg)
273                hmp = True
274            elif arg == "-p":
275                if pp is not None:
276                    fail_cmdline(arg)
277                pp = pprint.PrettyPrinter(indent=4)
278            else:
279                if qemu is not None:
280                    fail_cmdline(arg)
281                if hmp:
282                    qemu = HMPShell(arg)
283                else:
284                    qemu = QMPShell(arg, pp)
285                addr = arg
286
287        if qemu is None:
288            fail_cmdline()
289    except QMPShellBadPort:
290        die('bad port number in command-line')
291
292    try:
293        qemu.connect()
294    except qmp.QMPConnectError:
295        die('Didn\'t get QMP greeting message')
296    except qmp.QMPCapabilitiesError:
297        die('Could not negotiate capabilities')
298    except qemu.error:
299        die('Could not connect to %s' % addr)
300
301    qemu.show_banner()
302    while qemu.read_exec_command('(QEMU) '):
303        pass
304    qemu.close()
305
306if __name__ == '__main__':
307    main()
308