Philippe Mathieu-Daudé | 3d004a3 | 2020-01-30 17:32:25 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 2 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 3 | # Low-level QEMU shell on top of QMP. |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 4 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 5 | # Copyright (C) 2009, 2010 Red Hat Inc. |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 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 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 17 | # # qemu [...] -qmp unix:./qmp-sock,server |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 18 | # |
| 19 | # Run the shell: |
| 20 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 21 | # $ qmp-shell ./qmp-sock |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 22 | # |
| 23 | # Commands have the following format: |
| 24 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 25 | # < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 26 | # |
| 27 | # For example: |
| 28 | # |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 29 | # (QEMU) device_add driver=e1000 id=net1 |
| 30 | # {u'return': {}} |
| 31 | # (QEMU) |
John Snow | e2f9a65 | 2015-07-01 14:25:49 -0400 | [diff] [blame] | 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. |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 67 | |
Stefan Hajnoczi | ff9ec34 | 2014-01-29 12:17:31 +0100 | [diff] [blame] | 68 | import json |
John Snow | 6092c3e | 2015-04-29 15:14:02 -0400 | [diff] [blame] | 69 | import ast |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 70 | import readline |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 71 | import sys |
John Snow | aa3b167 | 2017-04-27 18:36:28 -0400 | [diff] [blame] | 72 | import os |
| 73 | import errno |
| 74 | import atexit |
Marc-André Lureau | b35203b | 2019-02-05 14:49:26 +0100 | [diff] [blame] | 75 | import re |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 76 | |
Cleber Rosa | 8f8fd9e | 2019-02-06 11:29:01 -0500 | [diff] [blame] | 77 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) |
| 78 | from qemu import qmp |
| 79 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 80 | class QMPCompleter(list): |
| 81 | def complete(self, text, state): |
| 82 | for cmd in self: |
| 83 | if cmd.startswith(text): |
| 84 | if not state: |
| 85 | return cmd |
| 86 | else: |
| 87 | state -= 1 |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 88 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 89 | class QMPShellError(Exception): |
| 90 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 91 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 92 | class QMPShellBadPort(QMPShellError): |
| 93 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 94 | |
John Snow | 6092c3e | 2015-04-29 15:14:02 -0400 | [diff] [blame] | 95 | class FuzzyJSON(ast.NodeTransformer): |
| 96 | '''This extension of ast.NodeTransformer filters literal "true/false/null" |
| 97 | values in an AST and replaces them by proper "True/False/None" values that |
| 98 | Python can properly evaluate.''' |
| 99 | def visit_Name(self, node): |
| 100 | if node.id == 'true': |
| 101 | node.id = 'True' |
| 102 | if node.id == 'false': |
| 103 | node.id = 'False' |
| 104 | if node.id == 'null': |
| 105 | node.id = 'None' |
| 106 | return node |
| 107 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 108 | # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and |
| 109 | # _execute_cmd()). Let's design a better one. |
| 110 | class QMPShell(qmp.QEMUMonitorProtocol): |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 111 | def __init__(self, address, pretty=False): |
Lukáš Doktor | 3dd29b4 | 2017-08-18 16:26:10 +0200 | [diff] [blame] | 112 | super(QMPShell, self).__init__(self.__get_address(address)) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 113 | self._greeting = None |
| 114 | self._completer = None |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 115 | self._pretty = pretty |
John Snow | 30bd681 | 2015-04-29 15:14:03 -0400 | [diff] [blame] | 116 | self._transmode = False |
| 117 | self._actions = list() |
John Snow | aa3b167 | 2017-04-27 18:36:28 -0400 | [diff] [blame] | 118 | self._histfile = os.path.join(os.path.expanduser('~'), |
| 119 | '.qmp-shell_history') |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 120 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 121 | def __get_address(self, arg): |
| 122 | """ |
| 123 | Figure out if the argument is in the port:host form, if it's not it's |
| 124 | probably a file path. |
| 125 | """ |
| 126 | addr = arg.split(':') |
| 127 | if len(addr) == 2: |
| 128 | try: |
| 129 | port = int(addr[1]) |
| 130 | except ValueError: |
| 131 | raise QMPShellBadPort |
| 132 | return ( addr[0], port ) |
| 133 | # socket path |
| 134 | return arg |
| 135 | |
| 136 | def _fill_completion(self): |
Marc-André Lureau | daa5a72 | 2017-05-04 16:54:30 +0400 | [diff] [blame] | 137 | cmds = self.cmd('query-commands') |
Eduardo Habkost | d7a4228 | 2018-06-08 09:29:46 -0300 | [diff] [blame] | 138 | if 'error' in cmds: |
Marc-André Lureau | daa5a72 | 2017-05-04 16:54:30 +0400 | [diff] [blame] | 139 | return |
| 140 | for cmd in cmds['return']: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 141 | self._completer.append(cmd['name']) |
| 142 | |
| 143 | def __completer_setup(self): |
| 144 | self._completer = QMPCompleter() |
| 145 | self._fill_completion() |
John Snow | aa3b167 | 2017-04-27 18:36:28 -0400 | [diff] [blame] | 146 | readline.set_history_length(1024) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 147 | readline.set_completer(self._completer.complete) |
| 148 | readline.parse_and_bind("tab: complete") |
| 149 | # XXX: default delimiters conflict with some command names (eg. query-), |
| 150 | # clearing everything as it doesn't seem to matter |
| 151 | readline.set_completer_delims('') |
John Snow | aa3b167 | 2017-04-27 18:36:28 -0400 | [diff] [blame] | 152 | try: |
| 153 | readline.read_history_file(self._histfile) |
| 154 | except Exception as e: |
| 155 | if isinstance(e, IOError) and e.errno == errno.ENOENT: |
| 156 | # File not found. No problem. |
| 157 | pass |
| 158 | else: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 159 | print("Failed to read history '%s'; %s" % (self._histfile, e)) |
John Snow | aa3b167 | 2017-04-27 18:36:28 -0400 | [diff] [blame] | 160 | atexit.register(self.__save_history) |
| 161 | |
| 162 | def __save_history(self): |
| 163 | try: |
| 164 | readline.write_history_file(self._histfile) |
| 165 | except Exception as e: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 166 | print("Failed to save history file '%s'; %s" % (self._histfile, e)) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 167 | |
John Snow | 6092c3e | 2015-04-29 15:14:02 -0400 | [diff] [blame] | 168 | def __parse_value(self, val): |
| 169 | try: |
| 170 | return int(val) |
| 171 | except ValueError: |
| 172 | pass |
| 173 | |
| 174 | if val.lower() == 'true': |
| 175 | return True |
| 176 | if val.lower() == 'false': |
| 177 | return False |
| 178 | if val.startswith(('{', '[')): |
| 179 | # Try first as pure JSON: |
| 180 | try: |
| 181 | return json.loads(val) |
| 182 | except ValueError: |
| 183 | pass |
| 184 | # Try once again as FuzzyJSON: |
| 185 | try: |
| 186 | st = ast.parse(val, mode='eval') |
| 187 | return ast.literal_eval(FuzzyJSON().visit(st)) |
| 188 | except SyntaxError: |
| 189 | pass |
| 190 | except ValueError: |
| 191 | pass |
| 192 | return val |
| 193 | |
John Snow | a7430a0 | 2015-04-29 15:14:01 -0400 | [diff] [blame] | 194 | def __cli_expr(self, tokens, parent): |
| 195 | for arg in tokens: |
Daniel P. Berrange | f880cd6 | 2017-03-02 12:24:29 +0000 | [diff] [blame] | 196 | (key, sep, val) = arg.partition('=') |
| 197 | if sep != '=': |
John Snow | 6092c3e | 2015-04-29 15:14:02 -0400 | [diff] [blame] | 198 | raise QMPShellError("Expected a key=value pair, got '%s'" % arg) |
| 199 | |
| 200 | value = self.__parse_value(val) |
| 201 | optpath = key.split('.') |
Fam Zheng | cd159d0 | 2014-02-12 11:05:13 +0800 | [diff] [blame] | 202 | curpath = [] |
| 203 | for p in optpath[:-1]: |
| 204 | curpath.append(p) |
| 205 | d = parent.get(p, {}) |
| 206 | if type(d) is not dict: |
| 207 | raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) |
| 208 | parent[p] = d |
| 209 | parent = d |
| 210 | if optpath[-1] in parent: |
| 211 | if type(parent[optpath[-1]]) is dict: |
| 212 | raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) |
| 213 | else: |
John Snow | 6092c3e | 2015-04-29 15:14:02 -0400 | [diff] [blame] | 214 | raise QMPShellError('Cannot set "%s" multiple times' % key) |
Fam Zheng | cd159d0 | 2014-02-12 11:05:13 +0800 | [diff] [blame] | 215 | parent[optpath[-1]] = value |
John Snow | a7430a0 | 2015-04-29 15:14:01 -0400 | [diff] [blame] | 216 | |
| 217 | def __build_cmd(self, cmdline): |
| 218 | """ |
| 219 | Build a QMP input object from a user provided command-line in the |
| 220 | following format: |
| 221 | |
| 222 | < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
| 223 | """ |
Marc-André Lureau | b35203b | 2019-02-05 14:49:26 +0100 | [diff] [blame] | 224 | cmdargs = re.findall(r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+''', cmdline) |
John Snow | 30bd681 | 2015-04-29 15:14:03 -0400 | [diff] [blame] | 225 | |
| 226 | # Transactional CLI entry/exit: |
| 227 | if cmdargs[0] == 'transaction(': |
| 228 | self._transmode = True |
| 229 | cmdargs.pop(0) |
| 230 | elif cmdargs[0] == ')' and self._transmode: |
| 231 | self._transmode = False |
| 232 | if len(cmdargs) > 1: |
| 233 | raise QMPShellError("Unexpected input after close of Transaction sub-shell") |
| 234 | qmpcmd = { 'execute': 'transaction', |
| 235 | 'arguments': { 'actions': self._actions } } |
| 236 | self._actions = list() |
| 237 | return qmpcmd |
| 238 | |
| 239 | # Nothing to process? |
| 240 | if not cmdargs: |
| 241 | return None |
| 242 | |
| 243 | # Parse and then cache this Transactional Action |
| 244 | if self._transmode: |
| 245 | finalize = False |
| 246 | action = { 'type': cmdargs[0], 'data': {} } |
| 247 | if cmdargs[-1] == ')': |
| 248 | cmdargs.pop(-1) |
| 249 | finalize = True |
| 250 | self.__cli_expr(cmdargs[1:], action['data']) |
| 251 | self._actions.append(action) |
| 252 | return self.__build_cmd(')') if finalize else None |
| 253 | |
| 254 | # Standard command: parse and return it to be executed. |
John Snow | a7430a0 | 2015-04-29 15:14:01 -0400 | [diff] [blame] | 255 | qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } |
| 256 | self.__cli_expr(cmdargs[1:], qmpcmd['arguments']) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 257 | return qmpcmd |
| 258 | |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 259 | def _print(self, qmp): |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 260 | indent = None |
| 261 | if self._pretty: |
| 262 | indent = 4 |
| 263 | jsobj = json.dumps(qmp, indent=indent) |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 264 | print(str(jsobj)) |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 265 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 266 | def _execute_cmd(self, cmdline): |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 267 | try: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 268 | qmpcmd = self.__build_cmd(cmdline) |
Markus Armbruster | cf6c634 | 2015-12-18 08:52:42 +0100 | [diff] [blame] | 269 | except Exception as e: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 270 | print('Error while parsing command line: %s' % e) |
| 271 | print('command format: <command-name> ', end=' ') |
| 272 | print('[arg-name1=arg1] ... [arg-nameN=argN]') |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 273 | return True |
John Snow | 30bd681 | 2015-04-29 15:14:03 -0400 | [diff] [blame] | 274 | # For transaction mode, we may have just cached the action: |
| 275 | if qmpcmd is None: |
| 276 | return True |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 277 | if self._verbose: |
| 278 | self._print(qmpcmd) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 279 | resp = self.cmd_obj(qmpcmd) |
| 280 | if resp is None: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 281 | print('Disconnected') |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 282 | return False |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 283 | self._print(resp) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 284 | return True |
| 285 | |
Marc-André Lureau | c5e397d | 2017-05-04 16:54:29 +0400 | [diff] [blame] | 286 | def connect(self, negotiate): |
Lukáš Doktor | 3dd29b4 | 2017-08-18 16:26:10 +0200 | [diff] [blame] | 287 | self._greeting = super(QMPShell, self).connect(negotiate) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 288 | self.__completer_setup() |
| 289 | |
| 290 | def show_banner(self, msg='Welcome to the QMP low-level shell!'): |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 291 | print(msg) |
Marc-André Lureau | b13d2ff | 2017-05-04 16:54:31 +0400 | [diff] [blame] | 292 | if not self._greeting: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 293 | print('Connected') |
Marc-André Lureau | b13d2ff | 2017-05-04 16:54:31 +0400 | [diff] [blame] | 294 | return |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 295 | version = self._greeting['QMP']['version']['qemu'] |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 296 | print('Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 297 | |
John Snow | 30bd681 | 2015-04-29 15:14:03 -0400 | [diff] [blame] | 298 | def get_prompt(self): |
| 299 | if self._transmode: |
| 300 | return "TRANS> " |
| 301 | return "(QEMU) " |
| 302 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 303 | def read_exec_command(self, prompt): |
| 304 | """ |
| 305 | Read and execute a command. |
| 306 | |
| 307 | @return True if execution was ok, return False if disconnected. |
| 308 | """ |
| 309 | try: |
Igor Mammedov | 08aa87f | 2019-06-20 11:40:35 -0400 | [diff] [blame] | 310 | cmdline = input(prompt) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 311 | except EOFError: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 312 | print() |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 313 | return False |
| 314 | if cmdline == '': |
| 315 | for ev in self.get_events(): |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 316 | print(ev) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 317 | self.clear_events() |
| 318 | return True |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 319 | else: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 320 | return self._execute_cmd(cmdline) |
| 321 | |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 322 | def set_verbosity(self, verbose): |
| 323 | self._verbose = verbose |
| 324 | |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 325 | class HMPShell(QMPShell): |
| 326 | def __init__(self, address): |
| 327 | QMPShell.__init__(self, address) |
| 328 | self.__cpu_index = 0 |
| 329 | |
| 330 | def __cmd_completion(self): |
| 331 | for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): |
| 332 | if cmd and cmd[0] != '[' and cmd[0] != '\t': |
| 333 | name = cmd.split()[0] # drop help text |
| 334 | if name == 'info': |
| 335 | continue |
| 336 | if name.find('|') != -1: |
| 337 | # Command in the form 'foobar|f' or 'f|foobar', take the |
| 338 | # full name |
| 339 | opt = name.split('|') |
| 340 | if len(opt[0]) == 1: |
| 341 | name = opt[1] |
| 342 | else: |
| 343 | name = opt[0] |
| 344 | self._completer.append(name) |
| 345 | self._completer.append('help ' + name) # help completion |
| 346 | |
| 347 | def __info_completion(self): |
| 348 | for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): |
| 349 | if cmd: |
| 350 | self._completer.append('info ' + cmd.split()[1]) |
| 351 | |
| 352 | def __other_completion(self): |
| 353 | # special cases |
| 354 | self._completer.append('help info') |
| 355 | |
| 356 | def _fill_completion(self): |
| 357 | self.__cmd_completion() |
| 358 | self.__info_completion() |
| 359 | self.__other_completion() |
| 360 | |
| 361 | def __cmd_passthrough(self, cmdline, cpu_index = 0): |
| 362 | return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': |
| 363 | { 'command-line': cmdline, |
| 364 | 'cpu-index': cpu_index } }) |
| 365 | |
| 366 | def _execute_cmd(self, cmdline): |
| 367 | if cmdline.split()[0] == "cpu": |
| 368 | # trap the cpu command, it requires special setting |
| 369 | try: |
| 370 | idx = int(cmdline.split()[1]) |
| 371 | if not 'return' in self.__cmd_passthrough('info version', idx): |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 372 | print('bad CPU index') |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 373 | return True |
| 374 | self.__cpu_index = idx |
| 375 | except ValueError: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 376 | print('cpu command takes an integer argument') |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 377 | return True |
| 378 | resp = self.__cmd_passthrough(cmdline, self.__cpu_index) |
| 379 | if resp is None: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 380 | print('Disconnected') |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 381 | return False |
| 382 | assert 'return' in resp or 'error' in resp |
| 383 | if 'return' in resp: |
| 384 | # Success |
| 385 | if len(resp['return']) > 0: |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 386 | print(resp['return'], end=' ') |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 387 | else: |
| 388 | # Error |
Eduardo Habkost | f03868b | 2018-06-08 09:29:43 -0300 | [diff] [blame] | 389 | print('%s: %s' % (resp['error']['class'], resp['error']['desc'])) |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 390 | return True |
| 391 | |
| 392 | def show_banner(self): |
| 393 | QMPShell.show_banner(self, msg='Welcome to the HMP shell!') |
| 394 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 395 | def die(msg): |
| 396 | sys.stderr.write('ERROR: %s\n' % msg) |
| 397 | sys.exit(1) |
| 398 | |
| 399 | def fail_cmdline(option=None): |
| 400 | if option: |
| 401 | sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) |
Marc-André Lureau | dcd3b25 | 2017-05-04 16:54:32 +0400 | [diff] [blame] | 402 | sys.stderr.write('qmp-shell [ -v ] [ -p ] [ -H ] [ -N ] < UNIX socket path> | < TCP address:port >\n') |
| 403 | sys.stderr.write(' -v Verbose (echo command sent and received)\n') |
| 404 | sys.stderr.write(' -p Pretty-print JSON\n') |
| 405 | sys.stderr.write(' -H Use HMP interface\n') |
| 406 | sys.stderr.write(' -N Skip negotiate (for qemu-ga)\n') |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 407 | sys.exit(1) |
| 408 | |
| 409 | def main(): |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 410 | addr = '' |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 411 | qemu = None |
| 412 | hmp = False |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 413 | pretty = False |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 414 | verbose = False |
Marc-André Lureau | c5e397d | 2017-05-04 16:54:29 +0400 | [diff] [blame] | 415 | negotiate = True |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 416 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 417 | try: |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 418 | for arg in sys.argv[1:]: |
| 419 | if arg == "-H": |
| 420 | if qemu is not None: |
| 421 | fail_cmdline(arg) |
| 422 | hmp = True |
| 423 | elif arg == "-p": |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 424 | pretty = True |
Marc-André Lureau | c5e397d | 2017-05-04 16:54:29 +0400 | [diff] [blame] | 425 | elif arg == "-N": |
| 426 | negotiate = False |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 427 | elif arg == "-v": |
| 428 | verbose = True |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 429 | else: |
| 430 | if qemu is not None: |
| 431 | fail_cmdline(arg) |
| 432 | if hmp: |
| 433 | qemu = HMPShell(arg) |
| 434 | else: |
Daniel P. Berrange | e55250c | 2016-02-23 10:51:46 +0000 | [diff] [blame] | 435 | qemu = QMPShell(arg, pretty) |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 436 | addr = arg |
| 437 | |
| 438 | if qemu is None: |
| 439 | fail_cmdline() |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 440 | except QMPShellBadPort: |
| 441 | die('bad port number in command-line') |
| 442 | |
| 443 | try: |
Marc-André Lureau | c5e397d | 2017-05-04 16:54:29 +0400 | [diff] [blame] | 444 | qemu.connect(negotiate) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 445 | except qmp.QMPConnectError: |
| 446 | die('Didn\'t get QMP greeting message') |
| 447 | except qmp.QMPCapabilitiesError: |
| 448 | die('Could not negotiate capabilities') |
| 449 | except qemu.error: |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 450 | die('Could not connect to %s' % addr) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 451 | |
| 452 | qemu.show_banner() |
John Snow | 1ceca07 | 2015-04-29 15:14:04 -0400 | [diff] [blame] | 453 | qemu.set_verbosity(verbose) |
John Snow | 30bd681 | 2015-04-29 15:14:03 -0400 | [diff] [blame] | 454 | while qemu.read_exec_command(qemu.get_prompt()): |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 455 | pass |
| 456 | qemu.close() |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 457 | |
| 458 | if __name__ == '__main__': |
| 459 | main() |