use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use anyhow::Context;
use win_util::ProcessType;
pub type ExitCode = i32;
#[derive(Debug)]
pub struct ExitCodeWrapper(pub ExitCode);
impl Display for ExitCodeWrapper {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "exit code: {} = 0x{:08x}", self.0, self.0)
}
}
pub trait ExitContext<T, E> {
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>;
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static;
fn 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;
}
impl<T, E> ExitContext<T, E> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>,
{
self.context(ExitCodeWrapper(exit_code.into()))
}
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static,
{
self.context(ExitCodeWrapper(exit_code.into()))
.context(context)
}
fn 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,
{
self.context(ExitCodeWrapper(exit_code.into()))
.with_context(f)
}
}
pub trait ExitContextAnyhow<T> {
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>;
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static;
fn 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;
fn to_exit_code(&self) -> Option<ExitCode>;
}
impl<T> ExitContextAnyhow<T> for anyhow::Result<T> {
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>,
{
self.context(ExitCodeWrapper(exit_code.into()))
}
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static,
{
self.context(ExitCodeWrapper(exit_code.into()))
.context(context)
}
fn 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,
{
self.context(ExitCodeWrapper(exit_code.into()))
.with_context(f)
}
fn to_exit_code(&self) -> Option<ExitCode> {
self.as_ref()
.err()
.and_then(|e| e.downcast_ref::<ExitCodeWrapper>())
.map(|w| w.0)
}
}
pub trait ExitContextOption<T> {
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>;
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static;
fn 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;
}
impl<T> ExitContextOption<T> for std::option::Option<T> {
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
where
X: Into<ExitCode>,
{
self.context(ExitCodeWrapper(exit_code.into()))
}
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
where
X: Into<ExitCode>,
C: Display + Send + Sync + 'static,
{
self.context(ExitCodeWrapper(exit_code.into()))
.context(context)
}
fn 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,
{
self.context(ExitCodeWrapper(exit_code.into()))
.with_context(f)
}
}
#[macro_export]
macro_rules! bail_exit_code {
($exit_code:literal, $msg:literal $(,)?) => {
return Err(anyhow!($msg)).exit_code($exit_code)
};
($exit_code:literal, $err:expr $(,)?) => {
return Err(anyhow!($err)).exit_code($exit_code)
};
($exit_code:literal, $fmt:expr, $($arg:tt)*) => {
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
};
($exit_code:expr, $msg:literal $(,)?) => {
return Err(anyhow!($msg)).exit_code($exit_code)
};
($exit_code:expr, $err:expr $(,)?) => {
return Err(anyhow!($err)).exit_code($exit_code)
};
($exit_code:expr, $fmt:expr, $($arg:tt)*) => {
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
};
}
#[macro_export]
macro_rules! ensure_exit_code {
($cond:expr, $exit_code:literal $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
}
};
($cond:expr, $exit_code:literal, $msg:literal $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, $msg);
}
};
($cond:expr, $exit_code:literal, $err:expr $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, $err);
}
};
($cond:expr, $exit_code:literal, $fmt:expr, $($arg:tt)*) => {
if !$cond {
bail_exit_code!($exit_code, $fmt, $($arg)*);
}
};
($cond:expr, $exit_code:expr $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
}
};
($cond:expr, $exit_code:expr, $msg:literal $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, $msg);
}
};
($cond:expr, $exit_code:expr, $err:expr $(,)?) => {
if !$cond {
bail_exit_code!($exit_code, $err);
}
};
($cond:expr, $exit_code:expr, $fmt:expr, $($arg:tt)*) => {
if !$cond {
bail_exit_code!($exit_code, $fmt, $($arg)*);
}
};
}
#[allow(clippy::enum_clike_unportable_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Exit {
AddGpuDeviceMemory = 0xE0000001,
AddIrqChipVcpu = 0xE0000002,
AddPmemDeviceMemory = 0xE0000003,
AllocateGpuDeviceAddress = 0xE0000004,
AllocatePmemDeviceAddress = 0xE0000005,
BlockDeviceNew = 0xE0000006,
BuildVm = 0xE0000007,
ChownTpmStorage = 0xE0000008,
CloneEvent = 0xE000000A,
CloneVcpu = 0xE000000B,
ConfigureVcpu = 0xE000000C,
CreateConsole = 0xE000000E,
CreateDisk = 0xE000000F,
CreateEvent = 0xE0000010,
CreateGralloc = 0xE0000011,
CreateGvm = 0xE0000012,
CreateSocket = 0xE0000013,
CreateTapDevice = 0xE0000014,
CreateTimer = 0xE0000015,
CreateTpmStorage = 0xE0000016,
CreateVcpu = 0xE0000017,
CreateWaitContext = 0xE0000018,
Disk = 0xE0000019,
DiskImageLock = 0xE000001A,
DropCapabilities = 0xE000001B,
EventDeviceSetup = 0xE000001C,
EnableHighResTimer = 0xE000001D,
HandleCreateQcowError = 0xE000001E,
HandleVmRequestError = 0xE0000020,
InitSysLogError = 0xE0000021,
InputDeviceNew = 0xE0000022,
InputEventsOpen = 0xE0000023,
InvalidRunArgs = 0xE0000025,
InvalidSubCommand = 0xE0000026,
InvalidSubCommandArgs = 0xE0000027,
InvalidWaylandPath = 0xE0000028,
LoadKernel = 0xE0000029,
MissingCommandArg = 0xE0000030,
ModifyBatteryError = 0xE0000031,
NetDeviceNew = 0xE0000032,
OpenAcpiTable = 0xE0000033,
OpenAndroidFstab = 0xE0000034,
OpenBios = 0xE0000035,
OpenInitrd = 0xE0000036,
OpenKernel = 0xE0000037,
OpenVinput = 0xE0000038,
PivotRootDoesntExist = 0xE0000039,
PmemDeviceImageTooBig = 0xE000003A,
PmemDeviceNew = 0xE000003B,
ReadMemAvailable = 0xE000003C,
RegisterBalloon = 0xE000003D,
RegisterBlock = 0xE000003E,
RegisterGpu = 0xE000003F,
RegisterNet = 0xE0000040,
RegisterP9 = 0xE0000041,
RegisterRng = 0xE0000042,
RegisterWayland = 0xE0000043,
ReserveGpuMemory = 0xE0000044,
ReserveMemory = 0xE0000045,
ReservePmemMemory = 0xE0000046,
ResetTimer = 0xE0000047,
RngDeviceNew = 0xE0000048,
RunnableVcpu = 0xE0000049,
SettingSignalMask = 0xE000004B,
SpawnVcpu = 0xE000004D,
SysUtil = 0xE000004E,
Timer = 0xE000004F,
ValidateRawDescriptor = 0xE0000050,
VirtioPciDev = 0xE0000051,
WaitContextAdd = 0xE0000052,
WaitContextDelete = 0xE0000053,
WhpxSetupError = 0xE0000054,
VcpuFailEntry = 0xE0000055,
VcpuRunError = 0xE0000056,
VcpuShutdown = 0xE0000057,
VcpuSystemEvent = 0xE0000058,
WaitUntilRunnable = 0xE0000059,
CreateControlServer = 0xE000005A,
CreateTube = 0xE000005B,
UsbError = 0xE000005E,
GuestMemoryLayout = 0xE000005F,
CreateVm = 0xE0000060,
CreateGuestMemory = 0xE0000061,
CreateIrqChip = 0xE0000062,
SpawnIrqThread = 0xE0000063,
ConnectTube = 0xE0000064,
BalloonDeviceNew = 0xE0000065,
BalloonStats = 0xE0000066,
OpenCompositeFooterFile = 0xE0000068,
OpenCompositeHeaderFile = 0xE0000069,
OpenCompositeImageFile = 0xE0000070,
CreateCompositeDisk = 0xE0000071,
MissingControlTube = 0xE0000072,
TubeTransporterInit = 0xE0000073,
TubeFailure = 0xE0000074,
ProcessSpawnFailed = 0xE0000075,
LogFile = 0xE0000076,
CreateZeroFiller = 0xE0000077,
GenerateAcpi = 0xE0000078,
WaitContextWait = 0xE0000079,
SetSigintHandler = 0xE000007A,
KilledBySignal = 0xE000007B,
BrokerDeviceExitedTimeout = 0xE000007C,
BrokerMainExitedTimeout = 0xE000007D,
MemoryTooLarge = 0xE000007E,
BrokerMetricsExitedTimeout = 0xE000007F,
MetricsController = 0xE0000080,
SwiotlbTooLarge = 0xE0000081,
UserspaceVsockDeviceNew = 0xE0000082,
VhostUserBlockDeviceNew = 0xE0000083,
CrashReportingInit = 0xE0000084,
StartBackendDevice = 0xE0000085,
ConfigureHotPlugDevice = 0xE0000086,
InvalidHotPlugKey = 0xE0000087,
InvalidVfioPath = 0xE0000088,
NoHotPlugBus = 0xE0000089,
SandboxError = 0xE000008A,
Pstore = 0xE000008B,
ProcessInvariantsInit = 0xE000008C,
VirtioVhostUserDeviceNew = 0xE000008D,
CloneTube = 0xE000008E,
VhostUserGpuDeviceNew = 0xE000008F,
CreateAsyncDisk = 0xE0000090,
CreateDiskCheckAsyncOkError = 0xE0000091,
VhostUserNetDeviceNew = 0xE0000092,
BrokerSigtermTimeout = 0xE0000093,
SpawnVcpuMonitor = 0xE0000094,
NoDefaultHypervisor = 0xE0000095,
TscCalibrationFailed = 0xE0000096,
UnknownError = 0xE0000097,
CommonChildSetupError = 0xE0000098,
CreateImeThread = 0xE0000099,
OpenDiskImage = 0xE000009A,
VirtioSoundDeviceNew = 0xE000009B,
StartSpu = 0xE000009C,
SandboxCreateProcessAccessDenied = 0xE000009D,
SandboxCreateProcessElevationRequired = 0xE000009E,
BalloonSizeInvalid = 0xE000009F,
VhostUserSndDeviceNew = 0xE00000A0,
FailedToCreateControlServer = 0xE00000A1,
}
impl From<Exit> for ExitCode {
fn from(exit: Exit) -> Self {
exit as ExitCode
}
}
mod bitmasks {
pub const FACILITY_FIELD_LOWER_MASK: u32 = u32::from_be_bytes([0x00, 0x3F, 0x00, 0x00]);
pub const EXTRA_DATA_FIELD_MASK: u32 = u32::from_be_bytes([0x0F, 0xC0, 0x00, 0x00]);
#[cfg(test)]
pub const EXTRA_DATA_FIELD_COMMAND_TYPE_MASK: u32 =
u32::from_be_bytes([0x07, 0xC0, 0x00, 0x00]);
pub const EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK: u32 =
u32::from_be_bytes([0x08, 0x00, 0x00, 0x00]);
pub const VENDOR_FIELD_MASK: u32 = u32::from_be_bytes([0x20, 0x00, 0x00, 0x00]);
pub const RESERVED_BIT_MASK: u32 = u32::from_be_bytes([0x10, 0x00, 0x00, 0x00]);
pub const COMMAND_TYPE_MASK: u32 = u32::from_be_bytes([0x00, 0x00, 0x00, 0x1F]);
}
use bitmasks::*;
pub fn to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32 {
let is_vendor = error_code & VENDOR_FIELD_MASK != 0;
let is_reserved_bit_clear = error_code & RESERVED_BIT_MASK == 0;
let is_extra_data_field_clear = error_code & EXTRA_DATA_FIELD_MASK == 0;
let is_ntstatus = is_reserved_bit_clear && is_extra_data_field_clear;
let command_type = (cmd_type as u32 & COMMAND_TYPE_MASK) << 22;
match (is_ntstatus, is_vendor) {
(true, true) => {
error_code | FACILITY_FIELD_LOWER_MASK | command_type
}
(true, false) => {
error_code | VENDOR_FIELD_MASK | command_type
}
_ => {
error_code & !EXTRA_DATA_FIELD_MASK | command_type | EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK
}
}
}
#[cfg(test)]
mod tests {
use winapi::shared::ntstatus::STATUS_BAD_INITIAL_PC;
use super::*;
#[test]
fn test_to_process_type_error_ntstatus_vendor() {
let e = to_process_type_error(Exit::InvalidRunArgs as u32, ProcessType::Main);
assert_eq!(
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
(ProcessType::Main as u32) << 22
);
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
assert_eq!(e & RESERVED_BIT_MASK, 0);
assert_eq!(e & 0xF000FFFF_u32, Exit::InvalidRunArgs as u32);
}
#[test]
fn test_to_process_type_error_ntstatus_non_vendor() {
let e = to_process_type_error(STATUS_BAD_INITIAL_PC as u32, ProcessType::Main);
assert_eq!(
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
(ProcessType::Main as u32) << 22
);
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
assert_eq!(e & RESERVED_BIT_MASK, 0);
assert_eq!(
e & !EXTRA_DATA_FIELD_MASK & !VENDOR_FIELD_MASK,
STATUS_BAD_INITIAL_PC as u32
);
}
#[test]
fn test_to_process_type_error_wontfit_ntstatus() {
let e = to_process_type_error(0xFFFFFFFF, ProcessType::Main);
assert_eq!(
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
(ProcessType::Main as u32) << 22
);
assert_ne!(e & RESERVED_BIT_MASK, 0);
assert_ne!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
assert_eq!(e & 0xF03FFFFF_u32, 0xF03FFFFF_u32);
}
}