qemurunner.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. #
  2. # Copyright (C) 2013 Intel Corporation
  3. #
  4. # SPDX-License-Identifier: MIT
  5. #
  6. # This module provides a class for starting qemu images using runqemu.
  7. # It's used by testimage.bbclass.
  8. import subprocess
  9. import os
  10. import sys
  11. import time
  12. import signal
  13. import re
  14. import socket
  15. import select
  16. import errno
  17. import string
  18. import threading
  19. import codecs
  20. import logging
  21. from oeqa.utils.dump import HostDumper
  22. from collections import defaultdict
  23. # Get Unicode non printable control chars
  24. control_range = list(range(0,32))+list(range(127,160))
  25. control_chars = [chr(x) for x in control_range
  26. if chr(x) not in string.printable]
  27. re_control_char = re.compile('[%s]' % re.escape("".join(control_chars)))
  28. class QemuRunner:
  29. def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
  30. use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False):
  31. # Popen object for runqemu
  32. self.runqemu = None
  33. # pid of the qemu process that runqemu will start
  34. self.qemupid = None
  35. # target ip - from the command line or runqemu output
  36. self.ip = None
  37. # host ip - where qemu is running
  38. self.server_ip = None
  39. # target ip netmask
  40. self.netmask = None
  41. self.machine = machine
  42. self.rootfs = rootfs
  43. self.display = display
  44. self.tmpdir = tmpdir
  45. self.deploy_dir_image = deploy_dir_image
  46. self.logfile = logfile
  47. self.boottime = boottime
  48. self.logged = False
  49. self.thread = None
  50. self.use_kvm = use_kvm
  51. self.use_ovmf = use_ovmf
  52. self.use_slirp = use_slirp
  53. self.serial_ports = serial_ports
  54. self.msg = ''
  55. self.boot_patterns = boot_patterns
  56. self.runqemutime = 120
  57. self.qemu_pidfile = 'pidfile_'+str(os.getpid())
  58. self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
  59. self.monitorpipe = None
  60. self.logger = logger
  61. # Enable testing other OS's
  62. # Set commands for target communication, and default to Linux ALWAYS
  63. # Other OS's or baremetal applications need to provide their
  64. # own implementation passing it through QemuRunner's constructor
  65. # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag]
  66. # provided variables, where <flag> is one of the mentioned below.
  67. accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished']
  68. default_boot_patterns = defaultdict(str)
  69. # Default to the usual paterns used to communicate with the target
  70. default_boot_patterns['search_reached_prompt'] = b' login:'
  71. default_boot_patterns['send_login_user'] = 'root\n'
  72. default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
  73. default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"
  74. # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
  75. for pattern in accepted_patterns:
  76. if not self.boot_patterns[pattern]:
  77. self.boot_patterns[pattern] = default_boot_patterns[pattern]
  78. def create_socket(self):
  79. try:
  80. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  81. sock.setblocking(0)
  82. sock.bind(("127.0.0.1",0))
  83. sock.listen(2)
  84. port = sock.getsockname()[1]
  85. self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
  86. return (sock, port)
  87. except socket.error:
  88. sock.close()
  89. raise
  90. def log(self, msg):
  91. if self.logfile:
  92. # It is needed to sanitize the data received from qemu
  93. # because is possible to have control characters
  94. msg = msg.decode("utf-8", errors='ignore')
  95. msg = re_control_char.sub('', msg)
  96. self.msg += msg
  97. with codecs.open(self.logfile, "a", encoding="utf-8") as f:
  98. f.write("%s" % msg)
  99. def getOutput(self, o):
  100. import fcntl
  101. fl = fcntl.fcntl(o, fcntl.F_GETFL)
  102. fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  103. return os.read(o.fileno(), 1000000).decode("utf-8")
  104. def handleSIGCHLD(self, signum, frame):
  105. if self.runqemu and self.runqemu.poll():
  106. if self.runqemu.returncode:
  107. self.logger.error('runqemu exited with code %d' % self.runqemu.returncode)
  108. self.logger.error('Output from runqemu:\n%s' % self.getOutput(self.runqemu.stdout))
  109. self.stop()
  110. self._dump_host()
  111. raise SystemExit
  112. def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True):
  113. env = os.environ.copy()
  114. if self.display:
  115. env["DISPLAY"] = self.display
  116. # Set this flag so that Qemu doesn't do any grabs as SDL grabs
  117. # interact badly with screensavers.
  118. env["QEMU_DONT_GRAB"] = "1"
  119. if not os.path.exists(self.rootfs):
  120. self.logger.error("Invalid rootfs %s" % self.rootfs)
  121. return False
  122. if not os.path.exists(self.tmpdir):
  123. self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
  124. return False
  125. else:
  126. env["OE_TMPDIR"] = self.tmpdir
  127. if not os.path.exists(self.deploy_dir_image):
  128. self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
  129. return False
  130. else:
  131. env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image
  132. if not launch_cmd:
  133. launch_cmd = 'runqemu %s' % ('snapshot' if discard_writes else '')
  134. if self.use_kvm:
  135. self.logger.debug('Using kvm for runqemu')
  136. launch_cmd += ' kvm'
  137. else:
  138. self.logger.debug('Not using kvm for runqemu')
  139. if not self.display:
  140. launch_cmd += ' nographic'
  141. if self.use_slirp:
  142. launch_cmd += ' slirp'
  143. if self.use_ovmf:
  144. launch_cmd += ' ovmf'
  145. launch_cmd += ' %s %s %s' % (runqemuparams, self.machine, self.rootfs)
  146. return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)
  147. def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
  148. try:
  149. if self.serial_ports >= 2:
  150. self.threadsock, threadport = self.create_socket()
  151. self.server_socket, self.serverport = self.create_socket()
  152. except socket.error as msg:
  153. self.logger.error("Failed to create listening socket: %s" % msg[1])
  154. return False
  155. bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1'
  156. if extra_bootparams:
  157. bootparams = bootparams + ' ' + extra_bootparams
  158. # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
  159. # and analyze descendents in order to determine it.
  160. if os.path.exists(self.qemu_pidfile):
  161. os.remove(self.qemu_pidfile)
  162. self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1}"'.format(bootparams, self.qemu_pidfile)
  163. if qemuparams:
  164. self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'
  165. if self.serial_ports >= 2:
  166. launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams)
  167. else:
  168. launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams)
  169. self.origchldhandler = signal.getsignal(signal.SIGCHLD)
  170. signal.signal(signal.SIGCHLD, self.handleSIGCHLD)
  171. self.logger.debug('launchcmd=%s'%(launch_cmd))
  172. # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
  173. # blocking at the end of the runqemu script when using this within
  174. # oe-selftest (this makes stty error out immediately). There ought
  175. # to be a proper fix but this will suffice for now.
  176. self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env)
  177. output = self.runqemu.stdout
  178. #
  179. # We need the preexec_fn above so that all runqemu processes can easily be killed
  180. # (by killing their process group). This presents a problem if this controlling
  181. # process itself is killed however since those processes don't notice the death
  182. # of the parent and merrily continue on.
  183. #
  184. # Rather than hack runqemu to deal with this, we add something here instead.
  185. # Basically we fork off another process which holds an open pipe to the parent
  186. # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
  187. # the process group. This is like pctrl's PDEATHSIG but for a process group
  188. # rather than a single process.
  189. #
  190. r, w = os.pipe()
  191. self.monitorpid = os.fork()
  192. if self.monitorpid:
  193. os.close(r)
  194. self.monitorpipe = os.fdopen(w, "w")
  195. else:
  196. # child process
  197. os.setpgrp()
  198. os.close(w)
  199. r = os.fdopen(r)
  200. x = r.read()
  201. os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
  202. sys.exit(0)
  203. self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
  204. self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
  205. (self.runqemutime, time.strftime("%D %H:%M:%S")))
  206. endtime = time.time() + self.runqemutime
  207. while not self.is_alive() and time.time() < endtime:
  208. if self.runqemu.poll():
  209. if self.runqemu.returncode:
  210. # No point waiting any longer
  211. self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode)
  212. self._dump_host()
  213. self.logger.warning("Output from runqemu:\n%s" % self.getOutput(output))
  214. self.stop()
  215. return False
  216. time.sleep(0.5)
  217. if not self.is_alive():
  218. self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
  219. (self.runqemutime, time.strftime("%D %H:%M:%S")))
  220. # Dump all processes to help us to figure out what is going on...
  221. ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0]
  222. processes = ps.decode("utf-8")
  223. self.logger.debug("Running processes:\n%s" % processes)
  224. self._dump_host()
  225. op = self.getOutput(output)
  226. self.stop()
  227. if op:
  228. self.logger.error("Output from runqemu:\n%s" % op)
  229. else:
  230. self.logger.error("No output from runqemu.\n")
  231. return False
  232. # We are alive: qemu is running
  233. out = self.getOutput(output)
  234. netconf = False # network configuration is not required by default
  235. self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" %
  236. (time.time() - (endtime - self.runqemutime),
  237. self.qemupid, time.strftime("%D %H:%M:%S")))
  238. cmdline = ''
  239. if get_ip:
  240. with open('/proc/%s/cmdline' % self.qemupid) as p:
  241. cmdline = p.read()
  242. # It is needed to sanitize the data received
  243. # because is possible to have control characters
  244. cmdline = re_control_char.sub(' ', cmdline)
  245. try:
  246. if self.use_slirp:
  247. tcp_ports = cmdline.split("hostfwd=tcp::")[1]
  248. host_port = tcp_ports[:tcp_ports.find('-')]
  249. self.ip = "localhost:%s" % host_port
  250. else:
  251. ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
  252. self.ip = ips[0]
  253. self.server_ip = ips[1]
  254. self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
  255. except (IndexError, ValueError):
  256. # Try to get network configuration from runqemu output
  257. match = re.match(r'.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*',
  258. out, re.MULTILINE|re.DOTALL)
  259. if match:
  260. self.ip, self.server_ip, self.netmask = match.groups()
  261. # network configuration is required as we couldn't get it
  262. # from the runqemu command line, so qemu doesn't run kernel
  263. # and guest networking is not configured
  264. netconf = True
  265. else:
  266. self.logger.error("Couldn't get ip from qemu command line and runqemu output! "
  267. "Here is the qemu command line used:\n%s\n"
  268. "and output from runqemu:\n%s" % (cmdline, out))
  269. self._dump_host()
  270. self.stop()
  271. return False
  272. self.logger.debug("Target IP: %s" % self.ip)
  273. self.logger.debug("Server IP: %s" % self.server_ip)
  274. if self.serial_ports >= 2:
  275. self.thread = LoggingThread(self.log, self.threadsock, self.logger)
  276. self.thread.start()
  277. if not self.thread.connection_established.wait(self.boottime):
  278. self.logger.error("Didn't receive a console connection from qemu. "
  279. "Here is the qemu command line used:\n%s\nand "
  280. "output from runqemu:\n%s" % (cmdline, out))
  281. self.stop_thread()
  282. return False
  283. self.logger.debug("Output from runqemu:\n%s", out)
  284. self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
  285. (self.boottime, time.strftime("%D %H:%M:%S")))
  286. endtime = time.time() + self.boottime
  287. socklist = [self.server_socket]
  288. reachedlogin = False
  289. stopread = False
  290. qemusock = None
  291. bootlog = b''
  292. data = b''
  293. while time.time() < endtime and not stopread:
  294. try:
  295. sread, swrite, serror = select.select(socklist, [], [], 5)
  296. except InterruptedError:
  297. continue
  298. for sock in sread:
  299. if sock is self.server_socket:
  300. qemusock, addr = self.server_socket.accept()
  301. qemusock.setblocking(0)
  302. socklist.append(qemusock)
  303. socklist.remove(self.server_socket)
  304. self.logger.debug("Connection from %s:%s" % addr)
  305. else:
  306. data = data + sock.recv(1024)
  307. if data:
  308. bootlog += data
  309. if self.serial_ports < 2:
  310. # this socket has mixed console/kernel data, log it to logfile
  311. self.log(data)
  312. data = b''
  313. if self.boot_patterns['search_reached_prompt'] in bootlog:
  314. self.server_socket = qemusock
  315. stopread = True
  316. reachedlogin = True
  317. self.logger.debug("Reached login banner in %s seconds (%s)" %
  318. (time.time() - (endtime - self.boottime),
  319. time.strftime("%D %H:%M:%S")))
  320. else:
  321. # no need to check if reachedlogin unless we support multiple connections
  322. self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" %
  323. time.strftime("%D %H:%M:%S"))
  324. socklist.remove(sock)
  325. sock.close()
  326. stopread = True
  327. if not reachedlogin:
  328. if time.time() >= endtime:
  329. self.logger.warning("Target didn't reach login banner in %d seconds (%s)" %
  330. (self.boottime, time.strftime("%D %H:%M:%S")))
  331. tail = lambda l: "\n".join(l.splitlines()[-25:])
  332. bootlog = bootlog.decode("utf-8")
  333. # in case bootlog is empty, use tail qemu log store at self.msg
  334. lines = tail(bootlog if bootlog else self.msg)
  335. self.logger.warning("Last 25 lines of text:\n%s" % lines)
  336. self.logger.warning("Check full boot log: %s" % self.logfile)
  337. self._dump_host()
  338. self.stop()
  339. return False
  340. # If we are not able to login the tests can continue
  341. try:
  342. (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True)
  343. if re.search(self.boot_patterns['search_login_succeeded'], output):
  344. self.logged = True
  345. self.logger.debug("Logged as root in serial console")
  346. if netconf:
  347. # configure guest networking
  348. cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask)
  349. output = self.run_serial(cmd, raw=True)[1]
  350. if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
  351. self.logger.debug("configured ip address %s", self.ip)
  352. else:
  353. self.logger.debug("Couldn't configure guest networking")
  354. else:
  355. self.logger.warning("Couldn't login into serial console"
  356. " as root using blank password")
  357. self.logger.warning("The output:\n%s" % output)
  358. except:
  359. self.logger.warning("Serial console failed while trying to login")
  360. return True
  361. def stop(self):
  362. if hasattr(self, "origchldhandler"):
  363. signal.signal(signal.SIGCHLD, self.origchldhandler)
  364. self.stop_thread()
  365. self.stop_qemu_system()
  366. if self.runqemu:
  367. if hasattr(self, "monitorpid"):
  368. os.kill(self.monitorpid, signal.SIGKILL)
  369. self.logger.debug("Sending SIGTERM to runqemu")
  370. try:
  371. os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
  372. except OSError as e:
  373. if e.errno != errno.ESRCH:
  374. raise
  375. endtime = time.time() + self.runqemutime
  376. while self.runqemu.poll() is None and time.time() < endtime:
  377. time.sleep(1)
  378. if self.runqemu.poll() is None:
  379. self.logger.debug("Sending SIGKILL to runqemu")
  380. os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
  381. self.runqemu.stdin.close()
  382. self.runqemu.stdout.close()
  383. self.runqemu = None
  384. if hasattr(self, 'server_socket') and self.server_socket:
  385. self.server_socket.close()
  386. self.server_socket = None
  387. if hasattr(self, 'threadsock') and self.threadsock:
  388. self.threadsock.close()
  389. self.threadsock = None
  390. self.qemupid = None
  391. self.ip = None
  392. if os.path.exists(self.qemu_pidfile):
  393. try:
  394. os.remove(self.qemu_pidfile)
  395. except FileNotFoundError as e:
  396. # We raced, ignore
  397. pass
  398. if self.monitorpipe:
  399. self.monitorpipe.close()
  400. def stop_qemu_system(self):
  401. if self.qemupid:
  402. try:
  403. # qemu-system behaves well and a SIGTERM is enough
  404. os.kill(self.qemupid, signal.SIGTERM)
  405. except ProcessLookupError as e:
  406. self.logger.warning('qemu-system ended unexpectedly')
  407. def stop_thread(self):
  408. if self.thread and self.thread.is_alive():
  409. self.thread.stop()
  410. self.thread.join()
  411. def restart(self, qemuparams = None):
  412. self.logger.warning("Restarting qemu process")
  413. if self.runqemu.poll() is None:
  414. self.stop()
  415. if self.start(qemuparams):
  416. return True
  417. return False
  418. def is_alive(self):
  419. if not self.runqemu or self.runqemu.poll() is not None:
  420. return False
  421. if os.path.isfile(self.qemu_pidfile):
  422. # when handling pidfile, qemu creates the file, stat it, lock it and then write to it
  423. # so it's possible that the file has been created but the content is empty
  424. pidfile_timeout = time.time() + 3
  425. while time.time() < pidfile_timeout:
  426. with open(self.qemu_pidfile, 'r') as f:
  427. qemu_pid = f.read().strip()
  428. # file created but not yet written contents
  429. if not qemu_pid:
  430. time.sleep(0.5)
  431. continue
  432. else:
  433. if os.path.exists("/proc/" + qemu_pid):
  434. self.qemupid = int(qemu_pid)
  435. return True
  436. return False
  437. def run_serial(self, command, raw=False, timeout=60):
  438. # We assume target system have echo to get command status
  439. if not raw:
  440. command = "%s; echo $?\n" % command
  441. data = ''
  442. status = 0
  443. self.server_socket.sendall(command.encode('utf-8'))
  444. start = time.time()
  445. end = start + timeout
  446. while True:
  447. now = time.time()
  448. if now >= end:
  449. data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
  450. break
  451. try:
  452. sread, _, _ = select.select([self.server_socket],[],[], end - now)
  453. except InterruptedError:
  454. continue
  455. if sread:
  456. answer = self.server_socket.recv(1024)
  457. if answer:
  458. data += answer.decode('utf-8')
  459. # Search the prompt to stop
  460. if re.search(self.boot_patterns['search_cmd_finished'], data):
  461. break
  462. else:
  463. raise Exception("No data on serial console socket")
  464. if data:
  465. if raw:
  466. status = 1
  467. else:
  468. # Remove first line (command line) and last line (prompt)
  469. data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
  470. index = data.rfind('\r\n')
  471. if index == -1:
  472. status_cmd = data
  473. data = ""
  474. else:
  475. status_cmd = data[index+2:]
  476. data = data[:index]
  477. if (status_cmd == "0"):
  478. status = 1
  479. return (status, str(data))
  480. def _dump_host(self):
  481. self.host_dumper.create_dir("qemu")
  482. self.logger.warning("Qemu ended unexpectedly, dump data from host"
  483. " is in %s" % self.host_dumper.dump_dir)
  484. self.host_dumper.dump_host()
  485. # This class is for reading data from a socket and passing it to logfunc
  486. # to be processed. It's completely event driven and has a straightforward
  487. # event loop. The mechanism for stopping the thread is a simple pipe which
  488. # will wake up the poll and allow for tearing everything down.
  489. class LoggingThread(threading.Thread):
  490. def __init__(self, logfunc, sock, logger):
  491. self.connection_established = threading.Event()
  492. self.serversock = sock
  493. self.logfunc = logfunc
  494. self.logger = logger
  495. self.readsock = None
  496. self.running = False
  497. self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL
  498. self.readevents = select.POLLIN | select.POLLPRI
  499. threading.Thread.__init__(self, target=self.threadtarget)
  500. def threadtarget(self):
  501. try:
  502. self.eventloop()
  503. finally:
  504. self.teardown()
  505. def run(self):
  506. self.logger.debug("Starting logging thread")
  507. self.readpipe, self.writepipe = os.pipe()
  508. threading.Thread.run(self)
  509. def stop(self):
  510. self.logger.debug("Stopping logging thread")
  511. if self.running:
  512. os.write(self.writepipe, bytes("stop", "utf-8"))
  513. def teardown(self):
  514. self.logger.debug("Tearing down logging thread")
  515. self.close_socket(self.serversock)
  516. if self.readsock is not None:
  517. self.close_socket(self.readsock)
  518. self.close_ignore_error(self.readpipe)
  519. self.close_ignore_error(self.writepipe)
  520. self.running = False
  521. def eventloop(self):
  522. poll = select.poll()
  523. event_read_mask = self.errorevents | self.readevents
  524. poll.register(self.serversock.fileno())
  525. poll.register(self.readpipe, event_read_mask)
  526. breakout = False
  527. self.running = True
  528. self.logger.debug("Starting thread event loop")
  529. while not breakout:
  530. events = poll.poll()
  531. for event in events:
  532. # An error occurred, bail out
  533. if event[1] & self.errorevents:
  534. raise Exception(self.stringify_event(event[1]))
  535. # Event to stop the thread
  536. if self.readpipe == event[0]:
  537. self.logger.debug("Stop event received")
  538. breakout = True
  539. break
  540. # A connection request was received
  541. elif self.serversock.fileno() == event[0]:
  542. self.logger.debug("Connection request received")
  543. self.readsock, _ = self.serversock.accept()
  544. self.readsock.setblocking(0)
  545. poll.unregister(self.serversock.fileno())
  546. poll.register(self.readsock.fileno(), event_read_mask)
  547. self.logger.debug("Setting connection established event")
  548. self.connection_established.set()
  549. # Actual data to be logged
  550. elif self.readsock.fileno() == event[0]:
  551. data = self.recv(1024)
  552. self.logfunc(data)
  553. # Since the socket is non-blocking make sure to honor EAGAIN
  554. # and EWOULDBLOCK.
  555. def recv(self, count):
  556. try:
  557. data = self.readsock.recv(count)
  558. except socket.error as e:
  559. if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
  560. return ''
  561. else:
  562. raise
  563. if data is None:
  564. raise Exception("No data on read ready socket")
  565. elif not data:
  566. # This actually means an orderly shutdown
  567. # happened. But for this code it counts as an
  568. # error since the connection shouldn't go away
  569. # until qemu exits.
  570. raise Exception("Console connection closed unexpectedly")
  571. return data
  572. def stringify_event(self, event):
  573. val = ''
  574. if select.POLLERR == event:
  575. val = 'POLLER'
  576. elif select.POLLHUP == event:
  577. val = 'POLLHUP'
  578. elif select.POLLNVAL == event:
  579. val = 'POLLNVAL'
  580. return val
  581. def close_socket(self, sock):
  582. sock.shutdown(socket.SHUT_RDWR)
  583. sock.close()
  584. def close_ignore_error(self, fd):
  585. try:
  586. os.close(fd)
  587. except OSError:
  588. pass