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