1#!/usr/bin/python 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 33import qmp 34import json 35import ast 36import readline 37import sys 38import pprint 39 40class QMPCompleter(list): 41 def complete(self, text, state): 42 for cmd in self: 43 if cmd.startswith(text): 44 if not state: 45 return cmd 46 else: 47 state -= 1 48 49class QMPShellError(Exception): 50 pass 51 52class QMPShellBadPort(QMPShellError): 53 pass 54 55class FuzzyJSON(ast.NodeTransformer): 56 '''This extension of ast.NodeTransformer filters literal "true/false/null" 57 values in an AST and replaces them by proper "True/False/None" values that 58 Python can properly evaluate.''' 59 def visit_Name(self, node): 60 if node.id == 'true': 61 node.id = 'True' 62 if node.id == 'false': 63 node.id = 'False' 64 if node.id == 'null': 65 node.id = 'None' 66 return node 67 68# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and 69# _execute_cmd()). Let's design a better one. 70class QMPShell(qmp.QEMUMonitorProtocol): 71 def __init__(self, address, pp=None): 72 qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) 73 self._greeting = None 74 self._completer = None 75 self._pp = pp 76 77 def __get_address(self, arg): 78 """ 79 Figure out if the argument is in the port:host form, if it's not it's 80 probably a file path. 81 """ 82 addr = arg.split(':') 83 if len(addr) == 2: 84 try: 85 port = int(addr[1]) 86 except ValueError: 87 raise QMPShellBadPort 88 return ( addr[0], port ) 89 # socket path 90 return arg 91 92 def _fill_completion(self): 93 for cmd in self.cmd('query-commands')['return']: 94 self._completer.append(cmd['name']) 95 96 def __completer_setup(self): 97 self._completer = QMPCompleter() 98 self._fill_completion() 99 readline.set_completer(self._completer.complete) 100 readline.parse_and_bind("tab: complete") 101 # XXX: default delimiters conflict with some command names (eg. query-), 102 # clearing everything as it doesn't seem to matter 103 readline.set_completer_delims('') 104 105 def __parse_value(self, val): 106 try: 107 return int(val) 108 except ValueError: 109 pass 110 111 if val.lower() == 'true': 112 return True 113 if val.lower() == 'false': 114 return False 115 if val.startswith(('{', '[')): 116 # Try first as pure JSON: 117 try: 118 return json.loads(val) 119 except ValueError: 120 pass 121 # Try once again as FuzzyJSON: 122 try: 123 st = ast.parse(val, mode='eval') 124 return ast.literal_eval(FuzzyJSON().visit(st)) 125 except SyntaxError: 126 pass 127 except ValueError: 128 pass 129 return val 130 131 def __cli_expr(self, tokens, parent): 132 for arg in tokens: 133 (key, _, val) = arg.partition('=') 134 if not val: 135 raise QMPShellError("Expected a key=value pair, got '%s'" % arg) 136 137 value = self.__parse_value(val) 138 optpath = key.split('.') 139 curpath = [] 140 for p in optpath[:-1]: 141 curpath.append(p) 142 d = parent.get(p, {}) 143 if type(d) is not dict: 144 raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) 145 parent[p] = d 146 parent = d 147 if optpath[-1] in parent: 148 if type(parent[optpath[-1]]) is dict: 149 raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) 150 else: 151 raise QMPShellError('Cannot set "%s" multiple times' % key) 152 parent[optpath[-1]] = value 153 154 def __build_cmd(self, cmdline): 155 """ 156 Build a QMP input object from a user provided command-line in the 157 following format: 158 159 < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] 160 """ 161 cmdargs = cmdline.split() 162 qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } 163 self.__cli_expr(cmdargs[1:], qmpcmd['arguments']) 164 return qmpcmd 165 166 def _execute_cmd(self, cmdline): 167 try: 168 qmpcmd = self.__build_cmd(cmdline) 169 except Exception, e: 170 print 'Error while parsing command line: %s' % e 171 print 'command format: <command-name> ', 172 print '[arg-name1=arg1] ... [arg-nameN=argN]' 173 return True 174 resp = self.cmd_obj(qmpcmd) 175 if resp is None: 176 print 'Disconnected' 177 return False 178 179 if self._pp is not None: 180 self._pp.pprint(resp) 181 else: 182 print resp 183 return True 184 185 def connect(self): 186 self._greeting = qmp.QEMUMonitorProtocol.connect(self) 187 self.__completer_setup() 188 189 def show_banner(self, msg='Welcome to the QMP low-level shell!'): 190 print msg 191 version = self._greeting['QMP']['version']['qemu'] 192 print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) 193 194 def read_exec_command(self, prompt): 195 """ 196 Read and execute a command. 197 198 @return True if execution was ok, return False if disconnected. 199 """ 200 try: 201 cmdline = raw_input(prompt) 202 except EOFError: 203 print 204 return False 205 if cmdline == '': 206 for ev in self.get_events(): 207 print ev 208 self.clear_events() 209 return True 210 else: 211 return self._execute_cmd(cmdline) 212 213class HMPShell(QMPShell): 214 def __init__(self, address): 215 QMPShell.__init__(self, address) 216 self.__cpu_index = 0 217 218 def __cmd_completion(self): 219 for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): 220 if cmd and cmd[0] != '[' and cmd[0] != '\t': 221 name = cmd.split()[0] # drop help text 222 if name == 'info': 223 continue 224 if name.find('|') != -1: 225 # Command in the form 'foobar|f' or 'f|foobar', take the 226 # full name 227 opt = name.split('|') 228 if len(opt[0]) == 1: 229 name = opt[1] 230 else: 231 name = opt[0] 232 self._completer.append(name) 233 self._completer.append('help ' + name) # help completion 234 235 def __info_completion(self): 236 for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): 237 if cmd: 238 self._completer.append('info ' + cmd.split()[1]) 239 240 def __other_completion(self): 241 # special cases 242 self._completer.append('help info') 243 244 def _fill_completion(self): 245 self.__cmd_completion() 246 self.__info_completion() 247 self.__other_completion() 248 249 def __cmd_passthrough(self, cmdline, cpu_index = 0): 250 return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': 251 { 'command-line': cmdline, 252 'cpu-index': cpu_index } }) 253 254 def _execute_cmd(self, cmdline): 255 if cmdline.split()[0] == "cpu": 256 # trap the cpu command, it requires special setting 257 try: 258 idx = int(cmdline.split()[1]) 259 if not 'return' in self.__cmd_passthrough('info version', idx): 260 print 'bad CPU index' 261 return True 262 self.__cpu_index = idx 263 except ValueError: 264 print 'cpu command takes an integer argument' 265 return True 266 resp = self.__cmd_passthrough(cmdline, self.__cpu_index) 267 if resp is None: 268 print 'Disconnected' 269 return False 270 assert 'return' in resp or 'error' in resp 271 if 'return' in resp: 272 # Success 273 if len(resp['return']) > 0: 274 print resp['return'], 275 else: 276 # Error 277 print '%s: %s' % (resp['error']['class'], resp['error']['desc']) 278 return True 279 280 def show_banner(self): 281 QMPShell.show_banner(self, msg='Welcome to the HMP shell!') 282 283def die(msg): 284 sys.stderr.write('ERROR: %s\n' % msg) 285 sys.exit(1) 286 287def fail_cmdline(option=None): 288 if option: 289 sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) 290 sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') 291 sys.exit(1) 292 293def main(): 294 addr = '' 295 qemu = None 296 hmp = False 297 pp = None 298 299 try: 300 for arg in sys.argv[1:]: 301 if arg == "-H": 302 if qemu is not None: 303 fail_cmdline(arg) 304 hmp = True 305 elif arg == "-p": 306 if pp is not None: 307 fail_cmdline(arg) 308 pp = pprint.PrettyPrinter(indent=4) 309 else: 310 if qemu is not None: 311 fail_cmdline(arg) 312 if hmp: 313 qemu = HMPShell(arg) 314 else: 315 qemu = QMPShell(arg, pp) 316 addr = arg 317 318 if qemu is None: 319 fail_cmdline() 320 except QMPShellBadPort: 321 die('bad port number in command-line') 322 323 try: 324 qemu.connect() 325 except qmp.QMPConnectError: 326 die('Didn\'t get QMP greeting message') 327 except qmp.QMPCapabilitiesError: 328 die('Could not negotiate capabilities') 329 except qemu.error: 330 die('Could not connect to %s' % addr) 331 332 qemu.show_banner() 333 while qemu.read_exec_command('(QEMU) '): 334 pass 335 qemu.close() 336 337if __name__ == '__main__': 338 main() 339