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