xref: /qemu/scripts/qmp/qemu-ga-client (revision e7b3af81)
1#!/usr/bin/python
2
3# QEMU Guest Agent Client
4#
5# Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
6#
7# This work is licensed under the terms of the GNU GPL, version 2.  See
8# the COPYING file in the top-level directory.
9#
10# Usage:
11#
12# Start QEMU with:
13#
14# # qemu [...] -chardev socket,path=/tmp/qga.sock,server,nowait,id=qga0 \
15#   -device virtio-serial -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
16#
17# Run the script:
18#
19# $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
20#
21# or
22#
23# $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
24# $ qemu-ga-client <command> [args...]
25#
26# For example:
27#
28# $ qemu-ga-client cat /etc/resolv.conf
29# # Generated by NetworkManager
30# nameserver 10.0.2.3
31# $ qemu-ga-client fsfreeze status
32# thawed
33# $ qemu-ga-client fsfreeze freeze
34# 2 filesystems frozen
35#
36# See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
37#
38
39from __future__ import print_function
40import base64
41import random
42
43import qmp
44
45
46class QemuGuestAgent(qmp.QEMUMonitorProtocol):
47    def __getattr__(self, name):
48        def wrapper(**kwds):
49            return self.command('guest-' + name.replace('_', '-'), **kwds)
50        return wrapper
51
52
53class QemuGuestAgentClient:
54    error = QemuGuestAgent.error
55
56    def __init__(self, address):
57        self.qga = QemuGuestAgent(address)
58        self.qga.connect(negotiate=False)
59
60    def sync(self, timeout=3):
61        # Avoid being blocked forever
62        if not self.ping(timeout):
63            raise EnvironmentError('Agent seems not alive')
64        uid = random.randint(0, (1 << 32) - 1)
65        while True:
66            ret = self.qga.sync(id=uid)
67            if isinstance(ret, int) and int(ret) == uid:
68                break
69
70    def __file_read_all(self, handle):
71        eof = False
72        data = ''
73        while not eof:
74            ret = self.qga.file_read(handle=handle, count=1024)
75            _data = base64.b64decode(ret['buf-b64'])
76            data += _data
77            eof = ret['eof']
78        return data
79
80    def read(self, path):
81        handle = self.qga.file_open(path=path)
82        try:
83            data = self.__file_read_all(handle)
84        finally:
85            self.qga.file_close(handle=handle)
86        return data
87
88    def info(self):
89        info = self.qga.info()
90
91        msgs = []
92        msgs.append('version: ' + info['version'])
93        msgs.append('supported_commands:')
94        enabled = [c['name'] for c in info['supported_commands'] if c['enabled']]
95        msgs.append('\tenabled: ' + ', '.join(enabled))
96        disabled = [c['name'] for c in info['supported_commands'] if not c['enabled']]
97        msgs.append('\tdisabled: ' + ', '.join(disabled))
98
99        return '\n'.join(msgs)
100
101    def __gen_ipv4_netmask(self, prefixlen):
102        mask = int('1' * prefixlen + '0' * (32 - prefixlen), 2)
103        return '.'.join([str(mask >> 24),
104                         str((mask >> 16) & 0xff),
105                         str((mask >> 8) & 0xff),
106                         str(mask & 0xff)])
107
108    def ifconfig(self):
109        nifs = self.qga.network_get_interfaces()
110
111        msgs = []
112        for nif in nifs:
113            msgs.append(nif['name'] + ':')
114            if 'ip-addresses' in nif:
115                for ipaddr in nif['ip-addresses']:
116                    if ipaddr['ip-address-type'] == 'ipv4':
117                        addr = ipaddr['ip-address']
118                        mask = self.__gen_ipv4_netmask(int(ipaddr['prefix']))
119                        msgs.append("\tinet %s  netmask %s" % (addr, mask))
120                    elif ipaddr['ip-address-type'] == 'ipv6':
121                        addr = ipaddr['ip-address']
122                        prefix = ipaddr['prefix']
123                        msgs.append("\tinet6 %s  prefixlen %s" % (addr, prefix))
124            if nif['hardware-address'] != '00:00:00:00:00:00':
125                msgs.append("\tether " + nif['hardware-address'])
126
127        return '\n'.join(msgs)
128
129    def ping(self, timeout):
130        self.qga.settimeout(timeout)
131        try:
132            self.qga.ping()
133        except self.qga.timeout:
134            return False
135        return True
136
137    def fsfreeze(self, cmd):
138        if cmd not in ['status', 'freeze', 'thaw']:
139            raise Exception('Invalid command: ' + cmd)
140
141        return getattr(self.qga, 'fsfreeze' + '_' + cmd)()
142
143    def fstrim(self, minimum=0):
144        return getattr(self.qga, 'fstrim')(minimum=minimum)
145
146    def suspend(self, mode):
147        if mode not in ['disk', 'ram', 'hybrid']:
148            raise Exception('Invalid mode: ' + mode)
149
150        try:
151            getattr(self.qga, 'suspend' + '_' + mode)()
152            # On error exception will raise
153        except self.qga.timeout:
154            # On success command will timed out
155            return
156
157    def shutdown(self, mode='powerdown'):
158        if mode not in ['powerdown', 'halt', 'reboot']:
159            raise Exception('Invalid mode: ' + mode)
160
161        try:
162            self.qga.shutdown(mode=mode)
163        except self.qga.timeout:
164            return
165
166
167def _cmd_cat(client, args):
168    if len(args) != 1:
169        print('Invalid argument')
170        print('Usage: cat <file>')
171        sys.exit(1)
172    print(client.read(args[0]))
173
174
175def _cmd_fsfreeze(client, args):
176    usage = 'Usage: fsfreeze status|freeze|thaw'
177    if len(args) != 1:
178        print('Invalid argument')
179        print(usage)
180        sys.exit(1)
181    if args[0] not in ['status', 'freeze', 'thaw']:
182        print('Invalid command: ' + args[0])
183        print(usage)
184        sys.exit(1)
185    cmd = args[0]
186    ret = client.fsfreeze(cmd)
187    if cmd == 'status':
188        print(ret)
189    elif cmd == 'freeze':
190        print("%d filesystems frozen" % ret)
191    else:
192        print("%d filesystems thawed" % ret)
193
194
195def _cmd_fstrim(client, args):
196    if len(args) == 0:
197        minimum = 0
198    else:
199        minimum = int(args[0])
200    print(client.fstrim(minimum))
201
202
203def _cmd_ifconfig(client, args):
204    print(client.ifconfig())
205
206
207def _cmd_info(client, args):
208    print(client.info())
209
210
211def _cmd_ping(client, args):
212    if len(args) == 0:
213        timeout = 3
214    else:
215        timeout = float(args[0])
216    alive = client.ping(timeout)
217    if not alive:
218        print("Not responded in %s sec" % args[0])
219        sys.exit(1)
220
221
222def _cmd_suspend(client, args):
223    usage = 'Usage: suspend disk|ram|hybrid'
224    if len(args) != 1:
225        print('Less argument')
226        print(usage)
227        sys.exit(1)
228    if args[0] not in ['disk', 'ram', 'hybrid']:
229        print('Invalid command: ' + args[0])
230        print(usage)
231        sys.exit(1)
232    client.suspend(args[0])
233
234
235def _cmd_shutdown(client, args):
236    client.shutdown()
237_cmd_powerdown = _cmd_shutdown
238
239
240def _cmd_halt(client, args):
241    client.shutdown('halt')
242
243
244def _cmd_reboot(client, args):
245    client.shutdown('reboot')
246
247
248commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
249
250
251def main(address, cmd, args):
252    if not os.path.exists(address):
253        print('%s not found' % address)
254        sys.exit(1)
255
256    if cmd not in commands:
257        print('Invalid command: ' + cmd)
258        print('Available commands: ' + ', '.join(commands))
259        sys.exit(1)
260
261    try:
262        client = QemuGuestAgentClient(address)
263    except QemuGuestAgent.error as e:
264        import errno
265
266        print(e)
267        if e.errno == errno.ECONNREFUSED:
268            print('Hint: qemu is not running?')
269        sys.exit(1)
270
271    if cmd == 'fsfreeze' and args[0] == 'freeze':
272        client.sync(60)
273    elif cmd != 'ping':
274        client.sync()
275
276    globals()['_cmd_' + cmd](client, args)
277
278
279if __name__ == '__main__':
280    import sys
281    import os
282    import optparse
283
284    address = os.environ['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os.environ else None
285
286    usage = "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
287    usage += '<command>: ' + ', '.join(commands)
288    parser = optparse.OptionParser(usage=usage)
289    parser.add_option('--address', action='store', type='string',
290                      default=address, help='Specify a ip:port pair or a unix socket path')
291    options, args = parser.parse_args()
292
293    address = options.address
294    if address is None:
295        parser.error('address is not specified')
296        sys.exit(1)
297
298    if len(args) == 0:
299        parser.error('Less argument')
300        sys.exit(1)
301
302    main(address, args[0], args[1:])
303