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