root / QMP / qmp-shell @ d2d979c6
History | View | Annotate | Download (7.7 kB)
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 |
import qmp |
34 |
import readline |
35 |
import sys |
36 |
|
37 |
class QMPCompleter(list): |
38 |
def complete(self, text, state): |
39 |
for cmd in self: |
40 |
if cmd.startswith(text): |
41 |
if not state: |
42 |
return cmd |
43 |
else: |
44 |
state -= 1 |
45 |
|
46 |
class QMPShellError(Exception): |
47 |
pass |
48 |
|
49 |
class QMPShellBadPort(QMPShellError): |
50 |
pass |
51 |
|
52 |
# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and |
53 |
# _execute_cmd()). Let's design a better one. |
54 |
class QMPShell(qmp.QEMUMonitorProtocol): |
55 |
def __init__(self, address): |
56 |
qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) |
57 |
self._greeting = None |
58 |
self._completer = None |
59 |
|
60 |
def __get_address(self, arg): |
61 |
""" |
62 |
Figure out if the argument is in the port:host form, if it's not it's |
63 |
probably a file path. |
64 |
""" |
65 |
addr = arg.split(':') |
66 |
if len(addr) == 2: |
67 |
try: |
68 |
port = int(addr[1]) |
69 |
except ValueError: |
70 |
raise QMPShellBadPort |
71 |
return ( addr[0], port ) |
72 |
# socket path |
73 |
return arg |
74 |
|
75 |
def _fill_completion(self): |
76 |
for cmd in self.cmd('query-commands')['return']: |
77 |
self._completer.append(cmd['name']) |
78 |
|
79 |
def __completer_setup(self): |
80 |
self._completer = QMPCompleter() |
81 |
self._fill_completion() |
82 |
readline.set_completer(self._completer.complete) |
83 |
readline.parse_and_bind("tab: complete") |
84 |
# XXX: default delimiters conflict with some command names (eg. query-), |
85 |
# clearing everything as it doesn't seem to matter |
86 |
readline.set_completer_delims('') |
87 |
|
88 |
def __build_cmd(self, cmdline): |
89 |
""" |
90 |
Build a QMP input object from a user provided command-line in the |
91 |
following format: |
92 |
|
93 |
< command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
94 |
""" |
95 |
cmdargs = cmdline.split() |
96 |
qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } |
97 |
for arg in cmdargs[1:]: |
98 |
opt = arg.split('=') |
99 |
try: |
100 |
value = int(opt[1]) |
101 |
except ValueError: |
102 |
value = opt[1] |
103 |
qmpcmd['arguments'][opt[0]] = value |
104 |
return qmpcmd |
105 |
|
106 |
def _execute_cmd(self, cmdline): |
107 |
try: |
108 |
qmpcmd = self.__build_cmd(cmdline) |
109 |
except: |
110 |
print 'command format: <command-name> ', |
111 |
print '[arg-name1=arg1] ... [arg-nameN=argN]' |
112 |
return True |
113 |
resp = self.cmd_obj(qmpcmd) |
114 |
if resp is None: |
115 |
print 'Disconnected' |
116 |
return False |
117 |
print resp |
118 |
return True |
119 |
|
120 |
def connect(self): |
121 |
self._greeting = qmp.QEMUMonitorProtocol.connect(self) |
122 |
self.__completer_setup() |
123 |
|
124 |
def show_banner(self, msg='Welcome to the QMP low-level shell!'): |
125 |
print msg |
126 |
version = self._greeting['QMP']['version']['qemu'] |
127 |
print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) |
128 |
|
129 |
def read_exec_command(self, prompt): |
130 |
""" |
131 |
Read and execute a command. |
132 |
|
133 |
@return True if execution was ok, return False if disconnected. |
134 |
""" |
135 |
try: |
136 |
cmdline = raw_input(prompt) |
137 |
except EOFError: |
138 |
|
139 |
return False |
140 |
if cmdline == '': |
141 |
for ev in self.get_events(): |
142 |
print ev |
143 |
self.clear_events() |
144 |
return True |
145 |
else: |
146 |
return self._execute_cmd(cmdline) |
147 |
|
148 |
class HMPShell(QMPShell): |
149 |
def __init__(self, address): |
150 |
QMPShell.__init__(self, address) |
151 |
self.__cpu_index = 0 |
152 |
|
153 |
def __cmd_completion(self): |
154 |
for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): |
155 |
if cmd and cmd[0] != '[' and cmd[0] != '\t': |
156 |
name = cmd.split()[0] # drop help text |
157 |
if name == 'info': |
158 |
continue |
159 |
if name.find('|') != -1: |
160 |
# Command in the form 'foobar|f' or 'f|foobar', take the |
161 |
# full name |
162 |
opt = name.split('|') |
163 |
if len(opt[0]) == 1: |
164 |
name = opt[1] |
165 |
else: |
166 |
name = opt[0] |
167 |
self._completer.append(name) |
168 |
self._completer.append('help ' + name) # help completion |
169 |
|
170 |
def __info_completion(self): |
171 |
for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): |
172 |
if cmd: |
173 |
self._completer.append('info ' + cmd.split()[1]) |
174 |
|
175 |
def __other_completion(self): |
176 |
# special cases |
177 |
self._completer.append('help info') |
178 |
|
179 |
def _fill_completion(self): |
180 |
self.__cmd_completion() |
181 |
self.__info_completion() |
182 |
self.__other_completion() |
183 |
|
184 |
def __cmd_passthrough(self, cmdline, cpu_index = 0): |
185 |
return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': |
186 |
{ 'command-line': cmdline, |
187 |
'cpu-index': cpu_index } }) |
188 |
|
189 |
def _execute_cmd(self, cmdline): |
190 |
if cmdline.split()[0] == "cpu": |
191 |
# trap the cpu command, it requires special setting |
192 |
try: |
193 |
idx = int(cmdline.split()[1]) |
194 |
if not 'return' in self.__cmd_passthrough('info version', idx): |
195 |
print 'bad CPU index' |
196 |
return True |
197 |
self.__cpu_index = idx |
198 |
except ValueError: |
199 |
print 'cpu command takes an integer argument' |
200 |
return True |
201 |
resp = self.__cmd_passthrough(cmdline, self.__cpu_index) |
202 |
if resp is None: |
203 |
print 'Disconnected' |
204 |
return False |
205 |
assert 'return' in resp or 'error' in resp |
206 |
if 'return' in resp: |
207 |
# Success |
208 |
if len(resp['return']) > 0: |
209 |
print resp['return'], |
210 |
else: |
211 |
# Error |
212 |
print '%s: %s' % (resp['error']['class'], resp['error']['desc']) |
213 |
return True |
214 |
|
215 |
def show_banner(self): |
216 |
QMPShell.show_banner(self, msg='Welcome to the HMP shell!') |
217 |
|
218 |
def die(msg): |
219 |
sys.stderr.write('ERROR: %s\n' % msg) |
220 |
sys.exit(1) |
221 |
|
222 |
def fail_cmdline(option=None): |
223 |
if option: |
224 |
sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) |
225 |
sys.stderr.write('qemu-shell [ -H ] < UNIX socket path> | < TCP address:port >\n') |
226 |
sys.exit(1) |
227 |
|
228 |
def main(): |
229 |
addr = '' |
230 |
try: |
231 |
if len(sys.argv) == 2: |
232 |
qemu = QMPShell(sys.argv[1]) |
233 |
addr = sys.argv[1] |
234 |
elif len(sys.argv) == 3: |
235 |
if sys.argv[1] != '-H': |
236 |
fail_cmdline(sys.argv[1]) |
237 |
qemu = HMPShell(sys.argv[2]) |
238 |
addr = sys.argv[2] |
239 |
else: |
240 |
fail_cmdline() |
241 |
except QMPShellBadPort: |
242 |
die('bad port number in command-line') |
243 |
|
244 |
try: |
245 |
qemu.connect() |
246 |
except qmp.QMPConnectError: |
247 |
die('Didn\'t get QMP greeting message') |
248 |
except qmp.QMPCapabilitiesError: |
249 |
die('Could not negotiate capabilities') |
250 |
except qemu.error: |
251 |
die('Could not connect to %s' % addr) |
252 |
|
253 |
qemu.show_banner() |
254 |
while qemu.read_exec_command('(QEMU) '): |
255 |
pass |
256 |
qemu.close() |
257 |
|
258 |
if __name__ == '__main__': |
259 |
main() |