]>
Commit | Line | Data |
---|---|---|
cedebdac LC |
1 | #!/usr/bin/python |
2 | # | |
9bed0d0d | 3 | # Low-level QEMU shell on top of QMP. |
cedebdac | 4 | # |
9bed0d0d | 5 | # Copyright (C) 2009, 2010 Red Hat Inc. |
cedebdac LC |
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 | # | |
9bed0d0d | 17 | # # qemu [...] -qmp unix:./qmp-sock,server |
cedebdac LC |
18 | # |
19 | # Run the shell: | |
20 | # | |
9bed0d0d | 21 | # $ qmp-shell ./qmp-sock |
cedebdac LC |
22 | # |
23 | # Commands have the following format: | |
24 | # | |
9bed0d0d | 25 | # < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] |
cedebdac LC |
26 | # |
27 | # For example: | |
28 | # | |
9bed0d0d LC |
29 | # (QEMU) device_add driver=e1000 id=net1 |
30 | # {u'return': {}} | |
31 | # (QEMU) | |
cedebdac LC |
32 | |
33 | import qmp | |
34 | import readline | |
9bed0d0d | 35 | import sys |
cedebdac | 36 | |
9bed0d0d LC |
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 | |
cedebdac | 45 | |
9bed0d0d LC |
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() | |
cedebdac | 123 | |
9bed0d0d LC |
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']) | |
cedebdac | 128 | |
9bed0d0d LC |
129 | def read_exec_command(self, prompt): |
130 | """ | |
131 | Read and execute a command. | |
cedebdac | 132 | |
9bed0d0d LC |
133 | @return True if execution was ok, return False if disconnected. |
134 | """ | |
cedebdac | 135 | try: |
9bed0d0d | 136 | cmdline = raw_input(prompt) |
cedebdac LC |
137 | except EOFError: |
138 | ||
9bed0d0d LC |
139 | return False |
140 | if cmdline == '': | |
141 | for ev in self.get_events(): | |
142 | print ev | |
143 | self.clear_events() | |
144 | return True | |
cedebdac | 145 | else: |
9bed0d0d LC |
146 | return self._execute_cmd(cmdline) |
147 | ||
148 | def die(msg): | |
149 | sys.stderr.write('ERROR: %s\n' % msg) | |
150 | sys.exit(1) | |
151 | ||
152 | def fail_cmdline(option=None): | |
153 | if option: | |
154 | sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) | |
155 | sys.stderr.write('qemu-shell [ -H ] < UNIX socket path> | < TCP address:port >\n') | |
156 | sys.exit(1) | |
157 | ||
158 | def main(): | |
159 | try: | |
160 | if len(sys.argv) == 2: | |
161 | qemu = QMPShell(sys.argv[1]) | |
162 | else: | |
163 | fail_cmdline() | |
164 | except QMPShellBadPort: | |
165 | die('bad port number in command-line') | |
166 | ||
167 | try: | |
168 | qemu.connect() | |
169 | except qmp.QMPConnectError: | |
170 | die('Didn\'t get QMP greeting message') | |
171 | except qmp.QMPCapabilitiesError: | |
172 | die('Could not negotiate capabilities') | |
173 | except qemu.error: | |
174 | die('Could not connect to %s' % sys.argv[1]) | |
175 | ||
176 | qemu.show_banner() | |
177 | while qemu.read_exec_command('(QEMU) '): | |
178 | pass | |
179 | qemu.close() | |
cedebdac LC |
180 | |
181 | if __name__ == '__main__': | |
182 | main() |