Merge remote-tracking branch 'remotes/mcayland/tags/qemu-openbios-signed' into staging
[qemu/ar7.git] / scripts / qmp / qmp-shell
blobbe449de621b9d53c8502fd8b4364c22781e18219
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 import qmp
69 import json
70 import ast
71 import readline
72 import sys
73 import os
74 import errno
75 import atexit
77 class 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
86 class QMPShellError(Exception):
87 pass
89 class QMPShellBadPort(QMPShellError):
90 pass
92 class 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
105 # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
106 # _execute_cmd()). Let's design a better one.
107 class QMPShell(qmp.QEMUMonitorProtocol):
108 def __init__(self, address, pretty=False):
109 super(QMPShell, self).__init__(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')
118 def __get_address(self, arg):
120 Figure out if the argument is in the port:host form, if it's not it's
121 probably a file path.
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
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'])
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)
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)
165 def __parse_value(self, val):
166 try:
167 return int(val)
168 except ValueError:
169 pass
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
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)
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
214 def __build_cmd(self, cmdline):
216 Build a QMP input object from a user provided command-line in the
217 following format:
219 < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
221 cmdargs = cmdline.split()
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
236 # Nothing to process?
237 if not cmdargs:
238 return None
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
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
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)
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
283 def connect(self, negotiate):
284 self._greeting = super(QMPShell, self).connect(negotiate)
285 self.__completer_setup()
287 def show_banner(self, msg='Welcome to the QMP low-level shell!'):
288 print msg
289 if not self._greeting:
290 print 'Connected'
291 return
292 version = self._greeting['QMP']['version']['qemu']
293 print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
295 def get_prompt(self):
296 if self._transmode:
297 return "TRANS> "
298 return "(QEMU) "
300 def read_exec_command(self, prompt):
302 Read and execute a command.
304 @return True if execution was ok, return False if disconnected.
306 try:
307 cmdline = raw_input(prompt)
308 except EOFError:
309 print
310 return False
311 if cmdline == '':
312 for ev in self.get_events():
313 print ev
314 self.clear_events()
315 return True
316 else:
317 return self._execute_cmd(cmdline)
319 def set_verbosity(self, verbose):
320 self._verbose = verbose
322 class HMPShell(QMPShell):
323 def __init__(self, address):
324 QMPShell.__init__(self, address)
325 self.__cpu_index = 0
327 def __cmd_completion(self):
328 for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
329 if cmd and cmd[0] != '[' and cmd[0] != '\t':
330 name = cmd.split()[0] # drop help text
331 if name == 'info':
332 continue
333 if name.find('|') != -1:
334 # Command in the form 'foobar|f' or 'f|foobar', take the
335 # full name
336 opt = name.split('|')
337 if len(opt[0]) == 1:
338 name = opt[1]
339 else:
340 name = opt[0]
341 self._completer.append(name)
342 self._completer.append('help ' + name) # help completion
344 def __info_completion(self):
345 for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
346 if cmd:
347 self._completer.append('info ' + cmd.split()[1])
349 def __other_completion(self):
350 # special cases
351 self._completer.append('help info')
353 def _fill_completion(self):
354 self.__cmd_completion()
355 self.__info_completion()
356 self.__other_completion()
358 def __cmd_passthrough(self, cmdline, cpu_index = 0):
359 return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
360 { 'command-line': cmdline,
361 'cpu-index': cpu_index } })
363 def _execute_cmd(self, cmdline):
364 if cmdline.split()[0] == "cpu":
365 # trap the cpu command, it requires special setting
366 try:
367 idx = int(cmdline.split()[1])
368 if not 'return' in self.__cmd_passthrough('info version', idx):
369 print 'bad CPU index'
370 return True
371 self.__cpu_index = idx
372 except ValueError:
373 print 'cpu command takes an integer argument'
374 return True
375 resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
376 if resp is None:
377 print 'Disconnected'
378 return False
379 assert 'return' in resp or 'error' in resp
380 if 'return' in resp:
381 # Success
382 if len(resp['return']) > 0:
383 print resp['return'],
384 else:
385 # Error
386 print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
387 return True
389 def show_banner(self):
390 QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
392 def die(msg):
393 sys.stderr.write('ERROR: %s\n' % msg)
394 sys.exit(1)
396 def fail_cmdline(option=None):
397 if option:
398 sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
399 sys.stderr.write('qmp-shell [ -v ] [ -p ] [ -H ] [ -N ] < UNIX socket path> | < TCP address:port >\n')
400 sys.stderr.write(' -v Verbose (echo command sent and received)\n')
401 sys.stderr.write(' -p Pretty-print JSON\n')
402 sys.stderr.write(' -H Use HMP interface\n')
403 sys.stderr.write(' -N Skip negotiate (for qemu-ga)\n')
404 sys.exit(1)
406 def main():
407 addr = ''
408 qemu = None
409 hmp = False
410 pretty = False
411 verbose = False
412 negotiate = True
414 try:
415 for arg in sys.argv[1:]:
416 if arg == "-H":
417 if qemu is not None:
418 fail_cmdline(arg)
419 hmp = True
420 elif arg == "-p":
421 pretty = True
422 elif arg == "-N":
423 negotiate = False
424 elif arg == "-v":
425 verbose = True
426 else:
427 if qemu is not None:
428 fail_cmdline(arg)
429 if hmp:
430 qemu = HMPShell(arg)
431 else:
432 qemu = QMPShell(arg, pretty)
433 addr = arg
435 if qemu is None:
436 fail_cmdline()
437 except QMPShellBadPort:
438 die('bad port number in command-line')
440 try:
441 qemu.connect(negotiate)
442 except qmp.QMPConnectError:
443 die('Didn\'t get QMP greeting message')
444 except qmp.QMPCapabilitiesError:
445 die('Could not negotiate capabilities')
446 except qemu.error:
447 die('Could not connect to %s' % addr)
449 qemu.show_banner()
450 qemu.set_verbosity(verbose)
451 while qemu.read_exec_command(qemu.get_prompt()):
452 pass
453 qemu.close()
455 if __name__ == '__main__':
456 main()