Statistics
| Branch: | Revision:

root / QMP / qmp-shell @ 1ab516ed

History | View | Annotate | Download (8.2 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
import pprint
37

    
38
class QMPCompleter(list):
39
    def complete(self, text, state):
40
        for cmd in self:
41
            if cmd.startswith(text):
42
                if not state:
43
                    return cmd
44
                else:
45
                    state -= 1
46

    
47
class QMPShellError(Exception):
48
    pass
49

    
50
class QMPShellBadPort(QMPShellError):
51
    pass
52

    
53
# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and
54
#       _execute_cmd()). Let's design a better one.
55
class QMPShell(qmp.QEMUMonitorProtocol):
56
    def __init__(self, address, pp=None):
57
        qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address))
58
        self._greeting = None
59
        self._completer = None
60
        self._pp = pp
61

    
62
    def __get_address(self, arg):
63
        """
64
        Figure out if the argument is in the port:host form, if it's not it's
65
        probably a file path.
66
        """
67
        addr = arg.split(':')
68
        if len(addr) == 2:
69
            try:
70
                port = int(addr[1])
71
            except ValueError:
72
                raise QMPShellBadPort
73
            return ( addr[0], port )
74
        # socket path
75
        return arg
76

    
77
    def _fill_completion(self):
78
        for cmd in self.cmd('query-commands')['return']:
79
            self._completer.append(cmd['name'])
80

    
81
    def __completer_setup(self):
82
        self._completer = QMPCompleter()
83
        self._fill_completion()
84
        readline.set_completer(self._completer.complete)
85
        readline.parse_and_bind("tab: complete")
86
        # XXX: default delimiters conflict with some command names (eg. query-),
87
        # clearing everything as it doesn't seem to matter
88
        readline.set_completer_delims('')
89

    
90
    def __build_cmd(self, cmdline):
91
        """
92
        Build a QMP input object from a user provided command-line in the
93
        following format:
94
    
95
            < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
96
        """
97
        cmdargs = cmdline.split()
98
        qmpcmd = { 'execute': cmdargs[0], 'arguments': {} }
99
        for arg in cmdargs[1:]:
100
            opt = arg.split('=')
101
            try:
102
                value = int(opt[1])
103
            except ValueError:
104
                value = opt[1]
105
            qmpcmd['arguments'][opt[0]] = value
106
        return qmpcmd
107

    
108
    def _execute_cmd(self, cmdline):
109
        try:
110
            qmpcmd = self.__build_cmd(cmdline)
111
        except:
112
            print 'command format: <command-name> ',
113
            print '[arg-name1=arg1] ... [arg-nameN=argN]'
114
            return True
115
        resp = self.cmd_obj(qmpcmd)
116
        if resp is None:
117
            print 'Disconnected'
118
            return False
119

    
120
        if self._pp is not None:
121
            self._pp.pprint(resp)
122
        else:
123
            print resp
124
        return True
125

    
126
    def connect(self):
127
        self._greeting = qmp.QEMUMonitorProtocol.connect(self)
128
        self.__completer_setup()
129

    
130
    def show_banner(self, msg='Welcome to the QMP low-level shell!'):
131
        print msg
132
        version = self._greeting['QMP']['version']['qemu']
133
        print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro'])
134

    
135
    def read_exec_command(self, prompt):
136
        """
137
        Read and execute a command.
138

    
139
        @return True if execution was ok, return False if disconnected.
140
        """
141
        try:
142
            cmdline = raw_input(prompt)
143
        except EOFError:
144
            print
145
            return False
146
        if cmdline == '':
147
            for ev in self.get_events():
148
                print ev
149
            self.clear_events()
150
            return True
151
        else:
152
            return self._execute_cmd(cmdline)
153

    
154
class HMPShell(QMPShell):
155
    def __init__(self, address):
156
        QMPShell.__init__(self, address)
157
        self.__cpu_index = 0
158

    
159
    def __cmd_completion(self):
160
        for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'):
161
            if cmd and cmd[0] != '[' and cmd[0] != '\t':
162
                name = cmd.split()[0] # drop help text
163
                if name == 'info':
164
                    continue
165
                if name.find('|') != -1:
166
                    # Command in the form 'foobar|f' or 'f|foobar', take the
167
                    # full name
168
                    opt = name.split('|')
169
                    if len(opt[0]) == 1:
170
                        name = opt[1]
171
                    else:
172
                        name = opt[0]
173
                self._completer.append(name)
174
                self._completer.append('help ' + name) # help completion
175

    
176
    def __info_completion(self):
177
        for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'):
178
            if cmd:
179
                self._completer.append('info ' + cmd.split()[1])
180

    
181
    def __other_completion(self):
182
        # special cases
183
        self._completer.append('help info')
184

    
185
    def _fill_completion(self):
186
        self.__cmd_completion()
187
        self.__info_completion()
188
        self.__other_completion()
189

    
190
    def __cmd_passthrough(self, cmdline, cpu_index = 0):
191
        return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments':
192
                              { 'command-line': cmdline,
193
                                'cpu-index': cpu_index } })
194

    
195
    def _execute_cmd(self, cmdline):
196
        if cmdline.split()[0] == "cpu":
197
            # trap the cpu command, it requires special setting
198
            try:
199
                idx = int(cmdline.split()[1])
200
                if not 'return' in self.__cmd_passthrough('info version', idx):
201
                    print 'bad CPU index'
202
                    return True
203
                self.__cpu_index = idx
204
            except ValueError:
205
                print 'cpu command takes an integer argument'
206
                return True
207
        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
208
        if resp is None:
209
            print 'Disconnected'
210
            return False
211
        assert 'return' in resp or 'error' in resp
212
        if 'return' in resp:
213
            # Success
214
            if len(resp['return']) > 0:
215
                print resp['return'],
216
        else:
217
            # Error
218
            print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
219
        return True
220

    
221
    def show_banner(self):
222
        QMPShell.show_banner(self, msg='Welcome to the HMP shell!')
223

    
224
def die(msg):
225
    sys.stderr.write('ERROR: %s\n' % msg)
226
    sys.exit(1)
227

    
228
def fail_cmdline(option=None):
229
    if option:
230
        sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option)
231
    sys.stderr.write('qemu-shell [ -p ] [ -H ] < UNIX socket path> | < TCP address:port >\n')
232
    sys.exit(1)
233

    
234
def main():
235
    addr = ''
236
    qemu = None
237
    hmp = False
238
    pp = None
239

    
240
    try:
241
        for arg in sys.argv[1:]:
242
            if arg == "-H":
243
                if qemu is not None:
244
                    fail_cmdline(arg)
245
                hmp = True
246
            elif arg == "-p":
247
                if pp is not None:
248
                    fail_cmdline(arg)
249
                pp = pprint.PrettyPrinter(indent=4)
250
            else:
251
                if qemu is not None:
252
                    fail_cmdline(arg)
253
                if hmp:
254
                    qemu = HMPShell(arg)
255
                else:
256
                    qemu = QMPShell(arg, pp)
257
                addr = arg
258

    
259
        if qemu is None:
260
            fail_cmdline()
261
    except QMPShellBadPort:
262
        die('bad port number in command-line')
263

    
264
    try:
265
        qemu.connect()
266
    except qmp.QMPConnectError:
267
        die('Didn\'t get QMP greeting message')
268
    except qmp.QMPCapabilitiesError:
269
        die('Could not negotiate capabilities')
270
    except qemu.error:
271
        die('Could not connect to %s' % addr)
272

    
273
    qemu.show_banner()
274
    while qemu.read_exec_command('(QEMU) '):
275
        pass
276
    qemu.close()
277

    
278
if __name__ == '__main__':
279
    main()