scripts/qmp-shell: declare verbose in __init__
[qemu.git] / scripts / qmp / qmp-shell
blob402745432494708855db3eee4b3bbd781226201a
1 #!/usr/bin/env python3
3 # Low-level QEMU shell on top of QMP.
5 # Copyright (C) 2009, 2010 Red Hat Inc.
7 # Authors:
8 #  Luiz Capitulino <lcapitulino@redhat.com>
10 # This work is licensed under the terms of the GNU GPL, version 2.  See
11 # the COPYING file in the top-level directory.
13 # Usage:
15 # Start QEMU with:
17 # # qemu [...] -qmp unix:./qmp-sock,server
19 # Run the shell:
21 # $ qmp-shell ./qmp-sock
23 # Commands have the following format:
25 #    < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
27 # For example:
29 # (QEMU) device_add driver=e1000 id=net1
30 # {u'return': {}}
31 # (QEMU)
33 # key=value pairs also support Python or JSON object literal subset notations,
34 # without spaces. Dictionaries/objects {} are supported as are arrays [].
36 #    example-command arg-name1={'key':'value','obj'={'prop':"value"}}
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.
43 # Transactions have the following multi-line format:
45 #    transaction(
46 #    action-name1 [ arg-name1=arg1 ] ... [arg-nameN=argN ]
47 #    ...
48 #    action-nameN [ arg-name1=arg1 ] ... [arg-nameN=argN ]
49 #    )
51 # One line transactions are also supported:
53 #    transaction( action-name1 ... )
55 # For example:
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)
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.
68 import ast
69 import atexit
70 import errno
71 import json
72 import os
73 import re
74 import readline
75 import sys
78 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
79 from qemu import qmp
82 class QMPCompleter(list):
83     def complete(self, text, state):
84         for cmd in self:
85             if cmd.startswith(text):
86                 if not state:
87                     return cmd
88                 else:
89                     state -= 1
92 class QMPShellError(Exception):
93     pass
96 class FuzzyJSON(ast.NodeTransformer):
97     '''This extension of ast.NodeTransformer filters literal "true/false/null"
98     values in an AST and replaces them by proper "True/False/None" values that
99     Python can properly evaluate.'''
100     @classmethod
101     def visit_Name(cls, node):
102         if node.id == 'true':
103             node.id = 'True'
104         if node.id == 'false':
105             node.id = 'False'
106         if node.id == 'null':
107             node.id = 'None'
108         return node
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().__init__(self.parse_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         self._verbose = False
125     def _fill_completion(self):
126         cmds = self.cmd('query-commands')
127         if 'error' in cmds:
128             return
129         for cmd in cmds['return']:
130             self._completer.append(cmd['name'])
132     def __completer_setup(self):
133         self._completer = QMPCompleter()
134         self._fill_completion()
135         readline.set_history_length(1024)
136         readline.set_completer(self._completer.complete)
137         readline.parse_and_bind("tab: complete")
138         # NB: default delimiters conflict with some command names
139         # (eg. query-), clearing everything as it doesn't seem to matter
140         readline.set_completer_delims('')
141         try:
142             readline.read_history_file(self._histfile)
143         except Exception as e:
144             if isinstance(e, IOError) and e.errno == errno.ENOENT:
145                 # File not found. No problem.
146                 pass
147             else:
148                 print("Failed to read history '%s'; %s" % (self._histfile, e))
149         atexit.register(self.__save_history)
151     def __save_history(self):
152         try:
153             readline.write_history_file(self._histfile)
154         except Exception as e:
155             print("Failed to save history file '%s'; %s" % (self._histfile, e))
157     @classmethod
158     def __parse_value(cls, val):
159         try:
160             return int(val)
161         except ValueError:
162             pass
164         if val.lower() == 'true':
165             return True
166         if val.lower() == 'false':
167             return False
168         if val.startswith(('{', '[')):
169             # Try first as pure JSON:
170             try:
171                 return json.loads(val)
172             except ValueError:
173                 pass
174             # Try once again as FuzzyJSON:
175             try:
176                 st = ast.parse(val, mode='eval')
177                 return ast.literal_eval(FuzzyJSON().visit(st))
178             except SyntaxError:
179                 pass
180             except ValueError:
181                 pass
182         return val
184     def __cli_expr(self, tokens, parent):
185         for arg in tokens:
186             (key, sep, val) = arg.partition('=')
187             if sep != '=':
188                 raise QMPShellError(
189                     f"Expected a key=value pair, got '{arg!s}'"
190                 )
192             value = self.__parse_value(val)
193             optpath = key.split('.')
194             curpath = []
195             for p in optpath[:-1]:
196                 curpath.append(p)
197                 d = parent.get(p, {})
198                 if type(d) is not dict:
199                     msg = 'Cannot use "{:s}" as both leaf and non-leaf key'
200                     raise QMPShellError(msg.format('.'.join(curpath)))
201                 parent[p] = d
202                 parent = d
203             if optpath[-1] in parent:
204                 if type(parent[optpath[-1]]) is dict:
205                     msg = 'Cannot use "{:s}" as both leaf and non-leaf key'
206                     raise QMPShellError(msg.format('.'.join(curpath)))
207                 else:
208                     raise QMPShellError(f'Cannot set "{key}" multiple times')
209             parent[optpath[-1]] = value
211     def __build_cmd(self, cmdline):
212         """
213         Build a QMP input object from a user provided command-line in the
214         following format:
216             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
217         """
218         argument_regex = r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+'''
219         cmdargs = re.findall(argument_regex, cmdline)
221         # Transactional CLI entry/exit:
222         if cmdargs[0] == 'transaction(':
223             self._transmode = True
224             cmdargs.pop(0)
225         elif cmdargs[0] == ')' and self._transmode:
226             self._transmode = False
227             if len(cmdargs) > 1:
228                 msg = 'Unexpected input after close of Transaction sub-shell'
229                 raise QMPShellError(msg)
230             qmpcmd = {
231                 'execute': 'transaction',
232                 'arguments': {'actions': self._actions}
233             }
234             self._actions = list()
235             return qmpcmd
237         # Nothing to process?
238         if not cmdargs:
239             return None
241         # Parse and then cache this Transactional Action
242         if self._transmode:
243             finalize = False
244             action = {'type': cmdargs[0], 'data': {}}
245             if cmdargs[-1] == ')':
246                 cmdargs.pop(-1)
247                 finalize = True
248             self.__cli_expr(cmdargs[1:], action['data'])
249             self._actions.append(action)
250             return self.__build_cmd(')') if finalize else None
252         # Standard command: parse and return it to be executed.
253         qmpcmd = {'execute': cmdargs[0], 'arguments': {}}
254         self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
255         return qmpcmd
257     def _print(self, qmp_message):
258         indent = None
259         if self._pretty:
260             indent = 4
261         jsobj = json.dumps(qmp_message, indent=indent, sort_keys=self._pretty)
262         print(str(jsobj))
264     def _execute_cmd(self, cmdline):
265         try:
266             qmpcmd = self.__build_cmd(cmdline)
267         except Exception as e:
268             print('Error while parsing command line: %s' % e)
269             print('command format: <command-name> ', end=' ')
270             print('[arg-name1=arg1] ... [arg-nameN=argN]')
271             return True
272         # For transaction mode, we may have just cached the action:
273         if qmpcmd is None:
274             return True
275         if self._verbose:
276             self._print(qmpcmd)
277         resp = self.cmd_obj(qmpcmd)
278         if resp is None:
279             print('Disconnected')
280             return False
281         self._print(resp)
282         return True
284     def connect(self, negotiate: bool = True):
285         self._greeting = super().connect(negotiate)
286         self.__completer_setup()
288     def show_banner(self, msg='Welcome to the QMP low-level shell!'):
289         print(msg)
290         if not self._greeting:
291             print('Connected')
292             return
293         version = self._greeting['QMP']['version']['qemu']
294         print("Connected to QEMU {major}.{minor}.{micro}\n".format(**version))
296     def get_prompt(self):
297         if self._transmode:
298             return "TRANS> "
299         return "(QEMU) "
301     def read_exec_command(self, prompt):
302         """
303         Read and execute a command.
305         @return True if execution was ok, return False if disconnected.
306         """
307         try:
308             cmdline = input(prompt)
309         except EOFError:
310             print()
311             return False
312         if cmdline == '':
313             for ev in self.get_events():
314                 print(ev)
315             self.clear_events()
316             return True
317         else:
318             return self._execute_cmd(cmdline)
320     def set_verbosity(self, verbose):
321         self._verbose = verbose
324 class HMPShell(QMPShell):
325     def __init__(self, address):
326         super().__init__(address)
327         self.__cpu_index = 0
329     def __cmd_completion(self):
330         for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
331             if cmd and cmd[0] != '[' and cmd[0] != '\t':
332                 name = cmd.split()[0]  # drop help text
333                 if name == 'info':
334                     continue
335                 if name.find('|') != -1:
336                     # Command in the form 'foobar|f' or 'f|foobar', take the
337                     # full name
338                     opt = name.split('|')
339                     if len(opt[0]) == 1:
340                         name = opt[1]
341                     else:
342                         name = opt[0]
343                 self._completer.append(name)
344                 self._completer.append('help ' + name)  # help completion
346     def __info_completion(self):
347         for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
348             if cmd:
349                 self._completer.append('info ' + cmd.split()[1])
351     def __other_completion(self):
352         # special cases
353         self._completer.append('help info')
355     def _fill_completion(self):
356         self.__cmd_completion()
357         self.__info_completion()
358         self.__other_completion()
360     def __cmd_passthrough(self, cmdline, cpu_index=0):
361         return self.cmd_obj({
362             'execute': 'human-monitor-command',
363             'arguments': {
364                 'command-line': cmdline,
365                 'cpu-index': cpu_index
366             }
367         })
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 'return' not 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
395     def show_banner(self, msg='Welcome to the HMP shell!'):
396         QMPShell.show_banner(self, msg)
399 def die(msg):
400     sys.stderr.write('ERROR: %s\n' % msg)
401     sys.exit(1)
404 def fail_cmdline(option=None):
405     if option:
406         sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
407     sys.stderr.write(
408         'qmp-shell [ -v ] [ -p ] [ -H ] [ -N ] '
409         '< UNIX socket path> | < TCP address:port >\n'
410     )
411     sys.stderr.write('    -v     Verbose (echo command sent and received)\n')
412     sys.stderr.write('    -p     Pretty-print JSON\n')
413     sys.stderr.write('    -H     Use HMP interface\n')
414     sys.stderr.write('    -N     Skip negotiate (for qemu-ga)\n')
415     sys.exit(1)
418 def main():
419     addr = ''
420     qemu = None
421     hmp = False
422     pretty = False
423     verbose = False
424     negotiate = True
426     try:
427         for arg in sys.argv[1:]:
428             if arg == "-H":
429                 if qemu is not None:
430                     fail_cmdline(arg)
431                 hmp = True
432             elif arg == "-p":
433                 pretty = True
434             elif arg == "-N":
435                 negotiate = False
436             elif arg == "-v":
437                 verbose = True
438             else:
439                 if qemu is not None:
440                     fail_cmdline(arg)
441                 if hmp:
442                     qemu = HMPShell(arg)
443                 else:
444                     qemu = QMPShell(arg, pretty)
445                 addr = arg
447         if qemu is None:
448             fail_cmdline()
449     except qmp.QMPBadPortError:
450         die('bad port number in command-line')
452     try:
453         qemu.connect(negotiate)
454     except qmp.QMPConnectError:
455         die('Didn\'t get QMP greeting message')
456     except qmp.QMPCapabilitiesError:
457         die('Could not negotiate capabilities')
458     except OSError:
459         die('Could not connect to %s' % addr)
461     qemu.show_banner()
462     qemu.set_verbosity(verbose)
463     while qemu.read_exec_command(qemu.get_prompt()):
464         pass
465     qemu.close()
468 if __name__ == '__main__':
469     main()