use std::ffi::{OsStr, OsString};
use bitflags::bitflags;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ProcessIdentity {
pid: u32,
creation_time_100ns_since_1601: u64,
}
impl ProcessIdentity {
pub fn from_raw_parts(pid: u32, creation_time_100ns_since_1601: u64) -> crate::Result<Self> {
if pid == 0 || pid == u32::MAX {
return Err(crate::Error::new(
crate::ErrorKind::InvalidInput,
None,
"a process identity PID must be neither zero nor the native invalid sentinel",
));
}
Ok(Self {
pid,
creation_time_100ns_since_1601,
})
}
pub(crate) const fn from_raw_parts_unchecked(
pid: u32,
creation_time_100ns_since_1601: u64,
) -> Self {
Self {
pid,
creation_time_100ns_since_1601,
}
}
#[must_use]
pub const fn pid(self) -> u32 {
self.pid
}
#[must_use]
pub const fn creation_time_100ns_since_1601(self) -> u64 {
self.creation_time_100ns_since_1601
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ApplicationType {
Unknown,
MainWindow,
OtherWindow,
Service,
Explorer,
Console,
Critical,
Unrecognized(i32),
}
impl ApplicationType {
pub(crate) const fn from_raw(value: i32) -> Self {
match value {
0 => Self::Unknown,
1 => Self::MainWindow,
2 => Self::OtherWindow,
3 => Self::Service,
4 => Self::Explorer,
5 => Self::Console,
1000 => Self::Critical,
other => Self::Unrecognized(other),
}
}
#[must_use]
pub const fn raw_value(self) -> i32 {
match self {
Self::Unknown => 0,
Self::MainWindow => 1,
Self::OtherWindow => 2,
Self::Service => 3,
Self::Explorer => 4,
Self::Console => 5,
Self::Critical => 1000,
Self::Unrecognized(value) => value,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ApplicationStatus: u32 {
const RUNNING = 0x01;
const STOPPED = 0x02;
const STOPPED_OTHER = 0x04;
const RESTARTED = 0x08;
const ERROR_ON_STOP = 0x10;
const ERROR_ON_RESTART = 0x20;
const SHUTDOWN_MASKED = 0x40;
const RESTART_MASKED = 0x80;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RebootReasons: u32 {
const PERMISSION_DENIED = 0x01;
const SESSION_MISMATCH = 0x02;
const CRITICAL_PROCESS = 0x04;
const CRITICAL_SERVICE = 0x08;
const DETECTED_SELF = 0x10;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AffectedApplication {
pub(crate) display_name: OsString,
pub(crate) service_name: Option<OsString>,
pub(crate) application_type: ApplicationType,
pub(crate) status: ApplicationStatus,
pub(crate) restartable: bool,
pub(crate) process: Option<ProcessIdentity>,
pub(crate) terminal_session_id: Option<u32>,
}
impl AffectedApplication {
#[must_use]
pub fn display_name(&self) -> &OsStr {
&self.display_name
}
#[must_use]
pub fn service_name(&self) -> Option<&OsStr> {
self.service_name.as_deref()
}
#[must_use]
pub const fn application_type(&self) -> ApplicationType {
self.application_type
}
#[must_use]
pub const fn status(&self) -> ApplicationStatus {
self.status
}
#[must_use]
pub const fn is_restartable(&self) -> bool {
self.restartable
}
#[must_use]
pub const fn process(&self) -> Option<ProcessIdentity> {
self.process
}
#[must_use]
pub const fn terminal_session_id(&self) -> Option<u32> {
self.terminal_session_id
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AffectedApplications {
pub(crate) applications: Vec<AffectedApplication>,
pub(crate) reboot_reasons: RebootReasons,
}
impl AffectedApplications {
#[must_use]
pub fn applications(&self) -> &[AffectedApplication] {
&self.applications
}
#[must_use]
pub const fn reboot_reasons(&self) -> RebootReasons {
self.reboot_reasons
}
pub fn iter(&self) -> std::slice::Iter<'_, AffectedApplication> {
self.applications.iter()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.applications.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.applications.len()
}
}
impl IntoIterator for AffectedApplications {
type Item = AffectedApplication;
type IntoIter = std::vec::IntoIter<AffectedApplication>;
fn into_iter(self) -> Self::IntoIter {
self.applications.into_iter()
}
}
impl<'a> IntoIterator for &'a AffectedApplications {
type Item = &'a AffectedApplication;
type IntoIter = std::slice::Iter<'a, AffectedApplication>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn application_type_distinguishes_unknown_and_future_values() {
let values = [0, 1, 2, 3, 4, 5, 1000, 77];
for value in values {
assert_eq!(ApplicationType::from_raw(value).raw_value(), value);
}
assert_eq!(ApplicationType::from_raw(0), ApplicationType::Unknown);
assert_eq!(
ApplicationType::from_raw(77),
ApplicationType::Unrecognized(77)
);
}
#[test]
fn status_and_reboot_reasons_retain_unknown_bits() {
let status = ApplicationStatus::from_bits_retain(0x4000_0001);
assert!(status.contains(ApplicationStatus::RUNNING));
assert_eq!(status.bits(), 0x4000_0001);
let reasons = RebootReasons::from_bits_retain(0x8000_0002);
assert!(reasons.contains(RebootReasons::SESSION_MISMATCH));
assert_eq!(reasons.bits(), 0x8000_0002);
}
#[test]
fn affected_report_accessors_and_iterators_are_reusable() {
let process = ProcessIdentity::from_raw_parts(42, 99).unwrap();
assert_eq!(process.pid(), 42);
assert_eq!(process.creation_time_100ns_since_1601(), 99);
let application = AffectedApplication {
display_name: OsString::from("display"),
service_name: Some(OsString::from("service")),
application_type: ApplicationType::Service,
status: ApplicationStatus::RUNNING | ApplicationStatus::RESTARTED,
restartable: true,
process: Some(process),
terminal_session_id: Some(7),
};
assert_eq!(application.display_name(), OsStr::new("display"));
assert_eq!(application.service_name(), Some(OsStr::new("service")));
assert_eq!(application.application_type(), ApplicationType::Service);
assert!(application.status().contains(ApplicationStatus::RUNNING));
assert!(application.is_restartable());
assert_eq!(application.process(), Some(process));
assert_eq!(application.terminal_session_id(), Some(7));
let report = AffectedApplications {
applications: vec![application],
reboot_reasons: RebootReasons::DETECTED_SELF,
};
assert_eq!(report.len(), 1);
assert!(!report.is_empty());
assert_eq!(report.reboot_reasons(), RebootReasons::DETECTED_SELF);
assert_eq!(report.iter().count(), 1);
assert_eq!((&report).into_iter().count(), 1);
assert_eq!(report.into_iter().count(), 1);
}
#[test]
fn process_identity_rejects_native_invalid_pids() {
assert!(ProcessIdentity::from_raw_parts(0, 1).is_err());
assert!(ProcessIdentity::from_raw_parts(u32::MAX, 1).is_err());
assert!(ProcessIdentity::from_raw_parts(1, 1).is_ok());
}
}