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