Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 1 | #!/usr/bin/python |
| 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) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 32 | |
| 33 | import qmp |
Stefan Hajnoczi | ff9ec34 | 2014-01-29 12:17:31 +0100 | [diff] [blame] | 34 | import json |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 35 | import readline |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 36 | import sys |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 37 | import pprint |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 38 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 39 | class 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 |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 47 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 48 | class QMPShellError(Exception): |
| 49 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 50 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 51 | class QMPShellBadPort(QMPShellError): |
| 52 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 53 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 54 | # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and |
| 55 | # _execute_cmd()). Let's design a better one. |
| 56 | class QMPShell(qmp.QEMUMonitorProtocol): |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 57 | def __init__(self, address, pp=None): |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 58 | qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) |
| 59 | self._greeting = None |
| 60 | self._completer = None |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 61 | self._pp = pp |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 62 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 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: |
Luiz Capitulino | 22f3946 | 2013-09-10 16:39:23 -0400 | [diff] [blame] | 95 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 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: |
Zhangleiqiang | 74bc906 | 2013-05-06 08:31:23 +0000 | [diff] [blame] | 103 | if(len(opt) > 2): |
| 104 | opt[1] = '='.join(opt[1:]) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 105 | value = int(opt[1]) |
| 106 | except ValueError: |
Igor Mammedov | e5ecec7 | 2013-03-25 15:48:46 +0100 | [diff] [blame] | 107 | if opt[1] == 'true': |
| 108 | value = True |
| 109 | elif opt[1] == 'false': |
| 110 | value = False |
Stefan Hajnoczi | ff9ec34 | 2014-01-29 12:17:31 +0100 | [diff] [blame] | 111 | elif opt[1].startswith('{'): |
| 112 | value = json.loads(opt[1]) |
Igor Mammedov | e5ecec7 | 2013-03-25 15:48:46 +0100 | [diff] [blame] | 113 | else: |
| 114 | value = opt[1] |
Fam Zheng | cd159d0 | 2014-02-12 11:05:13 +0800 | [diff] [blame] | 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 |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 131 | return qmpcmd |
| 132 | |
| 133 | def _execute_cmd(self, cmdline): |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 134 | try: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 135 | qmpcmd = self.__build_cmd(cmdline) |
Fam Zheng | cd159d0 | 2014-02-12 11:05:13 +0800 | [diff] [blame] | 136 | except Exception, e: |
| 137 | print 'Error while parsing command line: %s' % e |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 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 |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 145 | |
| 146 | if self._pp is not None: |
| 147 | self._pp.pprint(resp) |
| 148 | else: |
| 149 | print resp |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 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) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 169 | except EOFError: |
| 170 | print |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 171 | return False |
| 172 | if cmdline == '': |
| 173 | for ev in self.get_events(): |
| 174 | print ev |
| 175 | self.clear_events() |
| 176 | return True |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 177 | else: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 178 | return self._execute_cmd(cmdline) |
| 179 | |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 180 | class 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 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 250 | def die(msg): |
| 251 | sys.stderr.write('ERROR: %s\n' % msg) |
| 252 | sys.exit(1) |
| 253 | |
| 254 | def fail_cmdline(option=None): |
| 255 | if option: |
| 256 | sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 257 | sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 258 | sys.exit(1) |
| 259 | |
| 260 | def main(): |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 261 | addr = '' |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 262 | qemu = None |
| 263 | hmp = False |
| 264 | pp = None |
| 265 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 266 | try: |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 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() |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 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: |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 297 | die('Could not connect to %s' % addr) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 298 | |
| 299 | qemu.show_banner() |
| 300 | while qemu.read_exec_command('(QEMU) '): |
| 301 | pass |
| 302 | qemu.close() |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 303 | |
| 304 | if __name__ == '__main__': |
| 305 | main() |