xref: /openbmc/qemu/scripts/qmp/qmp-shell (revision 0399a381)
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 __build_cmd(self, cmdline):
92        """
93        Build a QMP input object from a user provided command-line in the
94        following format:
95
96            < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
97        """
98        cmdargs = cmdline.split()
99        qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
100        for arg in cmdargs[1:]:
101            opt = arg.split('=')
102            try:
103                if(len(opt) > 2):
104                    opt[1] = '='.join(opt[1:])
105                value = int(opt[1])
106            except ValueError:
107                if opt[1] == 'true':
108                    value = True
109                elif opt[1] == 'false':
110                    value = False
111                elif opt[1].startswith('{'):
112                    value = json.loads(opt[1])
113                else:
114                    value = opt[1]
115            optpath = opt[0].split('.')
116            parent = qmpcmd['arguments']
117            curpath = []
118            for p in optpath[:-1]:
119                curpath.append(p)
120                d = parent.get(p, {})
121                if type(d) is not dict:
122                    raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
123                parent[p] = d
124                parent = d
125            if optpath[-1] in parent:
126                if type(parent[optpath[-1]]) is dict:
127                    raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
128                else:
129                    raise QMPShellError('Cannot set "%s" multiple times' % opt[0])
130            parent[optpath[-1]] = value
131        return qmpcmd
132
133    def _execute_cmd(self, cmdline):
134        try:
135            qmpcmd = self.__build_cmd(cmdline)
136        except Exception, e:
137            print 'Error while parsing command line: %s' % e
138            print 'command format: <command-name> ',
139            print '[arg-name1=arg1] ... [arg-nameN=argN]'
140            return True
141        resp = self.cmd_obj(qmpcmd)
142        if resp is None:
143            print 'Disconnected'
144            return False
145
146        if self._pp is not None:
147            self._pp.pprint(resp)
148        else:
149            print resp
150        return True
151
152    def connect(self):
153        self._greeting = qmp.QEMUMonitorProtocol.connect(self)
154        self.__completer_setup()
155
156    def show_banner(self, msg='Welcome to the QMP low-level shell!'):
157        print msg
158        version = self._greeting['QMP']['version']['qemu']
159        print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
160
161    def read_exec_command(self, prompt):
162        """
163        Read and execute a command.
164
165        @return True if execution was ok, return False if disconnected.
166        """
167        try:
168            cmdline = raw_input(prompt)
169        except EOFError:
170            print
171            return False
172        if cmdline == '':
173            for ev in self.get_events():
174                print ev
175            self.clear_events()
176            return True
177        else:
178            return self._execute_cmd(cmdline)
179
180class HMPShell(QMPShell):
181    def __init__(self, address):
182        QMPShell.__init__(self, address)
183        self.__cpu_index = 0
184
185    def __cmd_completion(self):
186        for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
187            if cmd and cmd[0] != '[' and cmd[0] != '\t':
188                name = cmd.split()[0] # drop help text
189                if name == 'info':
190                    continue
191                if name.find('|') != -1:
192                    # Command in the form 'foobar|f' or 'f|foobar', take the
193                    # full name
194                    opt = name.split('|')
195                    if len(opt[0]) == 1:
196                        name = opt[1]
197                    else:
198                        name = opt[0]
199                self._completer.append(name)
200                self._completer.append('help ' + name) # help completion
201
202    def __info_completion(self):
203        for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
204            if cmd:
205                self._completer.append('info ' + cmd.split()[1])
206
207    def __other_completion(self):
208        # special cases
209        self._completer.append('help info')
210
211    def _fill_completion(self):
212        self.__cmd_completion()
213        self.__info_completion()
214        self.__other_completion()
215
216    def __cmd_passthrough(self, cmdline, cpu_index = 0):
217        return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
218                              { 'command-line': cmdline,
219                                'cpu-index': cpu_index } })
220
221    def _execute_cmd(self, cmdline):
222        if cmdline.split()[0] == "cpu":
223            # trap the cpu command, it requires special setting
224            try:
225                idx = int(cmdline.split()[1])
226                if not 'return' in self.__cmd_passthrough('info version', idx):
227                    print 'bad CPU index'
228                    return True
229                self.__cpu_index = idx
230            except ValueError:
231                print 'cpu command takes an integer argument'
232                return True
233        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
234        if resp is None:
235            print 'Disconnected'
236            return False
237        assert 'return' in resp or 'error' in resp
238        if 'return' in resp:
239            # Success
240            if len(resp['return']) > 0:
241                print resp['return'],
242        else:
243            # Error
244            print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
245        return True
246
247    def show_banner(self):
248        QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
249
250def die(msg):
251    sys.stderr.write('ERROR: %s\n' % msg)
252    sys.exit(1)
253
254def fail_cmdline(option=None):
255    if option:
256        sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
257    sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n')
258    sys.exit(1)
259
260def main():
261    addr = ''
262    qemu = None
263    hmp = False
264    pp = None
265
266    try:
267        for arg in sys.argv[1:]:
268            if arg == "-H":
269                if qemu is not None:
270                    fail_cmdline(arg)
271                hmp = True
272            elif arg == "-p":
273                if pp is not None:
274                    fail_cmdline(arg)
275                pp = pprint.PrettyPrinter(indent=4)
276            else:
277                if qemu is not None:
278                    fail_cmdline(arg)
279                if hmp:
280                    qemu = HMPShell(arg)
281                else:
282                    qemu = QMPShell(arg, pp)
283                addr = arg
284
285        if qemu is None:
286            fail_cmdline()
287    except QMPShellBadPort:
288        die('bad port number in command-line')
289
290    try:
291        qemu.connect()
292    except qmp.QMPConnectError:
293        die('Didn\'t get QMP greeting message')
294    except qmp.QMPCapabilitiesError:
295        die('Could not negotiate capabilities')
296    except qemu.error:
297        die('Could not connect to %s' % addr)
298
299    qemu.show_banner()
300    while qemu.read_exec_command('(QEMU) '):
301        pass
302    qemu.close()
303
304if __name__ == '__main__':
305    main()
306