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