1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Enum and Anyhow helpers to set the process exit code.
6
7 use std::fmt;
8 use std::fmt::Display;
9 use std::fmt::Formatter;
10
11 use anyhow::Context;
12 use win_util::ProcessType;
13
14 pub type ExitCode = i32;
15
16 #[derive(Debug)]
17 pub struct ExitCodeWrapper(pub ExitCode);
18
19 impl Display for ExitCodeWrapper {
fmt(&self, f: &mut Formatter) -> fmt::Result20 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
21 write!(f, "exit code: {} = 0x{:08x}", self.0, self.0)
22 }
23 }
24
25 /// Trait for attaching context with process exit codes to a std::result::Result.
26 pub trait ExitContext<T, E> {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>27 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
28 where
29 X: Into<ExitCode>;
30
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static31 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
32 where
33 X: Into<ExitCode>,
34 C: Display + Send + Sync + 'static;
35
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C36 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
37 where
38 X: Into<ExitCode>,
39 C: Display + Send + Sync + 'static,
40 F: FnOnce() -> C;
41 }
42
43 impl<T, E> ExitContext<T, E> for std::result::Result<T, E>
44 where
45 E: std::error::Error + Send + Sync + 'static,
46 {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>,47 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
48 where
49 X: Into<ExitCode>,
50 {
51 self.context(ExitCodeWrapper(exit_code.into()))
52 }
53
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static,54 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
55 where
56 X: Into<ExitCode>,
57 C: Display + Send + Sync + 'static,
58 {
59 self.context(ExitCodeWrapper(exit_code.into()))
60 .context(context)
61 }
62
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C,63 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
64 where
65 X: Into<ExitCode>,
66 C: Display + Send + Sync + 'static,
67 F: FnOnce() -> C,
68 {
69 self.context(ExitCodeWrapper(exit_code.into()))
70 .with_context(f)
71 }
72 }
73
74 /// Trait for attaching context with process exit codes to an anyhow::Result.
75 pub trait ExitContextAnyhow<T> {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>76 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
77 where
78 X: Into<ExitCode>;
79
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static80 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
81 where
82 X: Into<ExitCode>,
83 C: Display + Send + Sync + 'static;
84
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C85 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
86 where
87 X: Into<ExitCode>,
88 C: Display + Send + Sync + 'static,
89 F: FnOnce() -> C;
90
to_exit_code(&self) -> Option<ExitCode>91 fn to_exit_code(&self) -> Option<ExitCode>;
92 }
93
94 impl<T> ExitContextAnyhow<T> for anyhow::Result<T> {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>,95 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
96 where
97 X: Into<ExitCode>,
98 {
99 self.context(ExitCodeWrapper(exit_code.into()))
100 }
101
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static,102 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
103 where
104 X: Into<ExitCode>,
105 C: Display + Send + Sync + 'static,
106 {
107 self.context(ExitCodeWrapper(exit_code.into()))
108 .context(context)
109 }
110
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C,111 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
112 where
113 X: Into<ExitCode>,
114 C: Display + Send + Sync + 'static,
115 F: FnOnce() -> C,
116 {
117 self.context(ExitCodeWrapper(exit_code.into()))
118 .with_context(f)
119 }
120
to_exit_code(&self) -> Option<ExitCode>121 fn to_exit_code(&self) -> Option<ExitCode> {
122 self.as_ref()
123 .err()
124 .and_then(|e| e.downcast_ref::<ExitCodeWrapper>())
125 .map(|w| w.0)
126 }
127 }
128
129 /// Trait for attaching context with process exit codes to an Option.
130 pub trait ExitContextOption<T> {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>131 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
132 where
133 X: Into<ExitCode>;
134
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static135 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
136 where
137 X: Into<ExitCode>,
138 C: Display + Send + Sync + 'static;
139
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C140 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
141 where
142 X: Into<ExitCode>,
143 C: Display + Send + Sync + 'static,
144 F: FnOnce() -> C;
145 }
146
147 impl<T> ExitContextOption<T> for std::option::Option<T> {
exit_code<X>(self, exit_code: X) -> anyhow::Result<T> where X: Into<ExitCode>,148 fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
149 where
150 X: Into<ExitCode>,
151 {
152 self.context(ExitCodeWrapper(exit_code.into()))
153 }
154
exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static,155 fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
156 where
157 X: Into<ExitCode>,
158 C: Display + Send + Sync + 'static,
159 {
160 self.context(ExitCodeWrapper(exit_code.into()))
161 .context(context)
162 }
163
with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T> where X: Into<ExitCode>, C: Display + Send + Sync + 'static, F: FnOnce() -> C,164 fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
165 where
166 X: Into<ExitCode>,
167 C: Display + Send + Sync + 'static,
168 F: FnOnce() -> C,
169 {
170 self.context(ExitCodeWrapper(exit_code.into()))
171 .with_context(f)
172 }
173 }
174
175 #[macro_export]
176 macro_rules! bail_exit_code {
177 ($exit_code:literal, $msg:literal $(,)?) => {
178 return Err(anyhow!($msg)).exit_code($exit_code)
179 };
180 ($exit_code:literal, $err:expr $(,)?) => {
181 return Err(anyhow!($err)).exit_code($exit_code)
182 };
183 ($exit_code:literal, $fmt:expr, $($arg:tt)*) => {
184 return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
185 };
186 ($exit_code:expr, $msg:literal $(,)?) => {
187 return Err(anyhow!($msg)).exit_code($exit_code)
188 };
189 ($exit_code:expr, $err:expr $(,)?) => {
190 return Err(anyhow!($err)).exit_code($exit_code)
191 };
192 ($exit_code:expr, $fmt:expr, $($arg:tt)*) => {
193 return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
194 };
195 }
196
197 #[macro_export]
198 macro_rules! ensure_exit_code {
199 ($cond:expr, $exit_code:literal $(,)?) => {
200 if !$cond {
201 bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
202 }
203 };
204 ($cond:expr, $exit_code:literal, $msg:literal $(,)?) => {
205 if !$cond {
206 bail_exit_code!($exit_code, $msg);
207 }
208 };
209 ($cond:expr, $exit_code:literal, $err:expr $(,)?) => {
210 if !$cond {
211 bail_exit_code!($exit_code, $err);
212 }
213 };
214 ($cond:expr, $exit_code:literal, $fmt:expr, $($arg:tt)*) => {
215 if !$cond {
216 bail_exit_code!($exit_code, $fmt, $($arg)*);
217 }
218 };
219 ($cond:expr, $exit_code:expr $(,)?) => {
220 if !$cond {
221 bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
222 }
223 };
224 ($cond:expr, $exit_code:expr, $msg:literal $(,)?) => {
225 if !$cond {
226 bail_exit_code!($exit_code, $msg);
227 }
228 };
229 ($cond:expr, $exit_code:expr, $err:expr $(,)?) => {
230 if !$cond {
231 bail_exit_code!($exit_code, $err);
232 }
233 };
234 ($cond:expr, $exit_code:expr, $fmt:expr, $($arg:tt)*) => {
235 if !$cond {
236 bail_exit_code!($exit_code, $fmt, $($arg)*);
237 }
238 };
239 }
240
241 #[allow(clippy::enum_clike_unportable_variant)]
242 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
243 pub enum Exit {
244 // Windows process exit codes triggered by the kernel tend to be NTSTATUS, so we treat
245 // our error codes as NTSTATUS to avoid clashing. This means we set the vendor bit. We also
246 // set the severity to error. As these all set in the MSB, we can write this as a prefix of
247 // 0xE0.
248 //
249 // Because of how these error codes are used in CommandType, we can only use the lower two
250 // bytes of the u32 for our error codes; in other words, the legal range is
251 // [0xE0000000, 0xE000FFFF].
252 AddGpuDeviceMemory = 0xE0000001,
253 AddIrqChipVcpu = 0xE0000002,
254 AddPmemDeviceMemory = 0xE0000003,
255 AllocateGpuDeviceAddress = 0xE0000004,
256 AllocatePmemDeviceAddress = 0xE0000005,
257 BlockDeviceNew = 0xE0000006,
258 BuildVm = 0xE0000007,
259 ChownTpmStorage = 0xE0000008,
260 CloneEvent = 0xE000000A,
261 CloneVcpu = 0xE000000B,
262 ConfigureVcpu = 0xE000000C,
263 CreateAc97 = 0xE000000D,
264 CreateConsole = 0xE000000E,
265 CreateDisk = 0xE000000F,
266 CreateEvent = 0xE0000010,
267 CreateGralloc = 0xE0000011,
268 CreateGvm = 0xE0000012,
269 CreateSocket = 0xE0000013,
270 CreateTapDevice = 0xE0000014,
271 CreateTimer = 0xE0000015,
272 CreateTpmStorage = 0xE0000016,
273 CreateVcpu = 0xE0000017,
274 CreateWaitContext = 0xE0000018,
275 Disk = 0xE0000019,
276 DiskImageLock = 0xE000001A,
277 DropCapabilities = 0xE000001B,
278 EventDeviceSetup = 0xE000001C,
279 EnableHighResTimer = 0xE000001D,
280 HandleCreateQcowError = 0xE000001E,
281 HandleVmRequestError = 0xE0000020,
282 InitSysLogError = 0xE0000021,
283 InputDeviceNew = 0xE0000022,
284 InputEventsOpen = 0xE0000023,
285 InvalidRunArgs = 0xE0000025,
286 InvalidSubCommand = 0xE0000026,
287 InvalidSubCommandArgs = 0xE0000027,
288 InvalidWaylandPath = 0xE0000028,
289 LoadKernel = 0xE0000029,
290 MissingCommandArg = 0xE0000030,
291 ModifyBatteryError = 0xE0000031,
292 NetDeviceNew = 0xE0000032,
293 OpenAcpiTable = 0xE0000033,
294 OpenAndroidFstab = 0xE0000034,
295 OpenBios = 0xE0000035,
296 OpenInitrd = 0xE0000036,
297 OpenKernel = 0xE0000037,
298 OpenVinput = 0xE0000038,
299 PivotRootDoesntExist = 0xE0000039,
300 PmemDeviceImageTooBig = 0xE000003A,
301 PmemDeviceNew = 0xE000003B,
302 ReadMemAvailable = 0xE000003C,
303 RegisterBalloon = 0xE000003D,
304 RegisterBlock = 0xE000003E,
305 RegisterGpu = 0xE000003F,
306 RegisterNet = 0xE0000040,
307 RegisterP9 = 0xE0000041,
308 RegisterRng = 0xE0000042,
309 RegisterWayland = 0xE0000043,
310 ReserveGpuMemory = 0xE0000044,
311 ReserveMemory = 0xE0000045,
312 ReservePmemMemory = 0xE0000046,
313 ResetTimer = 0xE0000047,
314 RngDeviceNew = 0xE0000048,
315 RunnableVcpu = 0xE0000049,
316 SettingSignalMask = 0xE000004B,
317 SpawnVcpu = 0xE000004D,
318 SysUtil = 0xE000004E,
319 Timer = 0xE000004F,
320 ValidateRawDescriptor = 0xE0000050,
321 VirtioPciDev = 0xE0000051,
322 WaitContextAdd = 0xE0000052,
323 WaitContextDelete = 0xE0000053,
324 WhpxSetupError = 0xE0000054,
325 VcpuFailEntry = 0xE0000055,
326 VcpuRunError = 0xE0000056,
327 VcpuShutdown = 0xE0000057,
328 VcpuSystemEvent = 0xE0000058,
329 WaitUntilRunnable = 0xE0000059,
330 CreateControlServer = 0xE000005A,
331 CreateTube = 0xE000005B,
332 UsbError = 0xE000005E,
333 GuestMemoryLayout = 0xE000005F,
334 CreateVm = 0xE0000060,
335 CreateGuestMemory = 0xE0000061,
336 CreateIrqChip = 0xE0000062,
337 SpawnIrqThread = 0xE0000063,
338 ConnectTube = 0xE0000064,
339 BalloonDeviceNew = 0xE0000065,
340 BalloonStats = 0xE0000066,
341 BorrowVfioContainer = 0xE0000067,
342 OpenCompositeFooterFile = 0xE0000068,
343 OpenCompositeHeaderFile = 0xE0000069,
344 OpenCompositeImageFile = 0xE0000070,
345 CreateCompositeDisk = 0xE0000071,
346 MissingControlTube = 0xE0000072,
347 TubeTransporterInit = 0xE0000073,
348 TubeFailure = 0xE0000074,
349 ProcessSpawnFailed = 0xE0000075,
350 LogFile = 0xE0000076,
351 CreateZeroFiller = 0xE0000077,
352 GenerateAcpi = 0xE0000078,
353 WaitContextWait = 0xE0000079,
354 SetSigintHandler = 0xE000007A,
355 KilledBySignal = 0xE000007B,
356 BrokerDeviceExitedTimeout = 0xE000007C,
357 BrokerMainExitedTimeout = 0xE000007D,
358 MemoryTooLarge = 0xE000007E,
359 BrokerMetricsExitedTimeout = 0xE000007F,
360 MetricsController = 0xE0000080,
361 SwiotlbTooLarge = 0xE0000081,
362 UserspaceVsockDeviceNew = 0xE0000082,
363 VhostUserBlockDeviceNew = 0xE0000083,
364 CrashReportingInit = 0xE0000084,
365 StartBackendDevice = 0xE0000085,
366 ConfigureHotPlugDevice = 0xE0000086,
367 InvalidHotPlugKey = 0xE0000087,
368 InvalidVfioPath = 0xE0000088,
369 NoHotPlugBus = 0xE0000089,
370 SandboxError = 0xE000008A,
371 Pstore = 0xE000008B,
372 ProcessInvariantsInit = 0xE000008C,
373 VirtioVhostUserDeviceNew = 0xE000008D,
374 CloneTube = 0xE000008E,
375 VhostUserGpuDeviceNew = 0xE000008F,
376 CreateAsyncDisk = 0xE0000090,
377 CreateDiskCheckAsyncOkError = 0xE0000091,
378 VhostUserNetDeviceNew = 0xE0000092,
379 BrokerSigtermTimeout = 0xE0000093,
380 SpawnVcpuMonitor = 0xE0000094,
381 NoDefaultHypervisor = 0xE0000095,
382 TscCalibrationFailed = 0xE0000096,
383 UnknownError = 0xE0000097,
384 CommonChildSetupError = 0xE0000098,
385 CreateImeThread = 0xE0000099,
386 OpenDiskImage = 0xE000009A,
387 VirtioSoundDeviceNew = 0xE000009B,
388 StartSpu = 0xE000009C,
389 SandboxCreateProcessAccessDenied = 0xE000009D,
390 SandboxCreateProcessElevationRequired = 0xE000009E,
391 BalloonSizeInvalid = 0xE000009F,
392 VhostUserSndDeviceNew = 0xE00000A0,
393 }
394
395 impl From<Exit> for ExitCode {
from(exit: Exit) -> Self396 fn from(exit: Exit) -> Self {
397 exit as ExitCode
398 }
399 }
400
401 // Bitfield masks for NTSTATUS & our extension of the format. See to_process_type_error for details.
402 mod bitmasks {
403 pub const FACILITY_FIELD_LOWER_MASK: u32 = u32::from_be_bytes([0x00, 0x3F, 0x00, 0x00]);
404 pub const EXTRA_DATA_FIELD_MASK: u32 = u32::from_be_bytes([0x0F, 0xC0, 0x00, 0x00]);
405 #[cfg(test)]
406 pub const EXTRA_DATA_FIELD_COMMAND_TYPE_MASK: u32 =
407 u32::from_be_bytes([0x07, 0xC0, 0x00, 0x00]);
408 pub const EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK: u32 =
409 u32::from_be_bytes([0x08, 0x00, 0x00, 0x00]);
410 pub const VENDOR_FIELD_MASK: u32 = u32::from_be_bytes([0x20, 0x00, 0x00, 0x00]);
411 pub const RESERVED_BIT_MASK: u32 = u32::from_be_bytes([0x10, 0x00, 0x00, 0x00]);
412 pub const COMMAND_TYPE_MASK: u32 = u32::from_be_bytes([0x00, 0x00, 0x00, 0x1F]);
413 }
414 use bitmasks::*;
415
416 /// If you are looking for a fun interview question, you have come to the right place. To
417 /// understand the details of NTSTATUS, which you'll want to do before reading further, visit
418 /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781.
419 ///
420 /// This function is unfortunately what happens when you only have six bits to store auxiliary
421 /// information, and have to fit in with an existing bitfield's schema.
422 ///
423 /// For reference, the format of the NTSTATUS field is as follows:
424 ///
425 /// | [31, 30] | [29] | [28] | [27, 16] | [15, 0] |
426 /// | Severity | Customer/vendor | N (reserved) | Facility | Code |
427 ///
428 /// This function packs bits in NTSTATUS results (generally what a Windows exit code should be).
429 /// There are three primary cases it deals with:
430 /// 1. Vendor specific exits. These are error codes we generate explicitly in crosvm. We will
431 /// pack these codes with the lower 6 "facility" bits ([21, 16]) set so they can't collide
432 /// with the other cases (this makes our facility value > FACILITY_MAXIMUM_VALUE). The top
433 /// 6 bits of the facility field ([27, 22]) will be clear at this point.
434 ///
435 /// 2. Non vendor NTSTATUS exits. These are error codes which come from Windows. We flip the
436 /// vendor bit on these because we're going to pack the facility field, and leaving it unset
437 /// would cause us to violate the rule that if the vendor bit is unset, we shouldn't exceed
438 /// FACILITY_MAXIMUM_VALUE in that field. The top six bits of the facility field ([27, 22])
439 /// will be clear in this scenario because Windows won't exceed FACILITY_MAXIMUM_VALUE;
440 /// however, if for some reason we see a non vendor code with any of those bits set, we will
441 /// fall through to case #3.
442 ///
443 /// 3. Non NTSTATUS errors. We detect these with two heuristics:
444 /// a) Reserved field is set.
445 /// b) The facility field has exceeded the bottom six bits ([21, 16]).
446 ///
447 /// For such cases, we pack as much of the error as we can into the lower 6 bits of the
448 /// facility field, and code field (2 bytes). In this case, the most significant bit of the
449 /// facility field is set.
450 ///
451 /// For all of the cases above, we pack the 5 bits following the most significant bit of the
452 /// facility field (e.g. [26, 22]) with information about what command type generated this error.
to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32453 pub fn to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32 {
454 let is_vendor = error_code & VENDOR_FIELD_MASK != 0;
455
456 // The reserved bit is always clear on a NTSTATUS code.
457 let is_reserved_bit_clear = error_code & RESERVED_BIT_MASK == 0;
458
459 // The six most significant bits of the facility field are where we'll be storing our
460 // command type and whether we have a valid NTSTATUS error. If bits are already set there,
461 // it means this isn't a valid NTSTATUS code.
462 let is_extra_data_field_clear = error_code & EXTRA_DATA_FIELD_MASK == 0;
463
464 let is_ntstatus = is_reserved_bit_clear && is_extra_data_field_clear;
465
466 // We use the top bit of the facility field to store whether we ran out of space to pack
467 // the error. The next five bits are where we store the command type, so we'll shift them
468 // into the appropriate position here.
469 let command_type = (cmd_type as u32 & COMMAND_TYPE_MASK) << 22;
470
471 match (is_ntstatus, is_vendor) {
472 // Valid vendor code
473 (true, true) => {
474 // Set all the lower facility bits, and attach the command type.
475 error_code | FACILITY_FIELD_LOWER_MASK | command_type
476 }
477
478 // Valid non-vendor code
479 (true, false) => {
480 // Set the vendor bit and attach the command type.
481 error_code | VENDOR_FIELD_MASK | command_type
482 }
483
484 // Not a valid NTSTATUS code.
485 _ => {
486 // Clear the extra data field, and set the the top bit of the facility field to
487 // signal that we didn't have enough space for the full error codes.
488 error_code & !EXTRA_DATA_FIELD_MASK | command_type | EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK
489 }
490 }
491 }
492
493 #[cfg(test)]
494 mod tests {
495 use winapi::shared::ntstatus::STATUS_BAD_INITIAL_PC;
496
497 use super::*;
498
499 #[test]
test_to_process_type_error_ntstatus_vendor()500 fn test_to_process_type_error_ntstatus_vendor() {
501 let e = to_process_type_error(Exit::InvalidRunArgs as u32, ProcessType::Main);
502 assert_eq!(
503 e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
504 (ProcessType::Main as u32) << 22
505 );
506 assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
507
508 // This is a valid NTSTATUS error.
509 assert_eq!(e & RESERVED_BIT_MASK, 0);
510
511 // Check the actual crosvm error code contained in the NTSTATUS. We don't mutate the
512 // severity field, so we don't mask it off. We mask off the facility field entirely because
513 // that's where we stored the command type & NTSTATUS validity bit.
514 assert_eq!(e & 0xF000FFFF_u32, Exit::InvalidRunArgs as u32);
515 }
516
517 #[test]
test_to_process_type_error_ntstatus_non_vendor()518 fn test_to_process_type_error_ntstatus_non_vendor() {
519 let e = to_process_type_error(STATUS_BAD_INITIAL_PC as u32, ProcessType::Main);
520 assert_eq!(
521 e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
522 (ProcessType::Main as u32) << 22
523 );
524 assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
525
526 // This is a valid NTSTATUS error.
527 assert_eq!(e & RESERVED_BIT_MASK, 0);
528
529 // Check the actual error code contained in the NTSTATUS. We mask off all our extra data
530 // fields and switch off the vendor bit to confirm the actual code was left alone.
531 assert_eq!(
532 e & !EXTRA_DATA_FIELD_MASK & !VENDOR_FIELD_MASK,
533 STATUS_BAD_INITIAL_PC as u32
534 );
535 }
536
537 #[test]
test_to_process_type_error_wontfit_ntstatus()538 fn test_to_process_type_error_wontfit_ntstatus() {
539 let e = to_process_type_error(0xFFFFFFFF, ProcessType::Main);
540 assert_eq!(
541 e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
542 (ProcessType::Main as u32) << 22
543 );
544
545 // -1 is not a valid NTSTATUS error.
546 assert_ne!(e & RESERVED_BIT_MASK, 0);
547
548 // Overflow did occur.
549 assert_ne!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
550
551 // Check that we left the rest of the bits (except for our command type field & overflow
552 // bit) in the exit code untouched.
553 assert_eq!(e & 0xF03FFFFF_u32, 0xF03FFFFF_u32);
554 }
555 }
556