| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | #!/usr/bin/python | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | # Low-level QEMU shell on top of QMP. | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | # Copyright (C) 2009, 2010 Red Hat Inc. | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | # | 
					
						
							|  |  |  | # Authors: | 
					
						
							|  |  |  | #  Luiz Capitulino <lcapitulino@redhat.com> | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This work is licensed under the terms of the GNU GPL, version 2.  See | 
					
						
							|  |  |  | # the COPYING file in the top-level directory. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Usage: | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Start QEMU with: | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | # # qemu [...] -qmp unix:./qmp-sock,server | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | # | 
					
						
							|  |  |  | # Run the shell: | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | # $ qmp-shell ./qmp-sock | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | # | 
					
						
							|  |  |  | # Commands have the following format: | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | #    < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | # | 
					
						
							|  |  |  | # For example: | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | # (QEMU) device_add driver=e1000 id=net1 | 
					
						
							|  |  |  | # {u'return': {}} | 
					
						
							|  |  |  | # (QEMU) | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import qmp | 
					
						
							| 
									
										
										
										
											2014-01-29 12:17:31 +01:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | import readline | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  | import pprint | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | class QMPCompleter(list): | 
					
						
							|  |  |  |     def complete(self, text, state): | 
					
						
							|  |  |  |         for cmd in self: | 
					
						
							|  |  |  |             if cmd.startswith(text): | 
					
						
							|  |  |  |                 if not state: | 
					
						
							|  |  |  |                     return cmd | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     state -= 1 | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | class QMPShellError(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class QMPShellBadPort(QMPShellError): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and | 
					
						
							|  |  |  | #       _execute_cmd()). Let's design a better one. | 
					
						
							|  |  |  | class QMPShell(qmp.QEMUMonitorProtocol): | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  |     def __init__(self, address, pp=None): | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |         qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) | 
					
						
							|  |  |  |         self._greeting = None | 
					
						
							|  |  |  |         self._completer = None | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  |         self._pp = pp | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __get_address(self, arg): | 
					
						
							|  |  |  |         """ | 
					
						
							|  |  |  |         Figure out if the argument is in the port:host form, if it's not it's | 
					
						
							|  |  |  |         probably a file path. | 
					
						
							|  |  |  |         """ | 
					
						
							|  |  |  |         addr = arg.split(':') | 
					
						
							|  |  |  |         if len(addr) == 2: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 port = int(addr[1]) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 raise QMPShellBadPort | 
					
						
							|  |  |  |             return ( addr[0], port ) | 
					
						
							|  |  |  |         # socket path | 
					
						
							|  |  |  |         return arg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _fill_completion(self): | 
					
						
							|  |  |  |         for cmd in self.cmd('query-commands')['return']: | 
					
						
							|  |  |  |             self._completer.append(cmd['name']) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __completer_setup(self): | 
					
						
							|  |  |  |         self._completer = QMPCompleter() | 
					
						
							|  |  |  |         self._fill_completion() | 
					
						
							|  |  |  |         readline.set_completer(self._completer.complete) | 
					
						
							|  |  |  |         readline.parse_and_bind("tab: complete") | 
					
						
							|  |  |  |         # XXX: default delimiters conflict with some command names (eg. query-), | 
					
						
							|  |  |  |         # clearing everything as it doesn't seem to matter | 
					
						
							|  |  |  |         readline.set_completer_delims('') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __build_cmd(self, cmdline): | 
					
						
							|  |  |  |         """ | 
					
						
							|  |  |  |         Build a QMP input object from a user provided command-line in the | 
					
						
							|  |  |  |         following format: | 
					
						
							| 
									
										
										
										
											2013-09-10 16:39:23 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |             < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] | 
					
						
							|  |  |  |         """ | 
					
						
							|  |  |  |         cmdargs = cmdline.split() | 
					
						
							|  |  |  |         qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } | 
					
						
							|  |  |  |         for arg in cmdargs[1:]: | 
					
						
							|  |  |  |             opt = arg.split('=') | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2013-05-06 08:31:23 +00:00
										 |  |  |                 if(len(opt) > 2): | 
					
						
							|  |  |  |                     opt[1] = '='.join(opt[1:]) | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |                 value = int(opt[1]) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							| 
									
										
										
										
											2013-03-25 15:48:46 +01:00
										 |  |  |                 if opt[1] == 'true': | 
					
						
							|  |  |  |                     value = True | 
					
						
							|  |  |  |                 elif opt[1] == 'false': | 
					
						
							|  |  |  |                     value = False | 
					
						
							| 
									
										
										
										
											2014-01-29 12:17:31 +01:00
										 |  |  |                 elif opt[1].startswith('{'): | 
					
						
							|  |  |  |                     value = json.loads(opt[1]) | 
					
						
							| 
									
										
										
										
											2013-03-25 15:48:46 +01:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     value = opt[1] | 
					
						
							| 
									
										
										
										
											2014-02-12 11:05:13 +08:00
										 |  |  |             optpath = opt[0].split('.') | 
					
						
							|  |  |  |             parent = qmpcmd['arguments'] | 
					
						
							|  |  |  |             curpath = [] | 
					
						
							|  |  |  |             for p in optpath[:-1]: | 
					
						
							|  |  |  |                 curpath.append(p) | 
					
						
							|  |  |  |                 d = parent.get(p, {}) | 
					
						
							|  |  |  |                 if type(d) is not dict: | 
					
						
							|  |  |  |                     raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) | 
					
						
							|  |  |  |                 parent[p] = d | 
					
						
							|  |  |  |                 parent = d | 
					
						
							|  |  |  |             if optpath[-1] in parent: | 
					
						
							|  |  |  |                 if type(parent[optpath[-1]]) is dict: | 
					
						
							|  |  |  |                     raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath)) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     raise QMPShellError('Cannot set "%s" multiple times' % opt[0]) | 
					
						
							|  |  |  |             parent[optpath[-1]] = value | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |         return qmpcmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _execute_cmd(self, cmdline): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             qmpcmd = self.__build_cmd(cmdline) | 
					
						
							| 
									
										
										
										
											2014-02-12 11:05:13 +08:00
										 |  |  |         except Exception, e: | 
					
						
							|  |  |  |             print 'Error while parsing command line: %s' % e | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |             print 'command format: <command-name> ', | 
					
						
							|  |  |  |             print '[arg-name1=arg1] ... [arg-nameN=argN]' | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         resp = self.cmd_obj(qmpcmd) | 
					
						
							|  |  |  |         if resp is None: | 
					
						
							|  |  |  |             print 'Disconnected' | 
					
						
							|  |  |  |             return False | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if self._pp is not None: | 
					
						
							|  |  |  |             self._pp.pprint(resp) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print resp | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def connect(self): | 
					
						
							|  |  |  |         self._greeting = qmp.QEMUMonitorProtocol.connect(self) | 
					
						
							|  |  |  |         self.__completer_setup() | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |     def show_banner(self, msg='Welcome to the QMP low-level shell!'): | 
					
						
							|  |  |  |         print msg | 
					
						
							|  |  |  |         version = self._greeting['QMP']['version']['qemu'] | 
					
						
							|  |  |  |         print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |     def read_exec_command(self, prompt): | 
					
						
							|  |  |  |         """ | 
					
						
							|  |  |  |         Read and execute a command. | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |         @return True if execution was ok, return False if disconnected. | 
					
						
							|  |  |  |         """ | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |             cmdline = raw_input(prompt) | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  |         except EOFError: | 
					
						
							|  |  |  |             print | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |             return False | 
					
						
							|  |  |  |         if cmdline == '': | 
					
						
							|  |  |  |             for ev in self.get_events(): | 
					
						
							|  |  |  |                 print ev | 
					
						
							|  |  |  |             self.clear_events() | 
					
						
							|  |  |  |             return True | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |             return self._execute_cmd(cmdline) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-28 13:28:37 -02:00
										 |  |  | class HMPShell(QMPShell): | 
					
						
							|  |  |  |     def __init__(self, address): | 
					
						
							|  |  |  |         QMPShell.__init__(self, address) | 
					
						
							|  |  |  |         self.__cpu_index = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __cmd_completion(self): | 
					
						
							|  |  |  |         for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): | 
					
						
							|  |  |  |             if cmd and cmd[0] != '[' and cmd[0] != '\t': | 
					
						
							|  |  |  |                 name = cmd.split()[0] # drop help text | 
					
						
							|  |  |  |                 if name == 'info': | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if name.find('|') != -1: | 
					
						
							|  |  |  |                     # Command in the form 'foobar|f' or 'f|foobar', take the | 
					
						
							|  |  |  |                     # full name | 
					
						
							|  |  |  |                     opt = name.split('|') | 
					
						
							|  |  |  |                     if len(opt[0]) == 1: | 
					
						
							|  |  |  |                         name = opt[1] | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         name = opt[0] | 
					
						
							|  |  |  |                 self._completer.append(name) | 
					
						
							|  |  |  |                 self._completer.append('help ' + name) # help completion | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __info_completion(self): | 
					
						
							|  |  |  |         for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): | 
					
						
							|  |  |  |             if cmd: | 
					
						
							|  |  |  |                 self._completer.append('info ' + cmd.split()[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __other_completion(self): | 
					
						
							|  |  |  |         # special cases | 
					
						
							|  |  |  |         self._completer.append('help info') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _fill_completion(self): | 
					
						
							|  |  |  |         self.__cmd_completion() | 
					
						
							|  |  |  |         self.__info_completion() | 
					
						
							|  |  |  |         self.__other_completion() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __cmd_passthrough(self, cmdline, cpu_index = 0): | 
					
						
							|  |  |  |         return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': | 
					
						
							|  |  |  |                               { 'command-line': cmdline, | 
					
						
							|  |  |  |                                 'cpu-index': cpu_index } }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _execute_cmd(self, cmdline): | 
					
						
							|  |  |  |         if cmdline.split()[0] == "cpu": | 
					
						
							|  |  |  |             # trap the cpu command, it requires special setting | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 idx = int(cmdline.split()[1]) | 
					
						
							|  |  |  |                 if not 'return' in self.__cmd_passthrough('info version', idx): | 
					
						
							|  |  |  |                     print 'bad CPU index' | 
					
						
							|  |  |  |                     return True | 
					
						
							|  |  |  |                 self.__cpu_index = idx | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 print 'cpu command takes an integer argument' | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  |         resp = self.__cmd_passthrough(cmdline, self.__cpu_index) | 
					
						
							|  |  |  |         if resp is None: | 
					
						
							|  |  |  |             print 'Disconnected' | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         assert 'return' in resp or 'error' in resp | 
					
						
							|  |  |  |         if 'return' in resp: | 
					
						
							|  |  |  |             # Success | 
					
						
							|  |  |  |             if len(resp['return']) > 0: | 
					
						
							|  |  |  |                 print resp['return'], | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Error | 
					
						
							|  |  |  |             print '%s: %s' % (resp['error']['class'], resp['error']['desc']) | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def show_banner(self): | 
					
						
							|  |  |  |         QMPShell.show_banner(self, msg='Welcome to the HMP shell!') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | def die(msg): | 
					
						
							|  |  |  |     sys.stderr.write('ERROR: %s\n' % msg) | 
					
						
							|  |  |  |     sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def fail_cmdline(option=None): | 
					
						
							|  |  |  |     if option: | 
					
						
							|  |  |  |         sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  |     sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |     sys.exit(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main(): | 
					
						
							| 
									
										
										
										
											2010-10-28 13:28:37 -02:00
										 |  |  |     addr = '' | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  |     qemu = None | 
					
						
							|  |  |  |     hmp = False | 
					
						
							|  |  |  |     pp = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2012-08-15 11:33:47 +01:00
										 |  |  |         for arg in sys.argv[1:]: | 
					
						
							|  |  |  |             if arg == "-H": | 
					
						
							|  |  |  |                 if qemu is not None: | 
					
						
							|  |  |  |                     fail_cmdline(arg) | 
					
						
							|  |  |  |                 hmp = True | 
					
						
							|  |  |  |             elif arg == "-p": | 
					
						
							|  |  |  |                 if pp is not None: | 
					
						
							|  |  |  |                     fail_cmdline(arg) | 
					
						
							|  |  |  |                 pp = pprint.PrettyPrinter(indent=4) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 if qemu is not None: | 
					
						
							|  |  |  |                     fail_cmdline(arg) | 
					
						
							|  |  |  |                 if hmp: | 
					
						
							|  |  |  |                     qemu = HMPShell(arg) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     qemu = QMPShell(arg, pp) | 
					
						
							|  |  |  |                 addr = arg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if qemu is None: | 
					
						
							|  |  |  |             fail_cmdline() | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  |     except QMPShellBadPort: | 
					
						
							|  |  |  |         die('bad port number in command-line') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         qemu.connect() | 
					
						
							|  |  |  |     except qmp.QMPConnectError: | 
					
						
							|  |  |  |         die('Didn\'t get QMP greeting message') | 
					
						
							|  |  |  |     except qmp.QMPCapabilitiesError: | 
					
						
							|  |  |  |         die('Could not negotiate capabilities') | 
					
						
							|  |  |  |     except qemu.error: | 
					
						
							| 
									
										
										
										
											2010-10-28 13:28:37 -02:00
										 |  |  |         die('Could not connect to %s' % addr) | 
					
						
							| 
									
										
										
										
											2010-10-27 17:57:51 -02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     qemu.show_banner() | 
					
						
							|  |  |  |     while qemu.read_exec_command('(QEMU) '): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  |     qemu.close() | 
					
						
							| 
									
										
										
										
											2009-11-26 22:59:09 -02:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     main() |