@@ -11,83 +11,224 @@ use std::ffi::OsString;
11
11
use std:: io:: { BufRead , BufReader , Read , Write } ;
12
12
use std:: path:: { Path , PathBuf } ;
13
13
use std:: process:: { Child , Command , Stdio } ;
14
- use std:: thread;
14
+ use std:: { env , thread} ;
15
15
use tempfile:: TempDir ;
16
16
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
+
17
32
struct OvmfPaths {
18
33
code : PathBuf ,
19
34
vars : PathBuf ,
20
- vars_read_only : bool ,
21
35
}
22
36
23
37
impl 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 {
25
47
match arch {
48
+ // Package "edk2-armvirt".
26
49
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 ( ) ,
32
52
} ,
53
+ // Package "edk2-ovmf".
33
54
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 ( ) ,
37
57
} ,
58
+ // Package "edk2-ovmf".
38
59
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 ( ) ,
42
62
} ,
43
63
}
44
64
}
45
65
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
+ }
48
84
}
49
85
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
+ } ,
59
126
}
127
+ }
60
128
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
+ } ,
66
145
}
146
+ }
67
147
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 ( ) ;
68
152
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 ( ) ) ;
79
195
}
80
196
}
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
+ ) ;
81
206
}
207
+ }
82
208
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 } )
84
217
}
85
218
}
86
219
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 ) {
88
226
// Build the argument as an OsString to avoid requiring a UTF-8 path.
89
227
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
+ } ) ;
91
232
arg. push ( ",file=" ) ;
92
233
arg. push ( file) ;
93
234
@@ -263,6 +404,18 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
263
404
} ;
264
405
let mut cmd = Command :: new ( qemu_exe) ;
265
406
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
+
266
419
// Disable default devices.
267
420
// QEMU by defaults enables a ton of devices which slow down boot.
268
421
cmd. arg ( "-nodefaults" ) ;
@@ -313,10 +466,20 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
313
466
}
314
467
}
315
468
469
+ let tmp_dir = TempDir :: new ( ) ?;
470
+ let tmp_dir = tmp_dir. path ( ) ;
471
+
316
472
// Set up OVMF.
317
473
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 ) ;
320
483
321
484
// Mount a local directory as a FAT partition.
322
485
cmd. arg ( "-drive" ) ;
@@ -331,9 +494,6 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
331
494
cmd. args ( & [ "-display" , "none" ] ) ;
332
495
}
333
496
334
- let tmp_dir = TempDir :: new ( ) ?;
335
- let tmp_dir = tmp_dir. path ( ) ;
336
-
337
497
let test_disk = tmp_dir. join ( "test_disk.fat.img" ) ;
338
498
create_mbr_test_disk ( & test_disk) ?;
339
499
0 commit comments