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 for the current platform.
65    ///
66    /// On Linux, this is typically 2^31-1 (4,194,303).
67    /// On other platforms, this is `u32::MAX`.
68    #[cfg(target_os = "linux")]
69    pub const MAX: u32 = 4_194_303;
70
71    /// Maximum valid PID value for the current platform.
72    ///
73    /// On Linux, this is typically 2^31-1 (4,194,303).
74    /// On other platforms, this is `u32::MAX`.
75    #[cfg(not(target_os = "linux"))]
76    pub const MAX: u32 = u32::MAX;
77
78    /// Creates a new PID from a value.
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the value is zero or exceeds the maximum.
83    pub const fn new(value: u32) -> Result<Self, PidError> {
84        if value == 0 {
85            return Err(PidError::InvalidValue(value));
86        }
87        #[allow(clippy::absurd_extreme_comparisons)]
88        if value > Self::MAX {
89            return Err(PidError::TooLarge(value));
90        }
91        Ok(Self(value))
92    }
93
94    /// Creates a new PID without validation.
95    ///
96    /// This constructor skips validation and is useful when the caller has
97    /// already validated the input or obtained the PID from a trusted source.
98    ///
99    /// # Examples
100    ///
101    /// ```rust
102    /// use bare_types::sys::Pid;
103    ///
104    /// // Safe because 100 is a valid PID
105    /// let pid = Pid::new_unchecked(100);
106    /// assert_eq!(pid.as_u32(), 100);
107    /// ```
108    #[must_use]
109    #[inline]
110    pub const fn new_unchecked(value: u32) -> Self {
111        Self(value)
112    }
113
114    /// Returns the PID value.
115    #[must_use]
116    #[inline]
117    pub const fn as_u32(&self) -> u32 {
118        self.0
119    }
120
121    /// Returns `true` if this is the init process (PID 1).
122    #[must_use]
123    #[inline]
124    pub const fn is_init(&self) -> bool {
125        self.0 == 1
126    }
127
128    /// Returns `true` if this is likely a system process.
129    ///
130    /// System processes typically have low PIDs (below 100 on most systems).
131    #[must_use]
132    #[inline]
133    pub const fn is_system_process(&self) -> bool {
134        self.0 < 100
135    }
136}
137
138impl TryFrom<u32> for Pid {
139    type Error = PidError;
140
141    fn try_from(value: u32) -> Result<Self, Self::Error> {
142        Self::new(value)
143    }
144}
145
146impl TryFrom<i32> for Pid {
147    type Error = PidError;
148
149    fn try_from(value: i32) -> Result<Self, Self::Error> {
150        if value <= 0 {
151            #[allow(clippy::cast_sign_loss)]
152            return Err(PidError::InvalidValue(value as u32));
153        }
154        #[allow(clippy::cast_sign_loss)]
155        Self::new(value as u32)
156    }
157}
158
159impl FromStr for Pid {
160    type Err = PidError;
161
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
164        Self::new(value)
165    }
166}
167
168impl fmt::Display for Pid {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "{}", self.0)
171    }
172}
173
174#[cfg(feature = "arbitrary")]
175impl<'a> arbitrary::Arbitrary<'a> for Pid {
176    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
177        let value = u32::arbitrary(u)?;
178        #[allow(clippy::absurd_extreme_comparisons)]
179        if value == 0 || value > Self::MAX {
180            Ok(Self(1))
181        } else {
182            Ok(Self(value))
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_new_valid() {
193        let pid = Pid::new(1).unwrap();
194        assert_eq!(pid.as_u32(), 1);
195        let pid = Pid::new(100).unwrap();
196        assert_eq!(pid.as_u32(), 100);
197    }
198
199    #[test]
200    fn test_new_zero() {
201        assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
202    }
203
204    #[test]
205    #[cfg(target_os = "linux")]
206    fn test_new_too_large() {
207        assert!(matches!(Pid::new(4_194_304), Err(PidError::TooLarge(_))));
208    }
209
210    #[test]
211    #[cfg(not(target_os = "linux"))]
212    #[ignore = "On non-Linux platforms, MAX equals u32::MAX so no value can exceed it"]
213    fn test_new_too_large() {
214        assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
215    }
216
217    #[test]
218    fn test_try_from_u32() {
219        let pid: Pid = 100u32.try_into().unwrap();
220        assert_eq!(pid.as_u32(), 100);
221    }
222
223    #[test]
224    fn test_try_from_i32_valid() {
225        let pid: Pid = 100i32.try_into().unwrap();
226        assert_eq!(pid.as_u32(), 100);
227    }
228
229    #[test]
230    fn test_try_from_i32_zero() {
231        assert!(matches!(
232            TryInto::<Pid>::try_into(0i32),
233            Err(PidError::InvalidValue(0))
234        ));
235    }
236
237    #[test]
238    fn test_try_from_i32_negative() {
239        assert!(matches!(
240            TryInto::<Pid>::try_into(-1i32),
241            Err(PidError::InvalidValue(_))
242        ));
243    }
244
245    #[test]
246    fn test_from_str() {
247        let pid: Pid = "100".parse().unwrap();
248        assert_eq!(pid.as_u32(), 100);
249    }
250
251    #[test]
252    fn test_from_str_error() {
253        assert!("0".parse::<Pid>().is_err());
254        assert!("abc".parse::<Pid>().is_err());
255    }
256
257    #[test]
258    fn test_display() {
259        let pid = Pid::new(100).unwrap();
260        assert_eq!(format!("{}", pid), "100");
261    }
262
263    #[test]
264    fn test_is_init() {
265        let pid = Pid::new(1).unwrap();
266        assert!(pid.is_init());
267        let pid = Pid::new(100).unwrap();
268        assert!(!pid.is_init());
269    }
270
271    #[test]
272    fn test_is_system_process() {
273        let pid = Pid::new(1).unwrap();
274        assert!(pid.is_system_process());
275        let pid = Pid::new(50).unwrap();
276        assert!(pid.is_system_process());
277        let pid = Pid::new(100).unwrap();
278        assert!(!pid.is_system_process());
279    }
280
281    #[test]
282    fn test_clone() {
283        let pid = Pid::new(100).unwrap();
284        let pid2 = pid.clone();
285        assert_eq!(pid, pid2);
286    }
287
288    #[test]
289    fn test_equality() {
290        let p1 = Pid::new(100).unwrap();
291        let p2 = Pid::new(100).unwrap();
292        let p3 = Pid::new(200).unwrap();
293        assert_eq!(p1, p2);
294        assert_ne!(p1, p3);
295    }
296
297    #[test]
298    fn test_ordering() {
299        let p1 = Pid::new(100).unwrap();
300        let p2 = Pid::new(200).unwrap();
301        assert!(p1 < p2);
302        assert!(p2 > p1);
303    }
304
305    #[test]
306    fn test_new_unchecked() {
307        let pid = Pid::new_unchecked(100);
308        assert_eq!(pid.as_u32(), 100);
309    }
310}