hw/ppc: Take QEMU lock when calling ppc_dcr_read/write()
[qemu.git] / scripts / qmp / qmp-shell
1 #!/usr/bin/env python3
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 #
33 # key=value pairs also support Python or JSON object literal subset notations,
34 # without spaces. Dictionaries/objects {} are supported as are arrays [].
35 #
36 #    example-command arg-name1={'key':'value','obj'={'prop':"value"}}
37 #
38 # Both JSON and Python formatting should work, including both styles of
39 # string literal quotes. Both paradigms of literal values should work,
40 # including null/true/false for JSON and None/True/False for Python.
41 #
42 #
43 # Transactions have the following multi-line format:
44 #
45 #    transaction(
46 #    action-name1 [ arg-name1=arg1 ] ... [arg-nameN=argN ]
47 #    ...
48 #    action-nameN [ arg-name1=arg1 ] ... [arg-nameN=argN ]
49 #    )
50 #
51 # One line transactions are also supported:
52 #
53 #    transaction( action-name1 ... )
54 #
55 # For example:
56 #
57 #     (QEMU) transaction(
58 #     TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1
59 #     TRANS> block-dirty-bitmap-clear node=drive0 name=bitmap0
60 #     TRANS> )
61 #     {"return": {}}
62 #     (QEMU)
63 #
64 # Use the -v and -p options to activate the verbose and pretty-print options,
65 # which will echo back the properly formatted JSON-compliant QMP that is being
66 # sent to QEMU, which is useful for debugging and documentation generation.
67
68 import json
69 import ast
70 import readline
71 import sys
72 import os
73 import errno
74 import atexit
75 import re
76
77 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
78 from qemu import qmp
79
80 if sys.version_info[0] == 2:
81     input = raw_input
82
83 class QMPCompleter(list):
84     def complete(self, text, state):
85         for cmd in self:
86             if cmd.startswith(text):
87                 if not state:
88                     return cmd
89                 else:
90                     state -= 1
91
92 class QMPShellError(Exception):
93     pass
94
95 class QMPShellBadPort(QMPShellError):
96     pass
97
98 class FuzzyJSON(ast.NodeTransformer):
99     '''This extension of ast.NodeTransformer filters literal "true/false/null"
100     values in an AST and replaces them by proper "True/False/None" values that
101     Python can properly evaluate.'''
102     def visit_Name(self, node):
103         if node.id == 'true':
104             node.id = 'True'
105         if node.id == 'false':
106             node.id = 'False'
107         if node.id == 'null':
108             node.id = 'None'
109         return node
110
111 # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
112 #       _execute_cmd()). Let's design a better one.
113 class QMPShell(qmp.QEMUMonitorProtocol):
114     def __init__(self, address, pretty=False):
115         super(QMPShell, self).__init__(self.__get_address(address))
116         self._greeting = None
117         self._completer = None
118         self._pretty = pretty
119         self._transmode = False
120         self._actions = list()
121         self._histfile = os.path.join(os.path.expanduser('~'),
122                                       '.qmp-shell_history')
123
124     def __get_address(self, arg):
125         """
126         Figure out if the argument is in the port:host form, if it's not it's
127         probably a file path.
128         """
129         addr = arg.split(':')
130         if len(addr) == 2:
131             try:
132                 port = int(addr[1])
133             except ValueError:
134                 raise QMPShellBadPort
135             return ( addr[0], port )
136         # socket path
137         return arg
138
139     def _fill_completion(self):
140         cmds = self.cmd('query-commands')
141         if 'error' in cmds:
142             return
143         for cmd in cmds['return']:
144             self._completer.append(cmd['name'])
145
146     def __completer_setup(self):
147         self._completer = QMPCompleter()
148         self._fill_completion()
149         readline.set_history_length(1024)
150         readline.set_completer(self._completer.complete)
151         readline.parse_and_bind("tab: complete")
152         # XXX: default delimiters conflict with some command names (eg. query-),
153         # clearing everything as it doesn't seem to matter
154         readline.set_completer_delims('')
155         try:
156             readline.read_history_file(self._histfile)
157         except Exception as e:
158             if isinstance(e, IOError) and e.errno == errno.ENOENT:
159                 # File not found. No problem.
160                 pass
161             else:
162                 print("Failed to read history '%s'; %s" % (self._histfile, e))
163         atexit.register(self.__save_history)
164
165     def __save_history(self):
166         try:
167             readline.write_history_file(self._histfile)
168         except Exception as e:
169             print("Failed to save history file '%s'; %s" % (self._histfile, e))
170
171     def __parse_value(self, val):
172         try:
173             return int(val)
174         except ValueError:
175             pass
176
177         if val.lower() == 'true':
178             return True
179         if val.lower() == 'false':
180             return False
181         if val.startswith(('{', '[')):
182             # Try first as pure JSON:
183             try:
184                 return json.loads(val)
185             except ValueError:
186                 pass
187             # Try once again as FuzzyJSON:
188             try:
189                 st = ast.parse(val, mode='eval')
190                 return ast.literal_eval(FuzzyJSON().visit(st))
191             except SyntaxError:
192                 pass
193             except ValueError:
194                 pass
195         return val
196
197     def __cli_expr(self, tokens, parent):
198         for arg in tokens:
199             (key, sep, val) = arg.partition('=')
200             if sep != '=':
201                 raise QMPShellError("Expected a key=value pair, got '%s'" % arg)
202
203             value = self.__parse_value(val)
204             optpath = key.split('.')
205             curpath = []
206             for p in optpath[:-1]:
207                 curpath.append(p)
208                 d = parent.get(p, {})
209                 if type(d) is not dict:
210                     raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
211                 parent[p] = d
212                 parent = d
213             if optpath[-1] in parent:
214                 if type(parent[optpath[-1]]) is dict:
215                     raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
216                 else:
217                     raise QMPShellError('Cannot set "%s" multiple times' % key)
218             parent[optpath[-1]] = value
219
220     def __build_cmd(self, cmdline):
221         """
222         Build a QMP input object from a user provided command-line in the
223         following format:
224
225             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
226         """
227         cmdargs = re.findall(r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+''', cmdline)
228
229         # Transactional CLI entry/exit:
230         if cmdargs[0] == 'transaction(':
231             self._transmode = True
232             cmdargs.pop(0)
233         elif cmdargs[0] == ')' and self._transmode:
234             self._transmode = False
235             if len(cmdargs) > 1:
236                 raise QMPShellError("Unexpected input after close of Transaction sub-shell")
237             qmpcmd = { 'execute': 'transaction',
238                        'arguments': { 'actions': self._actions } }
239             self._actions = list()
240             return qmpcmd
241
242         # Nothing to process?
243         if not cmdargs:
244             return None
245
246         # Parse and then cache this Transactional Action
247         if self._transmode:
248             finalize = False
249             action = { 'type': cmdargs[0], 'data': {} }
250             if cmdargs[-1] == ')':
251                 cmdargs.pop(-1)
252                 finalize = True
253             self.__cli_expr(cmdargs[1:], action['data'])
254             self._actions.append(action)
255             return self.__build_cmd(')') if finalize else None
256
257         # Standard command: parse and return it to be executed.
258         qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
259         self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
260         return qmpcmd
261
262     def _print(self, qmp):
263         indent = None
264         if self._pretty:
265             indent = 4
266         jsobj = json.dumps(qmp, indent=indent)
267         print(str(jsobj))
268
269     def _execute_cmd(self, cmdline):
270         try:
271             qmpcmd = self.__build_cmd(cmdline)
272         except Exception as e:
273             print('Error while parsing command line: %s' % e)
274             print('command format: <command-name> ', end=' ')
275             print('[arg-name1=arg1] ... [arg-nameN=argN]')
276             return True
277         # For transaction mode, we may have just cached the action:
278         if qmpcmd is None:
279             return True
280         if self._verbose:
281             self._print(qmpcmd)
282         resp = self.cmd_obj(qmpcmd)
283         if resp is None:
284             print('Disconnected')
285             return False
286         self._print(resp)
287         return True
288
289     def connect(self, negotiate):
290         self._greeting = super(QMPShell, self).connect(negotiate)
291         self.__completer_setup()
292
293     def show_banner(self, msg='Welcome to the QMP low-level shell!'):
294         print(msg)
295         if not self._greeting:
296             print('Connected')
297             return
298         version = self._greeting['QMP']['version']['qemu']
299         print('Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']))
300
301     def get_prompt(self):
302         if self._transmode:
303             return "TRANS> "
304         return "(QEMU) "
305
306     def read_exec_command(self, prompt):
307         """
308         Read and execute a command.
309
310         @return True if execution was ok, return False if disconnected.
311         """
312         try:
313             cmdline = input(prompt)
314         except EOFError:
315             print()
316             return False
317         if cmdline == '':
318             for ev in self.get_events():
319                 print(ev)
320             self.clear_events()
321             return True
322         else:
323             return self._execute_cmd(cmdline)
324
325     def set_verbosity(self, verbose):
326         self._verbose = verbose
327
328 class HMPShell(QMPShell):
329     def __init__(self, address):
330         QMPShell.__init__(self, address)
331         self.__cpu_index = 0
332
333     def __cmd_completion(self):
334         for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
335             if cmd and cmd[0] != '[' and cmd[0] != '\t':
336                 name = cmd.split()[0] # drop help text
337                 if name == 'info':
338                     continue
339                 if name.find('|') != -1:
340                     # Command in the form 'foobar|f' or 'f|foobar', take the
341                     # full name
342                     opt = name.split('|')
343                     if len(opt[0]) == 1:
344                         name = opt[1]
345                     else:
346                         name = opt[0]
347                 self._completer.append(name)
348                 self._completer.append('help ' + name) # help completion
349
350     def __info_completion(self):
351         for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
352             if cmd:
353                 self._completer.append('info ' + cmd.split()[1])
354
355     def __other_completion(self):
356         # special cases
357         self._completer.append('help info')
358
359     def _fill_completion(self):
360         self.__cmd_completion()
361         self.__info_completion()
362         self.__other_completion()
363
364     def __cmd_passthrough(self, cmdline, cpu_index = 0):
365         return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
366                               { 'command-line': cmdline,
367                                 'cpu-index': cpu_index } })
368
369     def _execute_cmd(self, cmdline):
370         if cmdline.split()[0] == "cpu":
371             # trap the cpu command, it requires special setting
372             try:
373                 idx = int(cmdline.split()[1])
374                 if not 'return' in self.__cmd_passthrough('info version', idx):
375                     print('bad CPU index')
376                     return True
377                 self.__cpu_index = idx
378             except ValueError:
379                 print('cpu command takes an integer argument')
380                 return True
381         resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
382         if resp is None:
383             print('Disconnected')
384             return False
385         assert 'return' in resp or 'error' in resp
386         if 'return' in resp:
387             # Success
388             if len(resp['return']) > 0:
389                 print(resp['return'], end=' ')
390         else:
391             # Error
392             print('%s: %s' % (resp['error']['class'], resp['error']['desc']))
393         return True
394
395     def show_banner(self):
396         QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
397
398 def die(msg):
399     sys.stderr.write('ERROR: %s\n' % msg)
400     sys.exit(1)
401
402 def fail_cmdline(option=None):
403     if option:
404         sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
405     sys.stderr.write('qmp-shell [ -v ] [ -p ] [ -H ] [ -N ] < UNIX socket path> | < TCP address:port >\n')
406     sys.stderr.write('    -v     Verbose (echo command sent and received)\n')
407     sys.stderr.write('    -p     Pretty-print JSON\n')
408     sys.stderr.write('    -H     Use HMP interface\n')
409     sys.stderr.write('    -N     Skip negotiate (for qemu-ga)\n')
410     sys.exit(1)
411
412 def main():
413     addr = ''
414     qemu = None
415     hmp = False
416     pretty = False
417     verbose = False
418     negotiate = True
419
420     try:
421         for arg in sys.argv[1:]:
422             if arg == "-H":
423                 if qemu is not None:
424                     fail_cmdline(arg)
425                 hmp = True
426             elif arg == "-p":
427                 pretty = True
428             elif arg == "-N":
429                 negotiate = False
430             elif arg == "-v":
431                 verbose = True
432             else:
433                 if qemu is not None:
434                     fail_cmdline(arg)
435                 if hmp:
436                     qemu = HMPShell(arg)
437                 else:
438                     qemu = QMPShell(arg, pretty)
439                 addr = arg
440
441         if qemu is None:
442             fail_cmdline()
443     except QMPShellBadPort:
444         die('bad port number in command-line')
445
446     try:
447         qemu.connect(negotiate)
448     except qmp.QMPConnectError:
449         die('Didn\'t get QMP greeting message')
450     except qmp.QMPCapabilitiesError:
451         die('Could not negotiate capabilities')
452     except qemu.error:
453         die('Could not connect to %s' % addr)
454
455     qemu.show_banner()
456     qemu.set_verbosity(verbose)
457     while qemu.read_exec_command(qemu.get_prompt()):
458         pass
459     qemu.close()
460
461 if __name__ == '__main__':
462     main()