]>
Commit | Line | Data |
---|---|---|
cedebdac LC |
1 | #!/usr/bin/python |
2 | # | |
9bed0d0d | 3 | # Low-level QEMU shell on top of QMP. |
cedebdac | 4 | # |
9bed0d0d | 5 | # Copyright (C) 2009, 2010 Red Hat Inc. |
cedebdac LC |
6 | # |
7 | # Authors: | |
8 | # Luiz Capitulino <[email protected]> | |
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 | # | |
9bed0d0d | 17 | # # qemu [...] -qmp unix:./qmp-sock,server |
cedebdac LC |
18 | # |
19 | # Run the shell: | |
20 | # | |
9bed0d0d | 21 | # $ qmp-shell ./qmp-sock |
cedebdac LC |
22 | # |
23 | # Commands have the following format: | |
24 | # | |
9bed0d0d | 25 | # < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
cedebdac LC |
26 | # |
27 | # For example: | |
28 | # | |
9bed0d0d LC |
29 | # (QEMU) device_add driver=e1000 id=net1 |
30 | # {u'return': {}} | |
31 | # (QEMU) | |
cedebdac LC |
32 | |
33 | import qmp | |
34 | import readline | |
9bed0d0d | 35 | import sys |
fa779b65 | 36 | import pprint |
cedebdac | 37 | |
9bed0d0d LC |
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 | |
cedebdac | 46 | |
9bed0d0d LC |
47 | class QMPShellError(Exception): |
48 | pass | |
49 | ||
50 | class QMPShellBadPort(QMPShellError): | |
51 | pass | |
52 | ||
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): | |
fa779b65 | 56 | def __init__(self, address, pp=None): |
9bed0d0d LC |
57 | qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) |
58 | self._greeting = None | |
59 | self._completer = None | |
fa779b65 | 60 | self._pp = pp |
9bed0d0d LC |
61 | |
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: | |
74bc9066 Z |
102 | if(len(opt) > 2): |
103 | opt[1] = '='.join(opt[1:]) | |
9bed0d0d LC |
104 | value = int(opt[1]) |
105 | except ValueError: | |
e5ecec7b IM |
106 | if opt[1] == 'true': |
107 | value = True | |
108 | elif opt[1] == 'false': | |
109 | value = False | |
110 | else: | |
111 | value = opt[1] | |
9bed0d0d LC |
112 | qmpcmd['arguments'][opt[0]] = value |
113 | return qmpcmd | |
114 | ||
115 | def _execute_cmd(self, cmdline): | |
116 | try: | |
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 | |
fa779b65 DB |
126 | |
127 | if self._pp is not None: | |
128 | self._pp.pprint(resp) | |
129 | else: | |
130 | print resp | |
9bed0d0d LC |
131 | return True |
132 | ||
133 | def connect(self): | |
134 | self._greeting = qmp.QEMUMonitorProtocol.connect(self) | |
135 | self.__completer_setup() | |
cedebdac | 136 | |
9bed0d0d LC |
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']) | |
cedebdac | 141 | |
9bed0d0d LC |
142 | def read_exec_command(self, prompt): |
143 | """ | |
144 | Read and execute a command. | |
cedebdac | 145 | |
9bed0d0d LC |
146 | @return True if execution was ok, return False if disconnected. |
147 | """ | |
cedebdac | 148 | try: |
9bed0d0d | 149 | cmdline = raw_input(prompt) |
cedebdac LC |
150 | except EOFError: |
151 | ||
9bed0d0d LC |
152 | return False |
153 | if cmdline == '': | |
154 | for ev in self.get_events(): | |
155 | print ev | |
156 | self.clear_events() | |
157 | return True | |
cedebdac | 158 | else: |
9bed0d0d LC |
159 | return self._execute_cmd(cmdline) |
160 | ||
11217a75 LC |
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 | ||
9bed0d0d LC |
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) | |
fa779b65 | 238 | sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n') |
9bed0d0d LC |
239 | sys.exit(1) |
240 | ||
241 | def main(): | |
11217a75 | 242 | addr = '' |
fa779b65 DB |
243 | qemu = None |
244 | hmp = False | |
245 | pp = None | |
246 | ||
9bed0d0d | 247 | try: |
fa779b65 DB |
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() | |
9bed0d0d LC |
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: | |
11217a75 | 278 | die('Could not connect to %s' % addr) |
9bed0d0d LC |
279 | |
280 | qemu.show_banner() | |
281 | while qemu.read_exec_command('(QEMU) '): | |
282 | pass | |
283 | qemu.close() | |
cedebdac LC |
284 | |
285 | if __name__ == '__main__': | |
286 | main() |