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