4 # Copyright (c) 2018 Vojtech Horky
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
11 # - Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # - Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # - The name of the author may not be used to endorse or promote products
17 # derived from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 from htest
.utils
import retries
, format_command
, format_command_pipe
36 from htest
.vm
.controller
import VMController
38 class QemuVMController(VMController
):
43 def __init__(self
, arch
, name
, boot_image
):
44 VMController
.__init
__(self
, 'QEMU-' + arch
)
48 self
.boot_image
= boot_image
49 self
.logger
= logging
.getLogger('qemu-{}'.format(name
))
51 def _check_is_up(self
):
53 raise Exception("Machine not launched")
55 def _send_command(self
, command
):
57 self
.logger
.debug("Sending command '{}'".format(command
))
58 command
= command
+ '\n'
59 self
.monitor
.sendall(command
.encode('utf-8'))
61 def _run_command(self
, command
):
62 proc
= subprocess
.Popen(command
)
64 if proc
.returncode
!= 0:
65 raise Exception("Command {} failed.".format(command
))
67 def _run_pipe(self
, commands
):
68 self
.logger
.debug("Running pipe {}".format(format_command_pipe(commands
)))
70 for command
in commands
:
73 inp
= procs
[-1].stdout
74 proc
= subprocess
.Popen(command
, stdout
=subprocess
.PIPE
, stdin
=inp
)
76 procs
[-1].communicate()
79 def boot(self
, **kwargs
):
80 self
.monitor_file
= self
.get_temp('monitor')
81 cmd
= [ 'qemu-system-x86_64' , '-usb', '-m', '256' ]
82 cmd
.append('-enable-kvm')
84 cmd
.append(self
.boot_image
)
85 #cmd.append('-daemonize')
86 cmd
.append('-monitor')
87 cmd
.append('unix:{},server,nowait'.format(self
.monitor_file
))
88 self
.logger
.debug("Starting QEMU: {}".format(format_command(cmd
)))
90 self
.proc
= subprocess
.Popen(cmd
)
91 self
.monitor
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
92 for xxx
in retries(timeout
=30, interval
=2, name
="ctl-socket", message
="Failed to connect to QEMU control socket."):
94 self
.monitor
.connect(self
.monitor_file
)
96 except FileNotFoundError
:
98 except ConnectionRefusedError
:
101 raise Exception("QEMU not started, aborting.")
104 self
.logger
.info("Machine started.")
109 uspace_booted
= False
110 for xxx
in retries(timeout
=3*60, interval
=5, name
="vterm", message
="Failed to boot into userspace"):
111 lines
= self
.capture_vterm()
113 if l
.find('to see a few survival tips') != -1:
121 self
.logger
.info("Machine booted into userspace.")
125 def capture_vterm_impl(self
):
126 screenshot_full
= self
.get_temp('screen-full.ppm')
127 screenshot_term
= self
.get_temp('screen-term.png')
128 screenshot_text
= self
.get_temp('screen-term.txt')
130 self
._send
_command
('screendump ' + screenshot_full
)
132 for xxx
in retries(timeout
=5, interval
=1, name
="scrdump", message
="Failed to capture screen"):
137 '-crop', '640x480+4+24',
160 '-e', 's|[0-9]*,[0-9]*: ([^)]*)[ ]*#\\([0-9A-Fa-f]\\{6\\}\\).*|\\1|',
162 '-e', 's#000000#0#g',
165 [ 'tee', self
.get_temp('1.txt') ],
169 '-e', 'N;s#\\n##;s#^@##;/@$/{s#@$##p;d}',
172 [ 'tee', self
.get_temp('2.txt') ],
181 [ 'tee', self
.get_temp('3.txt') ],
190 [ 'tee', self
.get_temp('4.txt') ],
200 with
open(screenshot_text
, 'r') as f
:
201 lines
= [ l
.strip('\n') for l
in f
.readlines() ]
202 self
.logger
.debug("Captured text:")
204 self
.logger
.debug("| " + l
)
210 self
._send
_command
('quit')
211 VMController
.terminate(self
)
213 def type(self
, what
):
223 if letter
in translations
:
224 letter
= translations
[letter
]
225 self
._send
_command
('sendkey ' + letter
)