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