oci_spec/runtime/
process.rs

1use crate::{
2    error::OciSpecError,
3    runtime::{Capabilities, Capability},
4};
5use derive_builder::Builder;
6use getset::{CopyGetters, Getters, MutGetters, Setters};
7use regex::Regex;
8use serde::{de, Deserialize, Deserializer, Serialize};
9use std::path::PathBuf;
10use std::sync::OnceLock;
11use strum_macros::{Display as StrumDisplay, EnumString};
12
13#[derive(
14    Builder,
15    Clone,
16    CopyGetters,
17    Debug,
18    Deserialize,
19    Getters,
20    MutGetters,
21    Setters,
22    Eq,
23    PartialEq,
24    Serialize,
25)]
26#[serde(rename_all = "camelCase")]
27#[builder(
28    default,
29    pattern = "owned",
30    setter(into, strip_option),
31    build_fn(error = "OciSpecError")
32)]
33/// Process contains information to start a specific application inside the
34/// container.
35pub struct Process {
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    #[getset(get_copy = "pub", set = "pub")]
38    /// Terminal creates an interactive terminal for the container.
39    terminal: Option<bool>,
40
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    #[getset(get_copy = "pub", set = "pub")]
43    /// ConsoleSize specifies the size of the console.
44    console_size: Option<Box>,
45
46    #[getset(get_mut = "pub", get = "pub", set = "pub")]
47    /// User specifies user information for the process.
48    user: User,
49
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    #[getset(get = "pub", set = "pub")]
52    /// Args specifies the binary and arguments for the application to
53    /// execute.
54    args: Option<Vec<String>>,
55
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    #[getset(get_mut = "pub", get = "pub", set = "pub")]
58    /// CommandLine specifies the full command line for the application to
59    /// execute on Windows.
60    command_line: Option<String>,
61
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    #[getset(get_mut = "pub", get = "pub", set = "pub")]
64    /// Env populates the process environment for the process.
65    env: Option<Vec<String>>,
66
67    #[getset(get = "pub", set = "pub")]
68    /// Cwd is the current working directory for the process and must be
69    /// relative to the container's root.
70    cwd: PathBuf,
71
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    #[getset(get = "pub", set = "pub")]
74    /// Capabilities are Linux capabilities that are kept for the process.
75    capabilities: Option<LinuxCapabilities>,
76
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    #[getset(get = "pub", set = "pub")]
79    /// Rlimits specifies rlimit options to apply to the process.
80    rlimits: Option<Vec<PosixRlimit>>,
81
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    #[getset(get_copy = "pub", set = "pub")]
84    /// NoNewPrivileges controls whether additional privileges could be
85    /// gained by processes in the container.
86    no_new_privileges: Option<bool>,
87
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    #[getset(get = "pub", set = "pub")]
90    /// ApparmorProfile specifies the apparmor profile for the container.
91    apparmor_profile: Option<String>,
92
93    #[serde(skip_serializing_if = "Option::is_none")]
94    #[getset(get_copy = "pub", set = "pub")]
95    /// Specify an oom_score_adj for the container.
96    oom_score_adj: Option<i32>,
97
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    #[getset(get = "pub", set = "pub")]
100    /// SelinuxLabel specifies the selinux context that the container
101    /// process is run as.
102    selinux_label: Option<String>,
103
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    #[getset(get = "pub", set = "pub")]
106    /// IOPriority contains the I/O priority settings for the cgroup.
107    io_priority: Option<LinuxIOPriority>,
108
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    #[getset(get = "pub", set = "pub")]
111    /// Scheduler specifies the scheduling attributes for a process
112    scheduler: Option<Scheduler>,
113
114    #[serde(
115        rename = "execCPUAffinity",
116        default,
117        skip_serializing_if = "Option::is_none"
118    )]
119    #[getset(get = "pub", set = "pub")]
120    /// ExecCPUAffinity specifies the cpu affinity for a process
121    exec_cpu_affinity: Option<ExecCPUAffinity>,
122}
123
124// Default impl for processes in the container
125impl Default for Process {
126    fn default() -> Self {
127        Process {
128            // Don't create an interactive terminal for container by default
129            terminal: false.into(),
130            // Gives default console size of 0, 0
131            console_size: Default::default(),
132            // Gives process a uid and gid of 0 (root)
133            user: Default::default(),
134            // By default executes sh command, giving user shell
135            args: vec!["sh".to_string()].into(),
136            // Sets linux default environment for binaries and default xterm emulator
137            env: vec![
138                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".into(),
139                "TERM=xterm".into(),
140            ]
141            .into(),
142            // Sets cwd of process to the container root by default
143            cwd: "/".into(),
144            // By default does not allow process to gain additional privileges
145            no_new_privileges: true.into(),
146            // Empty String, no default apparmor
147            apparmor_profile: Default::default(),
148            // Empty String, no default selinux
149            selinux_label: Default::default(),
150            // Empty String, no default scheduler
151            scheduler: Default::default(),
152            // See impl Default for LinuxCapabilities
153            capabilities: Some(Default::default()),
154            // Sets the default maximum of 1024 files the process can open
155            // This is the same as the linux kernel default
156            rlimits: vec![PosixRlimit {
157                typ: PosixRlimitType::RlimitNofile,
158                hard: 1024,
159                soft: 1024,
160            }]
161            .into(),
162            oom_score_adj: None,
163            command_line: None,
164            // Empty IOPriority, no default iopriority
165            io_priority: Default::default(),
166            exec_cpu_affinity: Default::default(),
167        }
168    }
169}
170
171#[derive(
172    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
173)]
174#[builder(
175    default,
176    pattern = "owned",
177    setter(into, strip_option),
178    build_fn(error = "OciSpecError")
179)]
180#[getset(get_copy = "pub", set = "pub")]
181/// Box specifies dimensions of a rectangle. Used for specifying the size of
182/// a console.
183pub struct Box {
184    #[serde(default)]
185    /// Height is the vertical dimension of a box.
186    height: u64,
187
188    #[serde(default)]
189    /// Width is the horizontal dimension of a box.
190    width: u64,
191}
192
193#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
194#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
195#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
196/// Available rlimit types (see <https://man7.org/linux/man-pages/man2/getrlimit.2.html>)
197pub enum PosixRlimitType {
198    /// Limit in seconds of the amount of CPU time that the process can consume.
199    RlimitCpu,
200
201    /// Maximum size in bytes of the files that the process creates.
202    RlimitFsize,
203
204    /// Maximum size of the process's data segment (init data, uninit data and
205    /// heap) in bytes.
206    RlimitData,
207
208    /// Maximum size of the process stack in bytes.
209    RlimitStack,
210
211    /// Maximum size of a core dump file in bytes.
212    RlimitCore,
213
214    /// Limit on the process's resident set (the number of virtual pages
215    /// resident in RAM).
216    RlimitRss,
217
218    /// Limit on number of threads for the real uid calling processes.
219    RlimitNproc,
220
221    /// One greator than the maximum number of file descriptors that one process
222    /// may open.
223    RlimitNofile,
224
225    /// Maximum number of bytes of memory that may be locked into RAM.
226    RlimitMemlock,
227
228    /// Maximum size of the process's virtual memory(address space) in bytes.
229    RlimitAs,
230
231    /// Limit on the number of locks and leases for the process.
232    RlimitLocks,
233
234    /// Limit on number of signals that may be queued for the process.
235    RlimitSigpending,
236
237    /// Limit on the number of bytes that can be allocated for POSIX message
238    /// queue.
239    RlimitMsgqueue,
240
241    /// Specifies a ceiling to which the process's nice value can be raised.
242    RlimitNice,
243
244    /// Specifies a ceiling on the real-time priority.
245    RlimitRtprio,
246
247    /// This is a limit (in microseconds) on the amount of CPU time that a
248    /// process scheduled under a real-time scheduling policy may consume
249    /// without making a blocking system call.
250    RlimitRttime,
251}
252
253impl Default for PosixRlimitType {
254    fn default() -> Self {
255        Self::RlimitCpu
256    }
257}
258
259#[derive(
260    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
261)]
262#[builder(
263    default,
264    pattern = "owned",
265    setter(into, strip_option),
266    build_fn(error = "OciSpecError")
267)]
268#[getset(get_copy = "pub", set = "pub")]
269/// RLimit types and restrictions.
270pub struct PosixRlimit {
271    #[serde(rename = "type")]
272    /// Type of Rlimit to set
273    typ: PosixRlimitType,
274
275    #[serde(default)]
276    /// Hard limit for specified type
277    hard: u64,
278
279    #[serde(default)]
280    /// Soft limit for specified type
281    soft: u64,
282}
283
284#[derive(
285    Builder,
286    Clone,
287    CopyGetters,
288    Debug,
289    Default,
290    Deserialize,
291    Getters,
292    MutGetters,
293    Setters,
294    Eq,
295    PartialEq,
296    Serialize,
297)]
298#[serde(rename_all = "camelCase")]
299#[builder(
300    default,
301    pattern = "owned",
302    setter(into, strip_option),
303    build_fn(error = "OciSpecError")
304)]
305/// User id (uid) and group id (gid) tracks file permissions.
306pub struct User {
307    #[serde(default)]
308    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
309    /// UID is the user id.
310    uid: u32,
311
312    #[serde(default)]
313    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
314    /// GID is the group id.
315    gid: u32,
316
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
319    /// Specifies the umask of the user.
320    umask: Option<u32>,
321
322    #[serde(default, skip_serializing_if = "Option::is_none")]
323    #[getset(get_mut = "pub", get = "pub", set = "pub")]
324    /// AdditionalGids are additional group ids set for the container's
325    /// process.
326    additional_gids: Option<Vec<u32>>,
327
328    #[serde(default, skip_serializing_if = "Option::is_none")]
329    #[getset(get_mut = "pub", get = "pub", set = "pub")]
330    /// Username is the user name.
331    username: Option<String>,
332}
333
334#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
335#[builder(
336    default,
337    pattern = "owned",
338    setter(into, strip_option),
339    build_fn(error = "OciSpecError")
340)]
341#[getset(get = "pub", set = "pub")]
342/// LinuxCapabilities specifies the list of allowed capabilities that are
343/// kept for a process. <http://man7.org/linux/man-pages/man7/capabilities.7.html>
344pub struct LinuxCapabilities {
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    /// Bounding is the set of capabilities checked by the kernel.
347    bounding: Option<Capabilities>,
348
349    #[serde(default, skip_serializing_if = "Option::is_none")]
350    /// Effective is the set of capabilities checked by the kernel.
351    effective: Option<Capabilities>,
352
353    #[serde(default, skip_serializing_if = "Option::is_none")]
354    /// Inheritable is the capabilities preserved across execve.
355    inheritable: Option<Capabilities>,
356
357    #[serde(default, skip_serializing_if = "Option::is_none")]
358    /// Permitted is the limiting superset for effective capabilities.
359    permitted: Option<Capabilities>,
360
361    #[serde(default, skip_serializing_if = "Option::is_none")]
362    /// Ambient is the ambient set of capabilities that are kept.
363    ambient: Option<Capabilities>,
364}
365
366// Default container's linux capabilities:
367// CAP_AUDIT_WRITE gives container ability to write to linux audit logs,
368// CAP_KILL gives container ability to kill non root processes
369// CAP_NET_BIND_SERVICE allows container to bind to ports below 1024
370impl Default for LinuxCapabilities {
371    fn default() -> Self {
372        let audit_write = Capability::AuditWrite;
373        let cap_kill = Capability::Kill;
374        let net_bind = Capability::NetBindService;
375        let default_vec = vec![audit_write, cap_kill, net_bind]
376            .into_iter()
377            .collect::<Capabilities>();
378        LinuxCapabilities {
379            bounding: default_vec.clone().into(),
380            effective: default_vec.clone().into(),
381            inheritable: default_vec.clone().into(),
382            permitted: default_vec.clone().into(),
383            ambient: default_vec.into(),
384        }
385    }
386}
387
388#[derive(
389    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
390)]
391#[builder(
392    default,
393    pattern = "owned",
394    setter(into, strip_option),
395    build_fn(error = "OciSpecError")
396)]
397#[getset(get_copy = "pub", set = "pub")]
398/// RLimit types and restrictions.
399pub struct LinuxIOPriority {
400    #[serde(default)]
401    /// Class represents an I/O scheduling class.
402    class: IOPriorityClass,
403
404    #[serde(default)]
405    /// Priority for the io operation
406    priority: i64,
407}
408
409#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
410#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
411#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
412/// IOPriorityClass represents an I/O scheduling class.
413pub enum IOPriorityClass {
414    /// This is the realtime io class. This scheduling class is given
415    /// higher priority than any other in the system, processes from this class are
416    /// given first access to the disk every time. Thus it needs to be used with some
417    /// care, one io RT process can starve the entire system. Within the RT class,
418    /// there are 8 levels of class data that determine exactly how much time this
419    /// process needs the disk for on each service. In the future this might change
420    /// to be more directly mappable to performance, by passing in a wanted data
421    /// rate instead
422    IoprioClassRt,
423    /// This is the best-effort scheduling class, which is the default
424    /// for any process that hasn't set a specific io priority. The class data
425    /// determines how much io bandwidth the process will get, it's directly mappable
426    /// to the cpu nice levels just more coarsely implemented. 0 is the highest
427    /// BE prio level, 7 is the lowest. The mapping between cpu nice level and io
428    /// nice level is determined as: io_nice = (cpu_nice + 20) / 5.
429    IoprioClassBe,
430    /// This is the idle scheduling class, processes running at this
431    /// level only get io time when no one else needs the disk. The idle class has no
432    /// class data, since it doesn't really apply here.
433    IoprioClassIdle,
434}
435
436impl Default for IOPriorityClass {
437    fn default() -> Self {
438        Self::IoprioClassBe
439    }
440}
441
442#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
443#[builder(
444    default,
445    pattern = "owned",
446    setter(into, strip_option),
447    build_fn(error = "OciSpecError")
448)]
449#[getset(get = "pub", set = "pub")]
450/// Scheduler represents the scheduling attributes for a process. It is based on
451/// the Linux sched_setattr(2) syscall.
452pub struct Scheduler {
453    /// Policy represents the scheduling policy (e.g., SCHED_FIFO, SCHED_RR, SCHED_OTHER).
454    policy: LinuxSchedulerPolicy,
455
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    /// Nice is the nice value for the process, which affects its priority.
458    nice: Option<i32>,
459
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    /// Priority represents the static priority of the process.
462    priority: Option<i32>,
463
464    #[serde(default, skip_serializing_if = "Option::is_none")]
465    /// Flags is an array of scheduling flags.
466    flags: Option<Vec<LinuxSchedulerFlag>>,
467
468    // The following ones are used by the DEADLINE scheduler.
469    #[serde(default, skip_serializing_if = "Option::is_none")]
470    /// Runtime is the amount of time in nanoseconds during which the process
471    /// is allowed to run in a given period.
472    runtime: Option<u64>,
473
474    #[serde(default, skip_serializing_if = "Option::is_none")]
475    /// Deadline is the absolute deadline for the process to complete its execution.
476    deadline: Option<u64>,
477
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    /// Period is the length of the period in nanoseconds used for determining the process runtime.
480    period: Option<u64>,
481}
482
483/// Default scheduler is SCHED_OTHER with no priority.
484impl Default for Scheduler {
485    fn default() -> Self {
486        Self {
487            policy: LinuxSchedulerPolicy::default(),
488            nice: None,
489            priority: None,
490            flags: None,
491            runtime: None,
492            deadline: None,
493            period: None,
494        }
495    }
496}
497
498#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
499#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
500#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
501///  LinuxSchedulerPolicy represents different scheduling policies used with the Linux Scheduler
502pub enum LinuxSchedulerPolicy {
503    /// SchedOther is the default scheduling policy
504    SchedOther,
505    /// SchedFIFO is the First-In-First-Out scheduling policy
506    SchedFifo,
507    /// SchedRR is the Round-Robin scheduling policy
508    SchedRr,
509    /// SchedBatch is the Batch scheduling policy
510    SchedBatch,
511    /// SchedISO is the Isolation scheduling policy
512    SchedIso,
513    /// SchedIdle is the Idle scheduling policy
514    SchedIdle,
515    /// SchedDeadline is the Deadline scheduling policy
516    SchedDeadline,
517}
518
519/// Default LinuxSchedulerPolicy is SchedOther
520impl Default for LinuxSchedulerPolicy {
521    fn default() -> Self {
522        LinuxSchedulerPolicy::SchedOther
523    }
524}
525
526#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
527#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
528#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
529///  LinuxSchedulerFlag represents the flags used by the Linux Scheduler.
530pub enum LinuxSchedulerFlag {
531    /// SchedFlagResetOnFork represents the reset on fork scheduling flag
532    SchedResetOnFork,
533    /// SchedFlagReclaim represents the reclaim scheduling flag
534    SchedFlagReclaim,
535    /// SchedFlagDLOverrun represents the deadline overrun scheduling flag
536    SchedFlagDLOverrun,
537    /// SchedFlagKeepPolicy represents the keep policy scheduling flag
538    SchedFlagKeepPolicy,
539    /// SchedFlagKeepParams represents the keep parameters scheduling flag
540    SchedFlagKeepParams,
541    /// SchedFlagUtilClampMin represents the utilization clamp minimum scheduling flag
542    SchedFlagUtilClampMin,
543    /// SchedFlagUtilClampMin represents the utilization clamp maximum scheduling flag
544    SchedFlagUtilClampMax,
545}
546
547/// Default LinuxSchedulerFlag is SchedResetOnFork
548impl Default for LinuxSchedulerFlag {
549    fn default() -> Self {
550        LinuxSchedulerFlag::SchedResetOnFork
551    }
552}
553
554#[derive(
555    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,
556)]
557#[builder(
558    default,
559    pattern = "owned",
560    setter(into, strip_option),
561    build_fn(validate = "Self::validate", error = "OciSpecError")
562)]
563#[getset(get = "pub", set = "pub")]
564/// ExecCPUAffinity specifies CPU affinity used to execute the process.
565/// This setting is not applicable to the container's init process.
566pub struct ExecCPUAffinity {
567    #[serde(
568        default,
569        skip_serializing_if = "Option::is_none",
570        deserialize_with = "deserialize"
571    )]
572    /// initial is a list of CPUs a runtime parent process to be run on
573    /// initially, before the transition to container's cgroup.
574    /// This is a a comma-separated list, with dashes to represent ranges.
575    /// For example, `0-3,7` represents CPUs 0,1,2,3, and 7.
576    initial: Option<String>,
577
578    #[serde(
579        default,
580        rename = "final",
581        skip_serializing_if = "Option::is_none",
582        deserialize_with = "deserialize"
583    )]
584    /// cpu_affinity_final is a list of CPUs the process will be run on after the transition
585    /// to container's cgroup. The format is the same as for `initial`. If omitted or empty,
586    /// runtime SHOULD NOT change process' CPU affinity after the process is moved to
587    /// container's cgroup, and the final affinity is determined by the Linux kernel.
588    cpu_affinity_final: Option<String>,
589}
590
591impl ExecCPUAffinityBuilder {
592    fn validate(&self) -> Result<(), OciSpecError> {
593        if let Some(Some(ref s)) = self.initial {
594            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
595        }
596
597        if let Some(Some(ref s)) = self.cpu_affinity_final {
598            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
599        }
600
601        Ok(())
602    }
603}
604
605fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
606where
607    D: Deserializer<'de>,
608{
609    let value: Option<String> = Option::deserialize(deserializer)?;
610
611    if let Some(ref s) = value {
612        validate_cpu_affinity(s).map_err(de::Error::custom)?;
613    }
614
615    Ok(value)
616}
617
618fn exec_cpu_affinity_regex() -> &'static Regex {
619    static EXEC_CPU_AFFINITY_REGEX: OnceLock<Regex> = OnceLock::new();
620    EXEC_CPU_AFFINITY_REGEX.get_or_init(|| {
621        Regex::new(r"^(\d+(-\d+)?)(,\d+(-\d+)?)*$")
622            .expect("Failed to create regex for execCPUAffinity")
623    })
624}
625
626fn validate_cpu_affinity(s: &str) -> Result<(), String> {
627    if !exec_cpu_affinity_regex().is_match(s) {
628        return Err(format!("Invalid execCPUAffinity format: {s}"));
629    }
630
631    Ok(())
632}
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637    use serde_json::json;
638
639    // PosixRlimitType test cases
640    #[test]
641    fn posix_rlimit_type_enum_to_string() {
642        let type_a = PosixRlimitType::RlimitCpu;
643        assert_eq!(type_a.to_string(), "RLIMIT_CPU");
644
645        let type_b = PosixRlimitType::RlimitData;
646        assert_eq!(type_b.to_string(), "RLIMIT_DATA");
647
648        let type_c = PosixRlimitType::RlimitNofile;
649        assert_eq!(type_c.to_string(), "RLIMIT_NOFILE");
650    }
651
652    #[test]
653    fn posix_rlimit_type_string_to_enum() {
654        let posix_rlimit_type_str = "RLIMIT_CPU";
655        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
656        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitCpu);
657
658        let posix_rlimit_type_str = "RLIMIT_DATA";
659        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
660        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitData);
661
662        let posix_rlimit_type_str = "RLIMIT_NOFILE";
663        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
664        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitNofile);
665
666        let invalid_posix_rlimit_type_str = "x";
667        let unknown_rlimit = invalid_posix_rlimit_type_str.parse::<PosixRlimitType>();
668        assert!(unknown_rlimit.is_err());
669    }
670
671    #[test]
672    fn exec_cpu_affinity_valid_initial_final() {
673        let json = json!({"initial": "0-3,7", "final": "4-6,8"});
674        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
675        assert!(result.is_ok());
676
677        let json = json!({"initial": "0-3", "final": "4-6"});
678        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
679        assert!(result.is_ok());
680
681        let json = json!({"initial": "0", "final": "4"});
682        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
683        assert!(result.is_ok());
684    }
685
686    #[test]
687    fn exec_cpu_affinity_invalid_initial() {
688        let json = json!({"initial": "0-3,,7", "final": "4-6,8"});
689        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
690        assert!(result.is_err());
691    }
692
693    #[test]
694    fn exec_cpu_affinity_invalid_final() {
695        let json = json!({"initial": "0-3,7", "final": "4-6.,8"});
696        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
697        assert!(result.is_err());
698    }
699
700    #[test]
701    fn exec_cpu_affinity_valid_final() {
702        let json = json!({"final": "0,1,2,3"});
703        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
704        assert!(result.is_ok());
705        assert!(result.unwrap().initial.is_none());
706    }
707
708    #[test]
709    fn exec_cpu_affinity_valid_initial() {
710        let json = json!({"initial": "0-1,2-5"});
711        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
712        assert!(result.is_ok());
713        assert!(result.unwrap().cpu_affinity_final.is_none());
714    }
715
716    #[test]
717    fn exec_cpu_affinity_empty() {
718        let json = json!({});
719        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
720        assert!(result.is_ok());
721        let affinity = result.unwrap();
722        assert!(affinity.initial.is_none());
723        assert!(affinity.cpu_affinity_final.is_none());
724    }
725
726    #[test]
727    fn test_build_valid_input() {
728        let affinity = ExecCPUAffinityBuilder::default()
729            .initial("0-3,7,8,9,10".to_string())
730            .cpu_affinity_final("4-6,8".to_string())
731            .build();
732        assert!(affinity.is_ok());
733        let affinity = affinity.unwrap();
734        assert_eq!(affinity.initial, Some("0-3,7,8,9,10".to_string()));
735        assert_eq!(affinity.cpu_affinity_final, Some("4-6,8".to_string()));
736    }
737
738    #[test]
739    fn test_build_invalid_initial() {
740        let affinity = ExecCPUAffinityBuilder::default()
741            .initial("0-3,i".to_string())
742            .cpu_affinity_final("4-6,8".to_string())
743            .build();
744        let err = affinity.unwrap_err();
745        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-3,i");
746
747        let affinity = ExecCPUAffinityBuilder::default()
748            .initial("-".to_string())
749            .cpu_affinity_final("4-6,8".to_string())
750            .build();
751        let err = affinity.unwrap_err();
752        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: -");
753    }
754
755    #[test]
756    fn test_build_invalid_final() {
757        let affinity = ExecCPUAffinityBuilder::default()
758            .initial("0-3,7".to_string())
759            .cpu_affinity_final("0-l1".to_string())
760            .build();
761        let err = affinity.unwrap_err();
762        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-l1");
763
764        let affinity = ExecCPUAffinityBuilder::default()
765            .initial("0-3,7".to_string())
766            .cpu_affinity_final(",1,2".to_string())
767            .build();
768        let err = affinity.unwrap_err();
769        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: ,1,2");
770    }
771
772    #[test]
773    fn test_build_empty() {
774        let affinity = ExecCPUAffinityBuilder::default().build();
775        let affinity = affinity.unwrap();
776        assert!(affinity.initial.is_none());
777        assert!(affinity.cpu_affinity_final.is_none());
778    }
779}