@@ -11,83 +11,224 @@ use std::ffi::OsString;
1111use std:: io:: { BufRead , BufReader , Read , Write } ;
1212use std:: path:: { Path , PathBuf } ;
1313use std:: process:: { Child , Command , Stdio } ;
14- use std:: thread;
14+ use std:: { env , thread} ;
1515use tempfile:: TempDir ;
1616
17+ #[ derive( Clone , Copy , Debug ) ]
18+ enum OvmfFileType {
19+ Code ,
20+ Vars ,
21+ }
22+
23+ impl OvmfFileType {
24+ fn as_str ( & self ) -> & ' static str {
25+ match self {
26+ Self :: Code => "code" ,
27+ Self :: Vars => "vars" ,
28+ }
29+ }
30+ }
31+
1732struct OvmfPaths {
1833 code : PathBuf ,
1934 vars : PathBuf ,
20- vars_read_only : bool ,
2135}
2236
2337impl OvmfPaths {
24- fn from_dir ( dir : & Path , arch : UefiArch ) -> Self {
38+ fn get_path ( & self , file_type : OvmfFileType ) -> & Path {
39+ match file_type {
40+ OvmfFileType :: Code => & self . code ,
41+ OvmfFileType :: Vars => & self . vars ,
42+ }
43+ }
44+
45+ /// Get the Arch Linux OVMF paths for the given guest arch.
46+ fn arch_linux ( arch : UefiArch ) -> Self {
2547 match arch {
48+ // Package "edk2-armvirt".
2649 UefiArch :: AArch64 => Self {
27- code : dir. join ( "QEMU_EFI-pflash.raw" ) ,
28- vars : dir. join ( "vars-template-pflash.raw" ) ,
29- // The OVMF implementation for AArch64 won't boot unless
30- // the vars file is writeable.
31- vars_read_only : false ,
50+ code : "/usr/share/edk2-armvirt/aarch64/QEMU_CODE.fd" . into ( ) ,
51+ vars : "/usr/share/edk2-armvirt/aarch64/QEMU_VARS.fd" . into ( ) ,
3252 } ,
53+ // Package "edk2-ovmf".
3354 UefiArch :: IA32 => Self {
34- code : dir. join ( "OVMF32_CODE.fd" ) ,
35- vars : dir. join ( "OVMF32_VARS.fd" ) ,
36- vars_read_only : true ,
55+ code : "/usr/share/edk2-ovmf/ia32/OVMF_CODE.fd" . into ( ) ,
56+ vars : "/usr/share/edk2-ovmf/ia32/OVMF_VARS.fd" . into ( ) ,
3757 } ,
58+ // Package "edk2-ovmf".
3859 UefiArch :: X86_64 => Self {
39- code : dir. join ( "OVMF_CODE.fd" ) ,
40- vars : dir. join ( "OVMF_VARS.fd" ) ,
41- vars_read_only : true ,
60+ code : "/usr/share/edk2-ovmf/x64/OVMF_CODE.fd" . into ( ) ,
61+ vars : "/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" . into ( ) ,
4262 } ,
4363 }
4464 }
4565
46- fn exists ( & self ) -> bool {
47- self . code . exists ( ) && self . vars . exists ( )
66+ /// Get the CentOS OVMF paths for the given guest arch.
67+ fn centos_linux ( arch : UefiArch ) -> Option < Self > {
68+ match arch {
69+ // Package "edk2-aarch64".
70+ UefiArch :: AArch64 => Some ( Self {
71+ code : "/usr/share/edk2/aarch64/QEMU_EFI-pflash.raw" . into ( ) ,
72+ vars : "/usr/share/edk2/aarch64/vars-template-pflash.raw" . into ( ) ,
73+ } ) ,
74+ // There's no official ia32 package.
75+ UefiArch :: IA32 => None ,
76+ // Package "edk2-ovmf".
77+ UefiArch :: X86_64 => Some ( Self {
78+ // Use the `.secboot` variant because the CentOS package
79+ // doesn't have a plain "OVMF_CODE.fd".
80+ code : "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd" . into ( ) ,
81+ vars : "/usr/share/edk2/ovmf/OVMF_VARS.fd" . into ( ) ,
82+ } ) ,
83+ }
4884 }
4985
50- /// Find path to OVMF files.
51- fn find ( opt : & QemuOpt , arch : UefiArch ) -> Result < Self > {
52- // If the path is specified in the settings, use it.
53- if let Some ( ovmf_dir) = & opt. ovmf_dir {
54- let ovmf_paths = Self :: from_dir ( ovmf_dir, arch) ;
55- if ovmf_paths. exists ( ) {
56- return Ok ( ovmf_paths) ;
57- }
58- bail ! ( "OVMF files not found in {}" , ovmf_dir. display( ) ) ;
86+ /// Get the Debian OVMF paths for the given guest arch. These paths
87+ /// also work on Ubuntu.
88+ fn debian_linux ( arch : UefiArch ) -> Self {
89+ match arch {
90+ // Package "qemu-efi-aarch64".
91+ UefiArch :: AArch64 => Self {
92+ code : "/usr/share/AAVMF/AAVMF_CODE.fd" . into ( ) ,
93+ vars : "/usr/share/AAVMF/AAVMF_VARS.fd" . into ( ) ,
94+ } ,
95+ // Package "ovmf-ia32".
96+ UefiArch :: IA32 => Self {
97+ code : "/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd" . into ( ) ,
98+ vars : "/usr/share/OVMF/OVMF32_VARS_4M.fd" . into ( ) ,
99+ } ,
100+ // Package "ovmf".
101+ UefiArch :: X86_64 => Self {
102+ code : "/usr/share/OVMF/OVMF_CODE.fd" . into ( ) ,
103+ vars : "/usr/share/OVMF/OVMF_VARS.fd" . into ( ) ,
104+ } ,
105+ }
106+ }
107+
108+ /// Get the Fedora OVMF paths for the given guest arch.
109+ fn fedora_linux ( arch : UefiArch ) -> Self {
110+ match arch {
111+ // Package "edk2-aarch64".
112+ UefiArch :: AArch64 => Self {
113+ code : "/usr/share/edk2/aarch64/QEMU_EFI-pflash.raw" . into ( ) ,
114+ vars : "/usr/share/edk2/aarch64/vars-template-pflash.raw" . into ( ) ,
115+ } ,
116+ // Package "edk2-ovmf-ia32".
117+ UefiArch :: IA32 => Self {
118+ code : "/usr/share/edk2/ovmf-ia32/OVMF_CODE.fd" . into ( ) ,
119+ vars : "/usr/share/edk2/ovmf-ia32/OVMF_VARS.fd" . into ( ) ,
120+ } ,
121+ // Package "edk2-ovmf".
122+ UefiArch :: X86_64 => Self {
123+ code : "/usr/share/edk2/ovmf/OVMF_CODE.fd" . into ( ) ,
124+ vars : "/usr/share/edk2/ovmf/OVMF_VARS.fd" . into ( ) ,
125+ } ,
59126 }
127+ }
60128
61- // Check whether the test runner directory contains the files.
62- let ovmf_dir = Path :: new ( "uefi-test-runner" ) ;
63- let ovmf_paths = Self :: from_dir ( ovmf_dir, arch) ;
64- if ovmf_paths. exists ( ) {
65- return Ok ( ovmf_paths) ;
129+ /// Get the Windows OVMF paths for the given guest arch.
130+ fn windows ( arch : UefiArch ) -> Self {
131+ match arch {
132+ UefiArch :: AArch64 => Self {
133+ code : r"C:\Program Files\qemu\share\edk2-aarch64-code.fd" . into ( ) ,
134+ vars : r"C:\Program Files\qemu\share\edk2-arm-vars.fd" . into ( ) ,
135+ } ,
136+ UefiArch :: IA32 => Self {
137+ code : r"C:\Program Files\qemu\share\edk2-i386-code.fd" . into ( ) ,
138+ vars : r"C:\Program Files\qemu\share\edk2-i386-vars.fd" . into ( ) ,
139+ } ,
140+ UefiArch :: X86_64 => Self {
141+ code : r"C:\Program Files\qemu\share\edk2-x86_64-code.fd" . into ( ) ,
142+ // There's no x86_64 vars file, but the i386 one works.
143+ vars : r"C:\Program Files\qemu\share\edk2-i386-vars.fd" . into ( ) ,
144+ } ,
66145 }
146+ }
67147
148+ /// Get candidate paths where OVMF code/vars might exist for the
149+ /// given guest arch and host platform.
150+ fn get_candidate_paths ( arch : UefiArch ) -> Vec < Self > {
151+ let mut candidates = Vec :: new ( ) ;
68152 if platform:: is_linux ( ) {
69- let possible_paths = [
70- // Most distros, including CentOS, Fedora, Debian, and Ubuntu.
71- Path :: new ( "/usr/share/OVMF" ) ,
72- // Arch Linux.
73- Path :: new ( "/usr/share/ovmf/x64" ) ,
74- ] ;
75- for path in possible_paths {
76- let ovmf_paths = Self :: from_dir ( path, arch) ;
77- if ovmf_paths. exists ( ) {
78- return Ok ( ovmf_paths) ;
153+ candidates. push ( Self :: arch_linux ( arch) ) ;
154+ if let Some ( candidate) = Self :: centos_linux ( arch) {
155+ candidates. push ( candidate) ;
156+ }
157+ candidates. push ( Self :: debian_linux ( arch) ) ;
158+ candidates. push ( Self :: fedora_linux ( arch) ) ;
159+ }
160+ if platform:: is_windows ( ) {
161+ candidates. push ( Self :: windows ( arch) ) ;
162+ }
163+ candidates
164+ }
165+
166+ /// Search for an OVMF file (either code or vars).
167+ ///
168+ /// If `user_provided_path` is not None, it is always used. An error
169+ /// is returned if the path does not exist.
170+ ///
171+ /// Otherwise, the paths in `candidates` are searched to find one
172+ /// that exists. If none of them exist, an error is returned.
173+ fn find_ovmf_file (
174+ file_type : OvmfFileType ,
175+ user_provided_path : & Option < PathBuf > ,
176+ candidates : & [ Self ] ,
177+ ) -> Result < PathBuf > {
178+ if let Some ( path) = user_provided_path {
179+ // The user provided an exact path to use; verify that it
180+ // exists.
181+ if path. exists ( ) {
182+ Ok ( path. to_owned ( ) )
183+ } else {
184+ bail ! (
185+ "ovmf {} file does not exist: {}" ,
186+ file_type. as_str( ) ,
187+ path. display( )
188+ ) ;
189+ }
190+ } else {
191+ for candidate in candidates {
192+ let path = candidate. get_path ( file_type) ;
193+ if path. exists ( ) {
194+ return Ok ( path. to_owned ( ) ) ;
79195 }
80196 }
197+
198+ bail ! (
199+ "no ovmf {} file found in candidates: {:?}" ,
200+ file_type. as_str( ) ,
201+ candidates
202+ . iter( )
203+ . map( |c| c. get_path( file_type) )
204+ . collect:: <Vec <_>>( ) ,
205+ ) ;
81206 }
207+ }
82208
83- bail ! ( "OVMF files not found anywhere" ) ;
209+ /// Find path to OVMF files.
210+ fn find ( opt : & QemuOpt , arch : UefiArch ) -> Result < Self > {
211+ let candidates = Self :: get_candidate_paths ( arch) ;
212+
213+ let code = Self :: find_ovmf_file ( OvmfFileType :: Code , & opt. ovmf_code , & candidates) ?;
214+ let vars = Self :: find_ovmf_file ( OvmfFileType :: Vars , & opt. ovmf_vars , & candidates) ?;
215+
216+ Ok ( Self { code, vars } )
84217 }
85218}
86219
87- fn add_pflash_args ( cmd : & mut Command , file : & Path , read_only : bool ) {
220+ enum PflashMode {
221+ ReadOnly ,
222+ ReadWrite ,
223+ }
224+
225+ fn add_pflash_args ( cmd : & mut Command , file : & Path , mode : PflashMode ) {
88226 // Build the argument as an OsString to avoid requiring a UTF-8 path.
89227 let mut arg = OsString :: from ( "if=pflash,format=raw,readonly=" ) ;
90- arg. push ( if read_only { "on" } else { "off" } ) ;
228+ arg. push ( match mode {
229+ PflashMode :: ReadOnly => "on" ,
230+ PflashMode :: ReadWrite => "off" ,
231+ } ) ;
91232 arg. push ( ",file=" ) ;
92233 arg. push ( file) ;
93234
@@ -263,6 +404,18 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
263404 } ;
264405 let mut cmd = Command :: new ( qemu_exe) ;
265406
407+ if platform:: is_windows ( ) {
408+ // The QEMU installer for Windows does not automatically add the
409+ // directory containing the QEMU executables to the PATH. Add
410+ // the default directory to the PATH to make it more likely that
411+ // QEMU will be found on Windows. (The directory is appended, so
412+ // if a different directory on the PATH already has the QEMU
413+ // binary this change won't affect anything.)
414+ let mut path = env:: var_os ( "PATH" ) . unwrap_or_default ( ) ;
415+ path. push ( r";C:\Program Files\qemu" ) ;
416+ cmd. env ( "PATH" , path) ;
417+ }
418+
266419 // Disable default devices.
267420 // QEMU by defaults enables a ton of devices which slow down boot.
268421 cmd. arg ( "-nodefaults" ) ;
@@ -313,10 +466,20 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
313466 }
314467 }
315468
469+ let tmp_dir = TempDir :: new ( ) ?;
470+ let tmp_dir = tmp_dir. path ( ) ;
471+
316472 // Set up OVMF.
317473 let ovmf_paths = OvmfPaths :: find ( opt, arch) ?;
318- add_pflash_args ( & mut cmd, & ovmf_paths. code , /*read_only=*/ true ) ;
319- add_pflash_args ( & mut cmd, & ovmf_paths. vars , ovmf_paths. vars_read_only ) ;
474+
475+ // Make a copy of the OVMF vars file so that it can be used
476+ // read+write without modifying the original. Under AArch64, some
477+ // versions of OVMF won't boot if the vars file isn't writeable.
478+ let ovmf_vars = tmp_dir. join ( "ovmf_vars" ) ;
479+ fs_err:: copy ( & ovmf_paths. vars , & ovmf_vars) ?;
480+
481+ add_pflash_args ( & mut cmd, & ovmf_paths. code , PflashMode :: ReadOnly ) ;
482+ add_pflash_args ( & mut cmd, & ovmf_vars, PflashMode :: ReadWrite ) ;
320483
321484 // Mount a local directory as a FAT partition.
322485 cmd. arg ( "-drive" ) ;
@@ -331,9 +494,6 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
331494 cmd. args ( & [ "-display" , "none" ] ) ;
332495 }
333496
334- let tmp_dir = TempDir :: new ( ) ?;
335- let tmp_dir = tmp_dir. path ( ) ;
336-
337497 let test_disk = tmp_dir. join ( "test_disk.fat.img" ) ;
338498 create_mbr_test_disk ( & test_disk) ?;
339499
0 commit comments