Skip to main content

bare_types/sys/
pid.rs

1//! Process ID type for system information.
2//!
3//! This module provides a type-safe abstraction for process IDs,
4//! ensuring valid PID values.
5
6use core::fmt;
7use core::str::FromStr;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Error type for PID parsing.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[non_exhaustive]
16pub enum PidError {
17    /// Invalid PID value (must be positive)
18    ///
19    /// Process IDs must be positive integers (greater than 0).
20    /// PID 0 is typically reserved for the kernel or scheduler.
21    /// This variant contains the invalid value.
22    InvalidValue(u32),
23    /// PID too large (platform-dependent)
24    ///
25    /// The provided PID exceeds the maximum valid value for the platform.
26    /// On Linux, the maximum is typically 2^31-1 (4,194,303).
27    /// On other platforms, it may be `u32::MAX`.
28    /// This variant contains the invalid value.
29    TooLarge(u32),
30}
31
32impl fmt::Display for PidError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::InvalidValue(v) => write!(f, "invalid PID value: {v} (must be positive)"),
36            Self::TooLarge(v) => write!(f, "PID too large: {v}"),
37        }
38    }
39}
40
41#[cfg(feature = "std")]
42impl std::error::Error for PidError {}
43
44/// Process ID.
45///
46/// This type provides type-safe process IDs. It uses the newtype pattern
47/// with `#[repr(transparent)]` for zero-cost abstraction.
48///
49/// # Platform Differences
50///
51/// - **Linux/Unix**: PIDs are typically 32-bit unsigned integers
52/// - **Windows**: PIDs are typically 32-bit unsigned integers
53///
54/// # Special Values
55///
56/// - `0`: Typically refers to the kernel or scheduler (may be invalid on some systems)
57/// - `1`: Typically the init process (or systemd on modern Linux)
58#[repr(transparent)]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct Pid(u32);
62
63impl Pid {
64    /// Maximum valid PID value (platform-dependent, typically 2^31-1 or 2^32-1)
65    #[cfg(target_os = "linux")]
66    pub const MAX: u32 = 4_194_303;
67
68    #[cfg(not(target_os = "linux"))]
69    pub const MAX: u32 = u32::MAX;
70
71    /// Creates a new PID from a value.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if the value is zero or exceeds the maximum.
76    pub const fn new(value: u32) -> Result<Self, PidError> {
77        if value == 0 {
78            return Err(PidError::InvalidValue(value));
79        }
80        if value > Self::MAX {
81            return Err(PidError::TooLarge(value));
82        }
83        Ok(Self(value))
84    }
85
86    /// Creates a new PID without validation.
87    ///
88    /// # Safety
89    ///
90    /// The caller must ensure the value is a valid PID.
91    #[must_use]
92    #[inline]
93    pub const fn new_unchecked(value: u32) -> Self {
94        Self(value)
95    }
96
97    /// Returns the PID value.
98    #[must_use]
99    #[inline]
100    pub const fn as_u32(&self) -> u32 {
101        self.0
102    }
103
104    /// Returns `true` if this is the init process (PID 1).
105    #[must_use]
106    #[inline]
107    pub const fn is_init(&self) -> bool {
108        self.0 == 1
109    }
110
111    /// Returns `true` if this is likely a system process.
112    ///
113    /// System processes typically have low PIDs (below 100 on most systems).
114    #[must_use]
115    #[inline]
116    pub const fn is_system_process(&self) -> bool {
117        self.0 < 100
118    }
119}
120
121impl TryFrom<u32> for Pid {
122    type Error = PidError;
123
124    fn try_from(value: u32) -> Result<Self, Self::Error> {
125        Self::new(value)
126    }
127}
128
129impl TryFrom<i32> for Pid {
130    type Error = PidError;
131
132    fn try_from(value: i32) -> Result<Self, Self::Error> {
133        if value <= 0 {
134            #[allow(clippy::cast_sign_loss)]
135            return Err(PidError::InvalidValue(value as u32));
136        }
137        #[allow(clippy::cast_sign_loss)]
138        Self::new(value as u32)
139    }
140}
141
142impl FromStr for Pid {
143    type Err = PidError;
144
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
147        Self::new(value)
148    }
149}
150
151impl fmt::Display for Pid {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "{}", self.0)
154    }
155}
156
157#[cfg(feature = "arbitrary")]
158impl<'a> arbitrary::Arbitrary<'a> for Pid {
159    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
160        let value = u32::arbitrary(u)?;
161        if value == 0 || value > Self::MAX {
162            Ok(Self(1))
163        } else {
164            Ok(Self(value))
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use core::convert::TryInto;
173
174    #[test]
175    fn test_new_valid() {
176        let pid = Pid::new(1).unwrap();
177        assert_eq!(pid.as_u32(), 1);
178        let pid = Pid::new(100).unwrap();
179        assert_eq!(pid.as_u32(), 100);
180    }
181
182    #[test]
183    fn test_new_zero() {
184        assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
185    }
186
187    #[test]
188    fn test_new_too_large() {
189        assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
190    }
191
192    #[test]
193    fn test_try_from_u32() {
194        let pid: Pid = 100u32.try_into().unwrap();
195        assert_eq!(pid.as_u32(), 100);
196    }
197
198    #[test]
199    fn test_try_from_i32_valid() {
200        let pid: Pid = 100i32.try_into().unwrap();
201        assert_eq!(pid.as_u32(), 100);
202    }
203
204    #[test]
205    fn test_try_from_i32_zero() {
206        assert!(matches!(
207            TryInto::<Pid>::try_into(0i32),
208            Err(PidError::InvalidValue(0))
209        ));
210    }
211
212    #[test]
213    fn test_try_from_i32_negative() {
214        assert!(matches!(
215            TryInto::<Pid>::try_into(-1i32),
216            Err(PidError::InvalidValue(_))
217        ));
218    }
219
220    #[test]
221    fn test_from_str() {
222        let pid: Pid = "100".parse().unwrap();
223        assert_eq!(pid.as_u32(), 100);
224    }
225
226    #[test]
227    fn test_from_str_error() {
228        assert!("0".parse::<Pid>().is_err());
229        assert!("abc".parse::<Pid>().is_err());
230    }
231
232    #[test]
233    fn test_display() {
234        let pid = Pid::new(100).unwrap();
235        assert_eq!(format!("{}", pid), "100");
236    }
237
238    #[test]
239    fn test_is_init() {
240        let pid = Pid::new(1).unwrap();
241        assert!(pid.is_init());
242        let pid = Pid::new(100).unwrap();
243        assert!(!pid.is_init());
244    }
245
246    #[test]
247    fn test_is_system_process() {
248        let pid = Pid::new(1).unwrap();
249        assert!(pid.is_system_process());
250        let pid = Pid::new(50).unwrap();
251        assert!(pid.is_system_process());
252        let pid = Pid::new(100).unwrap();
253        assert!(!pid.is_system_process());
254    }
255
256    #[test]
257    fn test_clone() {
258        let pid = Pid::new(100).unwrap();
259        let pid2 = pid.clone();
260        assert_eq!(pid, pid2);
261    }
262
263    #[test]
264    fn test_equality() {
265        let p1 = Pid::new(100).unwrap();
266        let p2 = Pid::new(100).unwrap();
267        let p3 = Pid::new(200).unwrap();
268        assert_eq!(p1, p2);
269        assert_ne!(p1, p3);
270    }
271
272    #[test]
273    fn test_ordering() {
274        let p1 = Pid::new(100).unwrap();
275        let p2 = Pid::new(200).unwrap();
276        assert!(p1 < p2);
277        assert!(p2 > p1);
278    }
279
280    #[test]
281    fn test_new_unchecked() {
282        let pid = Pid::new_unchecked(100);
283        assert_eq!(pid.as_u32(), 100);
284    }
285}