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