xref: /aosp_15_r20/external/crosvm/crosvm_cli/src/sys/windows/exit.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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     CreateConsole = 0xE000000E,
264     CreateDisk = 0xE000000F,
265     CreateEvent = 0xE0000010,
266     CreateGralloc = 0xE0000011,
267     CreateGvm = 0xE0000012,
268     CreateSocket = 0xE0000013,
269     CreateTapDevice = 0xE0000014,
270     CreateTimer = 0xE0000015,
271     CreateTpmStorage = 0xE0000016,
272     CreateVcpu = 0xE0000017,
273     CreateWaitContext = 0xE0000018,
274     Disk = 0xE0000019,
275     DiskImageLock = 0xE000001A,
276     DropCapabilities = 0xE000001B,
277     EventDeviceSetup = 0xE000001C,
278     EnableHighResTimer = 0xE000001D,
279     HandleCreateQcowError = 0xE000001E,
280     HandleVmRequestError = 0xE0000020,
281     InitSysLogError = 0xE0000021,
282     InputDeviceNew = 0xE0000022,
283     InputEventsOpen = 0xE0000023,
284     InvalidRunArgs = 0xE0000025,
285     InvalidSubCommand = 0xE0000026,
286     InvalidSubCommandArgs = 0xE0000027,
287     InvalidWaylandPath = 0xE0000028,
288     LoadKernel = 0xE0000029,
289     MissingCommandArg = 0xE0000030,
290     ModifyBatteryError = 0xE0000031,
291     NetDeviceNew = 0xE0000032,
292     OpenAcpiTable = 0xE0000033,
293     OpenAndroidFstab = 0xE0000034,
294     OpenBios = 0xE0000035,
295     OpenInitrd = 0xE0000036,
296     OpenKernel = 0xE0000037,
297     OpenVinput = 0xE0000038,
298     PivotRootDoesntExist = 0xE0000039,
299     PmemDeviceImageTooBig = 0xE000003A,
300     PmemDeviceNew = 0xE000003B,
301     ReadMemAvailable = 0xE000003C,
302     RegisterBalloon = 0xE000003D,
303     RegisterBlock = 0xE000003E,
304     RegisterGpu = 0xE000003F,
305     RegisterNet = 0xE0000040,
306     RegisterP9 = 0xE0000041,
307     RegisterRng = 0xE0000042,
308     RegisterWayland = 0xE0000043,
309     ReserveGpuMemory = 0xE0000044,
310     ReserveMemory = 0xE0000045,
311     ReservePmemMemory = 0xE0000046,
312     ResetTimer = 0xE0000047,
313     RngDeviceNew = 0xE0000048,
314     RunnableVcpu = 0xE0000049,
315     SettingSignalMask = 0xE000004B,
316     SpawnVcpu = 0xE000004D,
317     SysUtil = 0xE000004E,
318     Timer = 0xE000004F,
319     ValidateRawDescriptor = 0xE0000050,
320     VirtioPciDev = 0xE0000051,
321     WaitContextAdd = 0xE0000052,
322     WaitContextDelete = 0xE0000053,
323     WhpxSetupError = 0xE0000054,
324     VcpuFailEntry = 0xE0000055,
325     VcpuRunError = 0xE0000056,
326     VcpuShutdown = 0xE0000057,
327     VcpuSystemEvent = 0xE0000058,
328     WaitUntilRunnable = 0xE0000059,
329     CreateControlServer = 0xE000005A,
330     CreateTube = 0xE000005B,
331     UsbError = 0xE000005E,
332     GuestMemoryLayout = 0xE000005F,
333     CreateVm = 0xE0000060,
334     CreateGuestMemory = 0xE0000061,
335     CreateIrqChip = 0xE0000062,
336     SpawnIrqThread = 0xE0000063,
337     ConnectTube = 0xE0000064,
338     BalloonDeviceNew = 0xE0000065,
339     BalloonStats = 0xE0000066,
340     OpenCompositeFooterFile = 0xE0000068,
341     OpenCompositeHeaderFile = 0xE0000069,
342     OpenCompositeImageFile = 0xE0000070,
343     CreateCompositeDisk = 0xE0000071,
344     MissingControlTube = 0xE0000072,
345     TubeTransporterInit = 0xE0000073,
346     TubeFailure = 0xE0000074,
347     ProcessSpawnFailed = 0xE0000075,
348     LogFile = 0xE0000076,
349     CreateZeroFiller = 0xE0000077,
350     GenerateAcpi = 0xE0000078,
351     WaitContextWait = 0xE0000079,
352     SetSigintHandler = 0xE000007A,
353     KilledBySignal = 0xE000007B,
354     BrokerDeviceExitedTimeout = 0xE000007C,
355     BrokerMainExitedTimeout = 0xE000007D,
356     MemoryTooLarge = 0xE000007E,
357     BrokerMetricsExitedTimeout = 0xE000007F,
358     MetricsController = 0xE0000080,
359     SwiotlbTooLarge = 0xE0000081,
360     UserspaceVsockDeviceNew = 0xE0000082,
361     VhostUserBlockDeviceNew = 0xE0000083,
362     CrashReportingInit = 0xE0000084,
363     StartBackendDevice = 0xE0000085,
364     ConfigureHotPlugDevice = 0xE0000086,
365     InvalidHotPlugKey = 0xE0000087,
366     InvalidVfioPath = 0xE0000088,
367     NoHotPlugBus = 0xE0000089,
368     SandboxError = 0xE000008A,
369     Pstore = 0xE000008B,
370     ProcessInvariantsInit = 0xE000008C,
371     VirtioVhostUserDeviceNew = 0xE000008D,
372     CloneTube = 0xE000008E,
373     VhostUserGpuDeviceNew = 0xE000008F,
374     CreateAsyncDisk = 0xE0000090,
375     CreateDiskCheckAsyncOkError = 0xE0000091,
376     VhostUserNetDeviceNew = 0xE0000092,
377     BrokerSigtermTimeout = 0xE0000093,
378     SpawnVcpuMonitor = 0xE0000094,
379     NoDefaultHypervisor = 0xE0000095,
380     TscCalibrationFailed = 0xE0000096,
381     UnknownError = 0xE0000097,
382     CommonChildSetupError = 0xE0000098,
383     CreateImeThread = 0xE0000099,
384     OpenDiskImage = 0xE000009A,
385     VirtioSoundDeviceNew = 0xE000009B,
386     StartSpu = 0xE000009C,
387     SandboxCreateProcessAccessDenied = 0xE000009D,
388     SandboxCreateProcessElevationRequired = 0xE000009E,
389     BalloonSizeInvalid = 0xE000009F,
390     VhostUserSndDeviceNew = 0xE00000A0,
391     FailedToCreateControlServer = 0xE00000A1,
392 }
393 
394 impl From<Exit> for ExitCode {
from(exit: Exit) -> Self395     fn from(exit: Exit) -> Self {
396         exit as ExitCode
397     }
398 }
399 
400 // Bitfield masks for NTSTATUS & our extension of the format. See to_process_type_error for details.
401 mod bitmasks {
402     pub const FACILITY_FIELD_LOWER_MASK: u32 = u32::from_be_bytes([0x00, 0x3F, 0x00, 0x00]);
403     pub const EXTRA_DATA_FIELD_MASK: u32 = u32::from_be_bytes([0x0F, 0xC0, 0x00, 0x00]);
404     #[cfg(test)]
405     pub const EXTRA_DATA_FIELD_COMMAND_TYPE_MASK: u32 =
406         u32::from_be_bytes([0x07, 0xC0, 0x00, 0x00]);
407     pub const EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK: u32 =
408         u32::from_be_bytes([0x08, 0x00, 0x00, 0x00]);
409     pub const VENDOR_FIELD_MASK: u32 = u32::from_be_bytes([0x20, 0x00, 0x00, 0x00]);
410     pub const RESERVED_BIT_MASK: u32 = u32::from_be_bytes([0x10, 0x00, 0x00, 0x00]);
411     pub const COMMAND_TYPE_MASK: u32 = u32::from_be_bytes([0x00, 0x00, 0x00, 0x1F]);
412 }
413 use bitmasks::*;
414 
415 /// If you are looking for a fun interview question, you have come to the right place. To
416 /// understand the details of NTSTATUS, which you'll want to do before reading further, visit
417 /// <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781>.
418 ///
419 /// This function is unfortunately what happens when you only have six bits to store auxiliary
420 /// information, and have to fit in with an existing bitfield's schema.
421 ///
422 /// For reference, the format of the NTSTATUS field is as follows:
423 ///
424 /// | [31, 30] |      [29]         |      [28]       | [27, 16] | [15, 0] |
425 /// | Severity |  Customer/vendor  |  N (reserved)   | Facility |  Code   |
426 ///
427 /// This function packs bits in NTSTATUS results (generally what a Windows exit code should be).
428 /// There are three primary cases it deals with:
429 ///   1. Vendor specific exits. These are error codes we generate explicitly in crosvm. We will pack
430 ///      these codes with the lower 6 "facility" bits ([21, 16]) set so they can't collide with the
431 ///      other cases (this makes our facility value > FACILITY_MAXIMUM_VALUE). The top 6 bits of the
432 ///      facility field ([27, 22]) will be clear at this point.
433 ///
434 ///   2. Non vendor NTSTATUS exits. These are error codes which come from Windows. We flip the
435 ///      vendor bit on these because we're going to pack the facility field, and leaving it unset
436 ///      would cause us to violate the rule that if the vendor bit is unset, we shouldn't exceed
437 ///      FACILITY_MAXIMUM_VALUE in that field. The top six bits of the facility field ([27, 22])
438 ///      will be clear in this scenario because Windows won't exceed FACILITY_MAXIMUM_VALUE;
439 ///      however, if for some reason we see a non vendor code with any of those bits set, we will
440 ///      fall through to case #3.
441 ///
442 ///   3. Non NTSTATUS errors. We detect these with two heuristics: a) Reserved field is set. b) The
443 ///      facility field has exceeded the bottom six bits ([21, 16]).
444 ///
445 ///      For such cases, we pack as much of the error as we can into the lower 6 bits of the
446 ///      facility field, and code field (2 bytes). In this case, the most significant bit of the
447 ///      facility field is set.
448 ///
449 /// For all of the cases above, we pack the 5 bits following the most significant bit of the
450 /// 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) -> u32451 pub fn to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32 {
452     let is_vendor = error_code & VENDOR_FIELD_MASK != 0;
453 
454     // The reserved bit is always clear on a NTSTATUS code.
455     let is_reserved_bit_clear = error_code & RESERVED_BIT_MASK == 0;
456 
457     // The six most significant bits of the facility field are where we'll be storing our
458     // command type and whether we have a valid NTSTATUS error. If bits are already set there,
459     // it means this isn't a valid NTSTATUS code.
460     let is_extra_data_field_clear = error_code & EXTRA_DATA_FIELD_MASK == 0;
461 
462     let is_ntstatus = is_reserved_bit_clear && is_extra_data_field_clear;
463 
464     // We use the top bit of the facility field to store whether we ran out of space to pack
465     // the error. The next five bits are where we store the command type, so we'll shift them
466     // into the appropriate position here.
467     let command_type = (cmd_type as u32 & COMMAND_TYPE_MASK) << 22;
468 
469     match (is_ntstatus, is_vendor) {
470         // Valid vendor code
471         (true, true) => {
472             // Set all the lower facility bits, and attach the command type.
473             error_code | FACILITY_FIELD_LOWER_MASK | command_type
474         }
475 
476         // Valid non-vendor code
477         (true, false) => {
478             // Set the vendor bit and attach the command type.
479             error_code | VENDOR_FIELD_MASK | command_type
480         }
481 
482         // Not a valid NTSTATUS code.
483         _ => {
484             // Clear the extra data field, and set the the top bit of the facility field to
485             // signal that we didn't have enough space for the full error codes.
486             error_code & !EXTRA_DATA_FIELD_MASK | command_type | EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK
487         }
488     }
489 }
490 
491 #[cfg(test)]
492 mod tests {
493     use winapi::shared::ntstatus::STATUS_BAD_INITIAL_PC;
494 
495     use super::*;
496 
497     #[test]
test_to_process_type_error_ntstatus_vendor()498     fn test_to_process_type_error_ntstatus_vendor() {
499         let e = to_process_type_error(Exit::InvalidRunArgs as u32, ProcessType::Main);
500         assert_eq!(
501             e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
502             (ProcessType::Main as u32) << 22
503         );
504         assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
505 
506         // This is a valid NTSTATUS error.
507         assert_eq!(e & RESERVED_BIT_MASK, 0);
508 
509         // Check the actual crosvm error code contained in the NTSTATUS. We don't mutate the
510         // severity field, so we don't mask it off. We mask off the facility field entirely because
511         // that's where we stored the command type & NTSTATUS validity bit.
512         assert_eq!(e & 0xF000FFFF_u32, Exit::InvalidRunArgs as u32);
513     }
514 
515     #[test]
test_to_process_type_error_ntstatus_non_vendor()516     fn test_to_process_type_error_ntstatus_non_vendor() {
517         let e = to_process_type_error(STATUS_BAD_INITIAL_PC as u32, ProcessType::Main);
518         assert_eq!(
519             e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
520             (ProcessType::Main as u32) << 22
521         );
522         assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
523 
524         // This is a valid NTSTATUS error.
525         assert_eq!(e & RESERVED_BIT_MASK, 0);
526 
527         // Check the actual error code contained in the NTSTATUS. We mask off all our extra data
528         // fields and switch off the vendor bit to confirm the actual code was left alone.
529         assert_eq!(
530             e & !EXTRA_DATA_FIELD_MASK & !VENDOR_FIELD_MASK,
531             STATUS_BAD_INITIAL_PC as u32
532         );
533     }
534 
535     #[test]
test_to_process_type_error_wontfit_ntstatus()536     fn test_to_process_type_error_wontfit_ntstatus() {
537         let e = to_process_type_error(0xFFFFFFFF, ProcessType::Main);
538         assert_eq!(
539             e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
540             (ProcessType::Main as u32) << 22
541         );
542 
543         // -1 is not a valid NTSTATUS error.
544         assert_ne!(e & RESERVED_BIT_MASK, 0);
545 
546         // Overflow did occur.
547         assert_ne!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
548 
549         // Check that we left the rest of the bits (except for our command type field & overflow
550         // bit) in the exit code untouched.
551         assert_eq!(e & 0xF03FFFFF_u32, 0xF03FFFFF_u32);
552     }
553 }
554