Statistics
| Branch: | Revision:

root / QMP / qmp-shell @ 11217a75

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
            print
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()