microsandbox_core/vm/
rlimit.rs

1use crate::MicrosandboxError;
2use getset::Getters;
3use serde::{Deserialize, Serialize};
4use std::{convert::TryFrom, fmt, str::FromStr};
5
6//--------------------------------------------------------------------------------------------------
7// Types
8//--------------------------------------------------------------------------------------------------
9
10/// Represents the available Linux resource limits.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u32)]
13#[allow(non_camel_case_types)]
14pub enum LinuxRLimitResource {
15    /// CPU time in seconds
16    RLIMIT_CPU = 0,
17
18    /// Maximum size of files created by the process
19    RLIMIT_FSIZE = 1,
20
21    /// Maximum size of the data segment
22    RLIMIT_DATA = 2,
23
24    /// Maximum size of the stack segment
25    RLIMIT_STACK = 3,
26
27    /// Maximum size of core dumps
28    RLIMIT_CORE = 4,
29
30    /// Maximum resident set size (not enforced on Linux)
31    RLIMIT_RSS = 5,
32
33    /// Maximum number of processes
34    RLIMIT_NPROC = 6,
35
36    /// Maximum number of open file descriptors
37    RLIMIT_NOFILE = 7,
38
39    /// Maximum locked memory size
40    RLIMIT_MEMLOCK = 8,
41
42    /// Maximum size of the address space
43    RLIMIT_AS = 9,
44
45    /// Maximum number of file locks
46    RLIMIT_LOCKS = 10,
47
48    /// Maximum number of signals that can be queued
49    RLIMIT_SIGPENDING = 11,
50
51    /// Maximum number of bytes in POSIX message queues
52    RLIMIT_MSGQUEUE = 12,
53
54    /// Maximum nice priority
55    RLIMIT_NICE = 13,
56
57    /// Maximum real-time priority
58    RLIMIT_RTPRIO = 14,
59
60    /// Maximum seconds to sleep in real time
61    RLIMIT_RTTIME = 15,
62}
63
64/// Represents a resource limit for a Linux process.
65///
66/// This struct encapsulates a resource type and its corresponding soft and hard limits.
67/// The soft limit is the value that the kernel enforces for the corresponding resource.
68/// The hard limit acts as a ceiling for the soft limit.
69///
70/// ## Examples
71///
72/// ```
73/// use microsandbox_core::vm::{LinuxRlimit, LinuxRLimitResource};
74///
75/// // Create a new resource limit for CPU time
76/// let cpu_limit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
77///
78/// assert_eq!(cpu_limit.get_resource(), &LinuxRLimitResource::RLIMIT_CPU);
79/// assert_eq!(cpu_limit.get_soft(), &10);
80/// assert_eq!(cpu_limit.get_hard(), &20);
81///
82/// // Parse a resource limit from a string
83/// let nofile_limit: LinuxRlimit = "RLIMIT_NOFILE=1000:2000".parse().unwrap();
84///
85/// assert_eq!(nofile_limit.get_resource(), &LinuxRLimitResource::RLIMIT_NOFILE);
86/// assert_eq!(nofile_limit.get_soft(), &1000);
87/// assert_eq!(nofile_limit.get_hard(), &2000);
88/// ```
89#[derive(Debug, Clone, PartialEq, Eq, Getters)]
90#[getset(get = "pub with_prefix")]
91pub struct LinuxRlimit {
92    /// The resource to limit.
93    resource: LinuxRLimitResource,
94
95    /// The soft limit of the resource.
96    ///
97    /// This is the value that the kernel enforces for the corresponding resource.
98    soft: u64,
99
100    /// The hard limit of the resource.
101    ///
102    /// This acts as a ceiling for the soft limit.
103    hard: u64,
104}
105
106//--------------------------------------------------------------------------------------------------
107// Methods
108//--------------------------------------------------------------------------------------------------
109
110impl LinuxRLimitResource {
111    /// Get the corresponding enum integer value
112    pub fn as_int(&self) -> u32 {
113        *self as u32
114    }
115}
116
117impl LinuxRlimit {
118    /// Creates a new `LinuxRlimit` instance with the specified resource, soft limit, and hard limit.
119    ///
120    /// # Arguments
121    ///
122    /// * `resource` - The resource type to limit.
123    /// * `soft` - The soft limit value.
124    /// * `hard` - The hard limit value.
125    ///
126    /// ## Examples
127    ///
128    /// ```
129    /// use microsandbox_core::vm::{LinuxRlimit, LinuxRLimitResource};
130    ///
131    /// let cpu_limit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
132    /// assert_eq!(cpu_limit.get_resource(), &LinuxRLimitResource::RLIMIT_CPU);
133    /// assert_eq!(cpu_limit.get_soft(), &10);
134    /// assert_eq!(cpu_limit.get_hard(), &20);
135    /// ```
136    pub fn new(resource: LinuxRLimitResource, soft: u64, hard: u64) -> Self {
137        Self {
138            resource,
139            soft,
140            hard,
141        }
142    }
143}
144
145//--------------------------------------------------------------------------------------------------
146// Trait Implementations
147//--------------------------------------------------------------------------------------------------
148
149impl TryFrom<u32> for LinuxRLimitResource {
150    type Error = MicrosandboxError;
151
152    fn try_from(value: u32) -> Result<Self, Self::Error> {
153        match value {
154            0 => Ok(Self::RLIMIT_CPU),
155            1 => Ok(Self::RLIMIT_FSIZE),
156            2 => Ok(Self::RLIMIT_DATA),
157            3 => Ok(Self::RLIMIT_STACK),
158            4 => Ok(Self::RLIMIT_CORE),
159            5 => Ok(Self::RLIMIT_RSS),
160            6 => Ok(Self::RLIMIT_NPROC),
161            7 => Ok(Self::RLIMIT_NOFILE),
162            8 => Ok(Self::RLIMIT_MEMLOCK),
163            9 => Ok(Self::RLIMIT_AS),
164            10 => Ok(Self::RLIMIT_LOCKS),
165            11 => Ok(Self::RLIMIT_SIGPENDING),
166            12 => Ok(Self::RLIMIT_MSGQUEUE),
167            13 => Ok(Self::RLIMIT_NICE),
168            14 => Ok(Self::RLIMIT_RTPRIO),
169            15 => Ok(Self::RLIMIT_RTTIME),
170            _ => Err(MicrosandboxError::InvalidRLimitResource(value.to_string())),
171        }
172    }
173}
174
175impl FromStr for LinuxRLimitResource {
176    type Err = MicrosandboxError;
177
178    fn from_str(s: &str) -> Result<Self, Self::Err> {
179        match s {
180            "RLIMIT_CPU" => Ok(Self::RLIMIT_CPU),
181            "RLIMIT_FSIZE" => Ok(Self::RLIMIT_FSIZE),
182            "RLIMIT_DATA" => Ok(Self::RLIMIT_DATA),
183            "RLIMIT_STACK" => Ok(Self::RLIMIT_STACK),
184            "RLIMIT_CORE" => Ok(Self::RLIMIT_CORE),
185            "RLIMIT_RSS" => Ok(Self::RLIMIT_RSS),
186            "RLIMIT_NPROC" => Ok(Self::RLIMIT_NPROC),
187            "RLIMIT_NOFILE" => Ok(Self::RLIMIT_NOFILE),
188            "RLIMIT_MEMLOCK" => Ok(Self::RLIMIT_MEMLOCK),
189            "RLIMIT_AS" => Ok(Self::RLIMIT_AS),
190            "RLIMIT_LOCKS" => Ok(Self::RLIMIT_LOCKS),
191            "RLIMIT_SIGPENDING" => Ok(Self::RLIMIT_SIGPENDING),
192            "RLIMIT_MSGQUEUE" => Ok(Self::RLIMIT_MSGQUEUE),
193            "RLIMIT_NICE" => Ok(Self::RLIMIT_NICE),
194            "RLIMIT_RTPRIO" => Ok(Self::RLIMIT_RTPRIO),
195            "RLIMIT_RTTIME" => Ok(Self::RLIMIT_RTTIME),
196            _ => Err(MicrosandboxError::InvalidRLimitResource(s.to_string())),
197        }
198    }
199}
200
201impl fmt::Display for LinuxRLimitResource {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::RLIMIT_CPU => write!(f, "RLIMIT_CPU"),
205            Self::RLIMIT_FSIZE => write!(f, "RLIMIT_FSIZE"),
206            Self::RLIMIT_DATA => write!(f, "RLIMIT_DATA"),
207            Self::RLIMIT_STACK => write!(f, "RLIMIT_STACK"),
208            Self::RLIMIT_CORE => write!(f, "RLIMIT_CORE"),
209            Self::RLIMIT_RSS => write!(f, "RLIMIT_RSS"),
210            Self::RLIMIT_NPROC => write!(f, "RLIMIT_NPROC"),
211            Self::RLIMIT_NOFILE => write!(f, "RLIMIT_NOFILE"),
212            Self::RLIMIT_MEMLOCK => write!(f, "RLIMIT_MEMLOCK"),
213            Self::RLIMIT_AS => write!(f, "RLIMIT_AS"),
214            Self::RLIMIT_LOCKS => write!(f, "RLIMIT_LOCKS"),
215            Self::RLIMIT_SIGPENDING => write!(f, "RLIMIT_SIGPENDING"),
216            Self::RLIMIT_MSGQUEUE => write!(f, "RLIMIT_MSGQUEUE"),
217            Self::RLIMIT_NICE => write!(f, "RLIMIT_NICE"),
218            Self::RLIMIT_RTPRIO => write!(f, "RLIMIT_RTPRIO"),
219            Self::RLIMIT_RTTIME => write!(f, "RLIMIT_RTTIME"),
220        }
221    }
222}
223
224impl FromStr for LinuxRlimit {
225    type Err = MicrosandboxError;
226
227    fn from_str(s: &str) -> Result<Self, Self::Err> {
228        let parts: Vec<&str> = s.split('=').collect();
229        if parts.len() != 2 {
230            return Err(MicrosandboxError::InvalidRLimitFormat(s.to_string()));
231        }
232
233        let resource = if let Ok(resource_num) = parts[0].parse::<u32>() {
234            LinuxRLimitResource::try_from(resource_num)?
235        } else {
236            parts[0].parse()?
237        };
238
239        let limits: Vec<&str> = parts[1].split(':').collect();
240        if limits.len() != 2 {
241            return Err(MicrosandboxError::InvalidRLimitFormat(s.to_string()));
242        }
243
244        let soft = limits[0]
245            .parse()
246            .map_err(|_| MicrosandboxError::InvalidRLimitValue(limits[0].to_string()))?;
247        let hard = limits[1]
248            .parse()
249            .map_err(|_| MicrosandboxError::InvalidRLimitValue(limits[1].to_string()))?;
250
251        Ok(Self::new(resource, soft, hard))
252    }
253}
254
255impl fmt::Display for LinuxRlimit {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        write!(f, "{}={}:{}", self.resource.as_int(), self.soft, self.hard)
258    }
259}
260
261impl Serialize for LinuxRlimit {
262    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
263    where
264        S: serde::Serializer,
265    {
266        serializer.serialize_str(&self.to_string())
267    }
268}
269
270impl<'de> Deserialize<'de> for LinuxRlimit {
271    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
272    where
273        D: serde::Deserializer<'de>,
274    {
275        let s = String::deserialize(deserializer)?;
276        Self::from_str(&s).map_err(serde::de::Error::custom)
277    }
278}
279
280//--------------------------------------------------------------------------------------------------
281// Tests
282//--------------------------------------------------------------------------------------------------
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn test_linux_rlimit_resource_from_u32() -> anyhow::Result<()> {
290        assert_eq!(
291            LinuxRLimitResource::try_from(0)?,
292            LinuxRLimitResource::RLIMIT_CPU
293        );
294        assert_eq!(
295            LinuxRLimitResource::try_from(7)?,
296            LinuxRLimitResource::RLIMIT_NOFILE
297        );
298        assert_eq!(
299            LinuxRLimitResource::try_from(15)?,
300            LinuxRLimitResource::RLIMIT_RTTIME
301        );
302        assert!(LinuxRLimitResource::try_from(16).is_err());
303        Ok(())
304    }
305
306    #[test]
307    fn test_linux_rlimit_resource_as_int() {
308        assert_eq!(LinuxRLimitResource::RLIMIT_CPU.as_int(), 0);
309        assert_eq!(LinuxRLimitResource::RLIMIT_NOFILE.as_int(), 7);
310        assert_eq!(LinuxRLimitResource::RLIMIT_RTTIME.as_int(), 15);
311    }
312
313    #[test]
314    fn test_linux_rlimit_resource_from_str() -> anyhow::Result<()> {
315        assert_eq!(
316            "RLIMIT_CPU".parse::<LinuxRLimitResource>()?,
317            LinuxRLimitResource::RLIMIT_CPU
318        );
319        assert_eq!(
320            "RLIMIT_NOFILE".parse::<LinuxRLimitResource>()?,
321            LinuxRLimitResource::RLIMIT_NOFILE
322        );
323        assert_eq!(
324            "RLIMIT_RTTIME".parse::<LinuxRLimitResource>()?,
325            LinuxRLimitResource::RLIMIT_RTTIME
326        );
327        assert!("RLIMIT_INVALID".parse::<LinuxRLimitResource>().is_err());
328        Ok(())
329    }
330
331    #[test]
332    fn test_linux_rlimit_resource_display() {
333        assert_eq!(LinuxRLimitResource::RLIMIT_CPU.to_string(), "RLIMIT_CPU");
334        assert_eq!(
335            LinuxRLimitResource::RLIMIT_NOFILE.to_string(),
336            "RLIMIT_NOFILE"
337        );
338        assert_eq!(
339            LinuxRLimitResource::RLIMIT_RTTIME.to_string(),
340            "RLIMIT_RTTIME"
341        );
342    }
343
344    #[test]
345    fn test_linux_rlimit_new() {
346        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
347        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
348        assert_eq!(rlimit.soft, 10);
349        assert_eq!(rlimit.hard, 20);
350
351        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
352        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
353        assert_eq!(rlimit.soft, 1000);
354        assert_eq!(rlimit.hard, 2000);
355    }
356
357    #[test]
358    fn test_linux_rlimit_from_str_with_rlimit_syntax() -> anyhow::Result<()> {
359        let rlimit: LinuxRlimit = "RLIMIT_CPU=10:20".parse()?;
360        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
361        assert_eq!(rlimit.soft, 10);
362        assert_eq!(rlimit.hard, 20);
363
364        let rlimit: LinuxRlimit = "RLIMIT_NOFILE=1000:2000".parse()?;
365        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
366        assert_eq!(rlimit.soft, 1000);
367        assert_eq!(rlimit.hard, 2000);
368
369        let rlimit: LinuxRlimit = "RLIMIT_AS=1048576:2097152".parse()?;
370        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_AS);
371        assert_eq!(rlimit.soft, 1048576);
372        assert_eq!(rlimit.hard, 2097152);
373
374        assert!("RLIMIT_INVALID=10:20".parse::<LinuxRlimit>().is_err());
375        assert!("RLIMIT_CPU=10".parse::<LinuxRlimit>().is_err());
376        assert!("RLIMIT_CPU=10:".parse::<LinuxRlimit>().is_err());
377        assert!("RLIMIT_CPU=:20".parse::<LinuxRlimit>().is_err());
378        Ok(())
379    }
380
381    #[test]
382    fn test_linux_rlimit_from_str_mixed_syntax() -> anyhow::Result<()> {
383        let rlimit: LinuxRlimit = "0=10:20".parse()?;
384        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_CPU);
385        assert_eq!(rlimit.soft, 10);
386        assert_eq!(rlimit.hard, 20);
387
388        let rlimit: LinuxRlimit = "RLIMIT_NOFILE=1000:2000".parse()?;
389        assert_eq!(rlimit.resource, LinuxRLimitResource::RLIMIT_NOFILE);
390        assert_eq!(rlimit.soft, 1000);
391        assert_eq!(rlimit.hard, 2000);
392
393        Ok(())
394    }
395
396    #[test]
397    fn test_linux_rlimit_display() {
398        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
399        assert_eq!(rlimit.to_string(), "0=10:20");
400
401        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
402        assert_eq!(rlimit.to_string(), "7=1000:2000");
403    }
404
405    #[test]
406    fn test_linux_rlimit_serialize_deserialize() -> anyhow::Result<()> {
407        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_CPU, 10, 20);
408        let serialized = serde_json::to_string(&rlimit)?;
409        assert_eq!(serialized, "\"0=10:20\"");
410
411        let deserialized: LinuxRlimit = serde_json::from_str(&serialized)?;
412        assert_eq!(deserialized, rlimit);
413
414        let rlimit = LinuxRlimit::new(LinuxRLimitResource::RLIMIT_NOFILE, 1000, 2000);
415        let serialized = serde_json::to_string(&rlimit)?;
416        assert_eq!(serialized, "\"7=1000:2000\"");
417
418        let deserialized: LinuxRlimit = serde_json::from_str(&serialized)?;
419        assert_eq!(deserialized, rlimit);
420
421        Ok(())
422    }
423}