qmp-shell 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. from __future__ import print_function
  68. import json
  69. import ast
  70. import readline
  71. import sys
  72. import os
  73. import errno
  74. import atexit
  75. import re
  76. sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
  77. from qemu import qmp
  78. if sys.version_info[0] == 2:
  79. input = raw_input
  80. class QMPCompleter(list):
  81. def complete(self, text, state):
  82. for cmd in self:
  83. if cmd.startswith(text):
  84. if not state:
  85. return cmd
  86. else:
  87. state -= 1
  88. class QMPShellError(Exception):
  89. pass
  90. class QMPShellBadPort(QMPShellError):
  91. 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
  104. # TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
  105. # _execute_cmd()). Let's design a better one.
  106. class QMPShell(qmp.QEMUMonitorProtocol):
  107. def __init__(self, address, pretty=False):
  108. super(QMPShell, self).__init__(self.__get_address(address))
  109. self._greeting = None
  110. self._completer = None
  111. self._pretty = pretty
  112. self._transmode = False
  113. self._actions = list()
  114. self._histfile = os.path.join(os.path.expanduser('~'),
  115. '.qmp-shell_history')
  116. def __get_address(self, arg):
  117. """
  118. Figure out if the argument is in the port:host form, if it's not it's
  119. probably a file path.
  120. """
  121. addr = arg.split(':')
  122. if len(addr) == 2:
  123. try:
  124. port = int(addr[1])
  125. except ValueError:
  126. raise QMPShellBadPort
  127. return ( addr[0], port )
  128. # socket path
  129. return arg
  130. def _fill_completion(self):
  131. cmds = self.cmd('query-commands')
  132. if 'error' in cmds:
  133. return
  134. for cmd in cmds['return']:
  135. self._completer.append(cmd['name'])
  136. def __completer_setup(self):
  137. self._completer = QMPCompleter()
  138. self._fill_completion()
  139. readline.set_history_length(1024)
  140. readline.set_completer(self._completer.complete)
  141. readline.parse_and_bind("tab: complete")
  142. # XXX: default delimiters conflict with some command names (eg. query-),
  143. # clearing everything as it doesn't seem to matter
  144. readline.set_completer_delims('')
  145. try:
  146. readline.read_history_file(self._histfile)
  147. except Exception as e:
  148. if isinstance(e, IOError) and e.errno == errno.ENOENT:
  149. # File not found. No problem.
  150. pass
  151. else:
  152. print("Failed to read history '%s'; %s" % (self._histfile, e))
  153. atexit.register(self.__save_history)
  154. def __save_history(self):
  155. try:
  156. readline.write_history_file(self._histfile)
  157. except Exception as e:
  158. print("Failed to save history file '%s'; %s" % (self._histfile, e))
  159. def __parse_value(self, val):
  160. try:
  161. return int(val)
  162. except ValueError:
  163. pass
  164. if val.lower() == 'true':
  165. return True
  166. if val.lower() == 'false':
  167. return False
  168. if val.startswith(('{', '[')):
  169. # Try first as pure JSON:
  170. try:
  171. return json.loads(val)
  172. except ValueError:
  173. pass
  174. # Try once again as FuzzyJSON:
  175. try:
  176. st = ast.parse(val, mode='eval')
  177. return ast.literal_eval(FuzzyJSON().visit(st))
  178. except SyntaxError:
  179. pass
  180. except ValueError:
  181. pass
  182. return val
  183. def __cli_expr(self, tokens, parent):
  184. for arg in tokens:
  185. (key, sep, val) = arg.partition('=')
  186. if sep != '=':
  187. raise QMPShellError("Expected a key=value pair, got '%s'" % arg)
  188. value = self.__parse_value(val)
  189. optpath = key.split('.')
  190. curpath = []
  191. for p in optpath[:-1]:
  192. curpath.append(p)
  193. d = parent.get(p, {})
  194. if type(d) is not dict:
  195. raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
  196. parent[p] = d
  197. parent = d
  198. if optpath[-1] in parent:
  199. if type(parent[optpath[-1]]) is dict:
  200. raise QMPShellError('Cannot use "%s" as both leaf and non-leaf key' % '.'.join(curpath))
  201. else:
  202. raise QMPShellError('Cannot set "%s" multiple times' % key)
  203. parent[optpath[-1]] = value
  204. def __build_cmd(self, cmdline):
  205. """
  206. Build a QMP input object from a user provided command-line in the
  207. following format:
  208. < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
  209. """
  210. cmdargs = re.findall(r'''(?:[^\s"']|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+''', cmdline)
  211. # Transactional CLI entry/exit:
  212. if cmdargs[0] == 'transaction(':
  213. self._transmode = True
  214. cmdargs.pop(0)
  215. elif cmdargs[0] == ')' and self._transmode:
  216. self._transmode = False
  217. if len(cmdargs) > 1:
  218. raise QMPShellError("Unexpected input after close of Transaction sub-shell")
  219. qmpcmd = { 'execute': 'transaction',
  220. 'arguments': { 'actions': self._actions } }
  221. self._actions = list()
  222. return qmpcmd
  223. # Nothing to process?
  224. if not cmdargs:
  225. return None
  226. # Parse and then cache this Transactional Action
  227. if self._transmode:
  228. finalize = False
  229. action = { 'type': cmdargs[0], 'data': {} }
  230. if cmdargs[-1] == ')':
  231. cmdargs.pop(-1)
  232. finalize = True
  233. self.__cli_expr(cmdargs[1:], action['data'])
  234. self._actions.append(action)
  235. return self.__build_cmd(')') if finalize else None
  236. # Standard command: parse and return it to be executed.
  237. qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
  238. self.__cli_expr(cmdargs[1:], qmpcmd['arguments'])
  239. return qmpcmd
  240. def _print(self, qmp):
  241. indent = None
  242. if self._pretty:
  243. indent = 4
  244. jsobj = json.dumps(qmp, indent=indent)
  245. print(str(jsobj))
  246. def _execute_cmd(self, cmdline):
  247. try:
  248. qmpcmd = self.__build_cmd(cmdline)
  249. except Exception as e:
  250. print('Error while parsing command line: %s' % e)
  251. print('command format: <command-name> ', end=' ')
  252. print('[arg-name1=arg1] ... [arg-nameN=argN]')
  253. return True
  254. # For transaction mode, we may have just cached the action:
  255. if qmpcmd is None:
  256. return True
  257. if self._verbose:
  258. self._print(qmpcmd)
  259. resp = self.cmd_obj(qmpcmd)
  260. if resp is None:
  261. print('Disconnected')
  262. return False
  263. self._print(resp)
  264. return True
  265. def connect(self, negotiate):
  266. self._greeting = super(QMPShell, self).connect(negotiate)
  267. self.__completer_setup()
  268. def show_banner(self, msg='Welcome to the QMP low-level shell!'):
  269. print(msg)
  270. if not self._greeting:
  271. print('Connected')
  272. return
  273. version = self._greeting['QMP']['version']['qemu']
  274. print('Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']))
  275. def get_prompt(self):
  276. if self._transmode:
  277. return "TRANS> "
  278. return "(QEMU) "
  279. def read_exec_command(self, prompt):
  280. """
  281. Read and execute a command.
  282. @return True if execution was ok, return False if disconnected.
  283. """
  284. try:
  285. cmdline = input(prompt)
  286. except EOFError:
  287. print()
  288. return False
  289. if cmdline == '':
  290. for ev in self.get_events():
  291. print(ev)
  292. self.clear_events()
  293. return True
  294. else:
  295. return self._execute_cmd(cmdline)
  296. def set_verbosity(self, verbose):
  297. self._verbose = verbose
  298. class HMPShell(QMPShell):
  299. def __init__(self, address):
  300. QMPShell.__init__(self, address)
  301. self.__cpu_index = 0
  302. def __cmd_completion(self):
  303. for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
  304. if cmd and cmd[0] != '[' and cmd[0] != '\t':
  305. name = cmd.split()[0] # drop help text
  306. if name == 'info':
  307. continue
  308. if name.find('|') != -1:
  309. # Command in the form 'foobar|f' or 'f|foobar', take the
  310. # full name
  311. opt = name.split('|')
  312. if len(opt[0]) == 1:
  313. name = opt[1]
  314. else:
  315. name = opt[0]
  316. self._completer.append(name)
  317. 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])
  322. def __other_completion(self):
  323. # special cases
  324. self._completer.append('help info')
  325. def _fill_completion(self):
  326. self.__cmd_completion()
  327. self.__info_completion()
  328. self.__other_completion()
  329. def __cmd_passthrough(self, cmdline, cpu_index = 0):
  330. return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
  331. { 'command-line': cmdline,
  332. 'cpu-index': cpu_index } })
  333. def _execute_cmd(self, cmdline):
  334. if cmdline.split()[0] == "cpu":
  335. # trap the cpu command, it requires special setting
  336. try:
  337. idx = int(cmdline.split()[1])
  338. if not 'return' in self.__cmd_passthrough('info version', idx):
  339. print('bad CPU index')
  340. return True
  341. self.__cpu_index = idx
  342. except ValueError:
  343. print('cpu command takes an integer argument')
  344. return True
  345. resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
  346. if resp is None:
  347. print('Disconnected')
  348. return False
  349. assert 'return' in resp or 'error' in resp
  350. if 'return' in resp:
  351. # Success
  352. if len(resp['return']) > 0:
  353. print(resp['return'], end=' ')
  354. else:
  355. # Error
  356. print('%s: %s' % (resp['error']['class'], resp['error']['desc']))
  357. return True
  358. def show_banner(self):
  359. QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
  360. def die(msg):
  361. sys.stderr.write('ERROR: %s\n' % msg)
  362. sys.exit(1)
  363. def fail_cmdline(option=None):
  364. if option:
  365. sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
  366. sys.stderr.write('qmp-shell [ -v ] [ -p ] [ -H ] [ -N ] < UNIX socket path> | < TCP address:port >\n')
  367. sys.stderr.write(' -v Verbose (echo command sent and received)\n')
  368. sys.stderr.write(' -p Pretty-print JSON\n')
  369. sys.stderr.write(' -H Use HMP interface\n')
  370. sys.stderr.write(' -N Skip negotiate (for qemu-ga)\n')
  371. sys.exit(1)
  372. def main():
  373. addr = ''
  374. qemu = None
  375. hmp = False
  376. pretty = False
  377. verbose = False
  378. negotiate = True
  379. try:
  380. for arg in sys.argv[1:]:
  381. if arg == "-H":
  382. if qemu is not None:
  383. fail_cmdline(arg)
  384. hmp = True
  385. elif arg == "-p":
  386. pretty = True
  387. elif arg == "-N":
  388. negotiate = False
  389. elif arg == "-v":
  390. verbose = True
  391. else:
  392. if qemu is not None:
  393. fail_cmdline(arg)
  394. if hmp:
  395. qemu = HMPShell(arg)
  396. else:
  397. qemu = QMPShell(arg, pretty)
  398. addr = arg
  399. if qemu is None:
  400. fail_cmdline()
  401. except QMPShellBadPort:
  402. die('bad port number in command-line')
  403. try:
  404. qemu.connect(negotiate)
  405. except qmp.QMPConnectError:
  406. die('Didn\'t get QMP greeting message')
  407. except qmp.QMPCapabilitiesError:
  408. die('Could not negotiate capabilities')
  409. except qemu.error:
  410. die('Could not connect to %s' % addr)
  411. qemu.show_banner()
  412. qemu.set_verbosity(verbose)
  413. while qemu.read_exec_command(qemu.get_prompt()):
  414. pass
  415. qemu.close()
  416. if __name__ == '__main__':
  417. main()