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 |
| 34 | import readline |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 35 | import sys |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 36 | import pprint |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 37 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 38 | class QMPCompleter(list): |
| 39 | def complete(self, text, state): |
| 40 | for cmd in self: |
| 41 | if cmd.startswith(text): |
| 42 | if not state: |
| 43 | return cmd |
| 44 | else: |
| 45 | state -= 1 |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 46 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 47 | class QMPShellError(Exception): |
| 48 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 49 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 50 | class QMPShellBadPort(QMPShellError): |
| 51 | pass |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 52 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 53 | # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and |
| 54 | # _execute_cmd()). Let's design a better one. |
| 55 | class QMPShell(qmp.QEMUMonitorProtocol): |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 56 | def __init__(self, address, pp=None): |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 57 | qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) |
| 58 | self._greeting = None |
| 59 | self._completer = None |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 60 | self._pp = pp |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 61 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 62 | def __get_address(self, arg): |
| 63 | """ |
| 64 | Figure out if the argument is in the port:host form, if it's not it's |
| 65 | probably a file path. |
| 66 | """ |
| 67 | addr = arg.split(':') |
| 68 | if len(addr) == 2: |
| 69 | try: |
| 70 | port = int(addr[1]) |
| 71 | except ValueError: |
| 72 | raise QMPShellBadPort |
| 73 | return ( addr[0], port ) |
| 74 | # socket path |
| 75 | return arg |
| 76 | |
| 77 | def _fill_completion(self): |
| 78 | for cmd in self.cmd('query-commands')['return']: |
| 79 | self._completer.append(cmd['name']) |
| 80 | |
| 81 | def __completer_setup(self): |
| 82 | self._completer = QMPCompleter() |
| 83 | self._fill_completion() |
| 84 | readline.set_completer(self._completer.complete) |
| 85 | readline.parse_and_bind("tab: complete") |
| 86 | # XXX: default delimiters conflict with some command names (eg. query-), |
| 87 | # clearing everything as it doesn't seem to matter |
| 88 | readline.set_completer_delims('') |
| 89 | |
| 90 | def __build_cmd(self, cmdline): |
| 91 | """ |
| 92 | Build a QMP input object from a user provided command-line in the |
| 93 | following format: |
| 94 | |
| 95 | < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
| 96 | """ |
| 97 | cmdargs = cmdline.split() |
| 98 | qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } |
| 99 | for arg in cmdargs[1:]: |
| 100 | opt = arg.split('=') |
| 101 | try: |
Zhangleiqiang | 74bc906 | 2013-05-06 08:31:23 +0000 | [diff] [blame] | 102 | if(len(opt) > 2): |
| 103 | opt[1] = '='.join(opt[1:]) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 104 | value = int(opt[1]) |
| 105 | except ValueError: |
Igor Mammedov | e5ecec7 | 2013-03-25 15:48:46 +0100 | [diff] [blame] | 106 | if opt[1] == 'true': |
| 107 | value = True |
| 108 | elif opt[1] == 'false': |
| 109 | value = False |
| 110 | else: |
| 111 | value = opt[1] |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 112 | qmpcmd['arguments'][opt[0]] = value |
| 113 | return qmpcmd |
| 114 | |
| 115 | def _execute_cmd(self, cmdline): |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 116 | try: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 117 | qmpcmd = self.__build_cmd(cmdline) |
| 118 | except: |
| 119 | print 'command format: <command-name> ', |
| 120 | print '[arg-name1=arg1] ... [arg-nameN=argN]' |
| 121 | return True |
| 122 | resp = self.cmd_obj(qmpcmd) |
| 123 | if resp is None: |
| 124 | print 'Disconnected' |
| 125 | return False |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 126 | |
| 127 | if self._pp is not None: |
| 128 | self._pp.pprint(resp) |
| 129 | else: |
| 130 | print resp |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 131 | return True |
| 132 | |
| 133 | def connect(self): |
| 134 | self._greeting = qmp.QEMUMonitorProtocol.connect(self) |
| 135 | self.__completer_setup() |
| 136 | |
| 137 | def show_banner(self, msg='Welcome to the QMP low-level shell!'): |
| 138 | print msg |
| 139 | version = self._greeting['QMP']['version']['qemu'] |
| 140 | print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) |
| 141 | |
| 142 | def read_exec_command(self, prompt): |
| 143 | """ |
| 144 | Read and execute a command. |
| 145 | |
| 146 | @return True if execution was ok, return False if disconnected. |
| 147 | """ |
| 148 | try: |
| 149 | cmdline = raw_input(prompt) |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 150 | except EOFError: |
| 151 | print |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 152 | return False |
| 153 | if cmdline == '': |
| 154 | for ev in self.get_events(): |
| 155 | print ev |
| 156 | self.clear_events() |
| 157 | return True |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 158 | else: |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 159 | return self._execute_cmd(cmdline) |
| 160 | |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 161 | class HMPShell(QMPShell): |
| 162 | def __init__(self, address): |
| 163 | QMPShell.__init__(self, address) |
| 164 | self.__cpu_index = 0 |
| 165 | |
| 166 | def __cmd_completion(self): |
| 167 | for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): |
| 168 | if cmd and cmd[0] != '[' and cmd[0] != '\t': |
| 169 | name = cmd.split()[0] # drop help text |
| 170 | if name == 'info': |
| 171 | continue |
| 172 | if name.find('|') != -1: |
| 173 | # Command in the form 'foobar|f' or 'f|foobar', take the |
| 174 | # full name |
| 175 | opt = name.split('|') |
| 176 | if len(opt[0]) == 1: |
| 177 | name = opt[1] |
| 178 | else: |
| 179 | name = opt[0] |
| 180 | self._completer.append(name) |
| 181 | self._completer.append('help ' + name) # help completion |
| 182 | |
| 183 | def __info_completion(self): |
| 184 | for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): |
| 185 | if cmd: |
| 186 | self._completer.append('info ' + cmd.split()[1]) |
| 187 | |
| 188 | def __other_completion(self): |
| 189 | # special cases |
| 190 | self._completer.append('help info') |
| 191 | |
| 192 | def _fill_completion(self): |
| 193 | self.__cmd_completion() |
| 194 | self.__info_completion() |
| 195 | self.__other_completion() |
| 196 | |
| 197 | def __cmd_passthrough(self, cmdline, cpu_index = 0): |
| 198 | return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': |
| 199 | { 'command-line': cmdline, |
| 200 | 'cpu-index': cpu_index } }) |
| 201 | |
| 202 | def _execute_cmd(self, cmdline): |
| 203 | if cmdline.split()[0] == "cpu": |
| 204 | # trap the cpu command, it requires special setting |
| 205 | try: |
| 206 | idx = int(cmdline.split()[1]) |
| 207 | if not 'return' in self.__cmd_passthrough('info version', idx): |
| 208 | print 'bad CPU index' |
| 209 | return True |
| 210 | self.__cpu_index = idx |
| 211 | except ValueError: |
| 212 | print 'cpu command takes an integer argument' |
| 213 | return True |
| 214 | resp = self.__cmd_passthrough(cmdline, self.__cpu_index) |
| 215 | if resp is None: |
| 216 | print 'Disconnected' |
| 217 | return False |
| 218 | assert 'return' in resp or 'error' in resp |
| 219 | if 'return' in resp: |
| 220 | # Success |
| 221 | if len(resp['return']) > 0: |
| 222 | print resp['return'], |
| 223 | else: |
| 224 | # Error |
| 225 | print '%s: %s' % (resp['error']['class'], resp['error']['desc']) |
| 226 | return True |
| 227 | |
| 228 | def show_banner(self): |
| 229 | QMPShell.show_banner(self, msg='Welcome to the HMP shell!') |
| 230 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 231 | def die(msg): |
| 232 | sys.stderr.write('ERROR: %s\n' % msg) |
| 233 | sys.exit(1) |
| 234 | |
| 235 | def fail_cmdline(option=None): |
| 236 | if option: |
| 237 | sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 238 | 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] | 239 | sys.exit(1) |
| 240 | |
| 241 | def main(): |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 242 | addr = '' |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 243 | qemu = None |
| 244 | hmp = False |
| 245 | pp = None |
| 246 | |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 247 | try: |
Daniel P. Berrange | fa779b6 | 2012-08-15 11:33:47 +0100 | [diff] [blame] | 248 | for arg in sys.argv[1:]: |
| 249 | if arg == "-H": |
| 250 | if qemu is not None: |
| 251 | fail_cmdline(arg) |
| 252 | hmp = True |
| 253 | elif arg == "-p": |
| 254 | if pp is not None: |
| 255 | fail_cmdline(arg) |
| 256 | pp = pprint.PrettyPrinter(indent=4) |
| 257 | else: |
| 258 | if qemu is not None: |
| 259 | fail_cmdline(arg) |
| 260 | if hmp: |
| 261 | qemu = HMPShell(arg) |
| 262 | else: |
| 263 | qemu = QMPShell(arg, pp) |
| 264 | addr = arg |
| 265 | |
| 266 | if qemu is None: |
| 267 | fail_cmdline() |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 268 | except QMPShellBadPort: |
| 269 | die('bad port number in command-line') |
| 270 | |
| 271 | try: |
| 272 | qemu.connect() |
| 273 | except qmp.QMPConnectError: |
| 274 | die('Didn\'t get QMP greeting message') |
| 275 | except qmp.QMPCapabilitiesError: |
| 276 | die('Could not negotiate capabilities') |
| 277 | except qemu.error: |
Luiz Capitulino | 11217a7 | 2010-10-28 13:28:37 -0200 | [diff] [blame] | 278 | die('Could not connect to %s' % addr) |
Luiz Capitulino | 9bed0d0 | 2010-10-27 17:57:51 -0200 | [diff] [blame] | 279 | |
| 280 | qemu.show_banner() |
| 281 | while qemu.read_exec_command('(QEMU) '): |
| 282 | pass |
| 283 | qemu.close() |
Luiz Capitulino | cedebda | 2009-11-26 22:59:09 -0200 | [diff] [blame] | 284 | |
| 285 | if __name__ == '__main__': |
| 286 | main() |