@@ -54,6 +54,128 @@ def test_010_os_metadata(self):
5454 self .assertEqual (tpl .features .get ("os-distribution" ), "kali" )
5555 self .assertEqual (tpl .features .get ("os-distribution-like" ), "debian" )
5656
57+ def test_110_rescue_console (self ):
58+ self .loop .run_until_complete (self ._test_110_rescue_console ())
59+
60+ async def _test_110_rescue_console (self ):
61+ self .testvm = self .app .add_new_vm (
62+ "AppVM" , label = "red" , name = self .make_vm_name ("vm" )
63+ )
64+ await self .testvm .create_on_disk ()
65+ self .testvm .kernelopts = "emergency"
66+ # avoid qrexec timeout
67+ self .testvm .features ["qrexec" ] = ""
68+ self .app .save ()
69+ await self .testvm .start ()
70+ # call admin.vm.Console via qrexec-client so it sets all the variables
71+ console_proc = await asyncio .create_subprocess_exec (
72+ "qrexec-client" ,
73+ "-d" ,
74+ "dom0" ,
75+ f"DEFAULT:QUBESRPC admin.vm.Console dom0 name { self .testvm .name } " ,
76+ stdin = subprocess .PIPE ,
77+ stdout = subprocess .PIPE ,
78+ )
79+ try :
80+ await asyncio .wait_for (
81+ self ._interact_emergency_console (console_proc ), 120
82+ )
83+ finally :
84+ with contextlib .suppress (ProcessLookupError ):
85+ console_proc .terminate ()
86+ await console_proc .communicate ()
87+
88+ async def _interact_emergency_console (
89+ self , console_proc : asyncio .subprocess .Process
90+ ):
91+ emergency_mode_found = False
92+ whoami_typed = False
93+ while True :
94+ try :
95+ line = await asyncio .wait_for (
96+ console_proc .stdout .readline (), 30
97+ )
98+ except TimeoutError :
99+ break
100+ if b"emergency mode" in line :
101+ emergency_mode_found = True
102+ if emergency_mode_found and b"Press Enter" in line :
103+ console_proc .stdin .write (b"\n " )
104+ await console_proc .stdin .drain ()
105+ # shell prompt doesn't include newline, so the top loop won't
106+ # progress on it
107+ while True :
108+ try :
109+ line2 = await asyncio .wait_for (
110+ console_proc .stdout .read (128 ), 5
111+ )
112+ except TimeoutError :
113+ break
114+ if b"bash" in line2 or b"root#" in line2 :
115+ break
116+ console_proc .stdin .write (b"echo $USER\n " )
117+ await console_proc .stdin .drain ()
118+ whoami_typed = True
119+ if whoami_typed and b"root" in line :
120+ return
121+ if whoami_typed :
122+ self .fail ("Calling whoami failed, but emergency console started" )
123+ if emergency_mode_found :
124+ self .fail ("Emergency mode started, but didn't got shell" )
125+ self .fail ("Emergency mode not found" )
126+
127+ def test_111_rescue_console_initrd (self ):
128+ if "minimal" in self .template :
129+ self .skipTest (
130+ "Test not relevant for minimal template - booting "
131+ "in-vm kernel not supported"
132+ )
133+ self .loop .run_until_complete (self ._test_111_rescue_console_initrd ())
134+
135+ async def _test_111_rescue_console_initrd (self ):
136+ self .testvm = self .app .add_new_vm (
137+ qubes .vm .standalonevm .StandaloneVM ,
138+ name = self .make_vm_name ("vm" ),
139+ label = "red" ,
140+ )
141+ self .testvm .kernel = None
142+ self .testvm .features .update (self .app .default_template .features )
143+ await self .testvm .clone_disk_files (self .app .default_template )
144+ self .app .save ()
145+
146+ await self .testvm .start ()
147+ await self .testvm .run_for_stdio (
148+ "echo 'GRUB_CMDLINE_LINUX=\" $GRUB_CMDLINE_LINUX rd.emergency\" ' >> "
149+ "/etc/default/grub" ,
150+ user = "root" ,
151+ )
152+ await self .testvm .run_for_stdio (
153+ "update-grub2 || grub2-mkconfig -o /boot/grub2/grub.cfg" ,
154+ user = "root" ,
155+ )
156+ await self .testvm .shutdown (wait = True )
157+
158+ # avoid qrexec timeout
159+ self .testvm .features ["qrexec" ] = ""
160+ await self .testvm .start ()
161+ # call admin.vm.Console via qrexec-client so it sets all the variables
162+ console_proc = await asyncio .create_subprocess_exec (
163+ "qrexec-client" ,
164+ "-d" ,
165+ "dom0" ,
166+ f"DEFAULT:QUBESRPC admin.vm.Console dom0 name { self .testvm .name } " ,
167+ stdin = subprocess .PIPE ,
168+ stdout = subprocess .PIPE ,
169+ )
170+ try :
171+ await asyncio .wait_for (
172+ self ._interact_emergency_console (console_proc ), 60
173+ )
174+ finally :
175+ with contextlib .suppress (ProcessLookupError ):
176+ console_proc .terminate ()
177+ await console_proc .communicate ()
178+
57179 @unittest .skipUnless (
58180 spawn .find_executable ("xdotool" ), "xdotool not installed"
59181 )
0 commit comments