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)]
33pub struct Process {
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 #[getset(get_copy = "pub", set = "pub")]
38 terminal: Option<bool>,
40
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 #[getset(get_copy = "pub", set = "pub")]
43 console_size: Option<Box>,
45
46 #[getset(get_mut = "pub", get = "pub", set = "pub")]
47 user: User,
49
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 #[getset(get = "pub", set = "pub")]
52 args: Option<Vec<String>>,
55
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 #[getset(get_mut = "pub", get = "pub", set = "pub")]
58 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: Option<Vec<String>>,
66
67 #[getset(get = "pub", set = "pub")]
68 cwd: PathBuf,
71
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 #[getset(get = "pub", set = "pub")]
74 capabilities: Option<LinuxCapabilities>,
76
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 #[getset(get = "pub", set = "pub")]
79 rlimits: Option<Vec<PosixRlimit>>,
81
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 #[getset(get_copy = "pub", set = "pub")]
84 no_new_privileges: Option<bool>,
87
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 #[getset(get = "pub", set = "pub")]
90 apparmor_profile: Option<String>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
94 #[getset(get_copy = "pub", set = "pub")]
95 oom_score_adj: Option<i32>,
97
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 #[getset(get = "pub", set = "pub")]
100 selinux_label: Option<String>,
103
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 #[getset(get = "pub", set = "pub")]
106 io_priority: Option<LinuxIOPriority>,
108
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 #[getset(get = "pub", set = "pub")]
111 scheduler: Option<Scheduler>,
113
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 #[getset(get = "pub", set = "pub")]
116 exec_cpu_affinity: Option<ExecCPUAffinity>,
118}
119
120impl Default for Process {
122 fn default() -> Self {
123 Process {
124 terminal: false.into(),
126 console_size: Default::default(),
128 user: Default::default(),
130 args: vec!["sh".to_string()].into(),
132 env: vec![
134 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".into(),
135 "TERM=xterm".into(),
136 ]
137 .into(),
138 cwd: "/".into(),
140 no_new_privileges: true.into(),
142 apparmor_profile: Default::default(),
144 selinux_label: Default::default(),
146 scheduler: Default::default(),
148 capabilities: Some(Default::default()),
150 rlimits: vec![PosixRlimit {
153 typ: PosixRlimitType::RlimitNofile,
154 hard: 1024,
155 soft: 1024,
156 }]
157 .into(),
158 oom_score_adj: None,
159 command_line: None,
160 io_priority: Default::default(),
162 exec_cpu_affinity: Default::default(),
163 }
164 }
165}
166
167#[derive(
168 Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
169)]
170#[builder(
171 default,
172 pattern = "owned",
173 setter(into, strip_option),
174 build_fn(error = "OciSpecError")
175)]
176#[getset(get_copy = "pub", set = "pub")]
177pub struct Box {
180 #[serde(default)]
181 height: u64,
183
184 #[serde(default)]
185 width: u64,
187}
188
189#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
190#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
191#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
192pub enum PosixRlimitType {
194 RlimitCpu,
196
197 RlimitFsize,
199
200 RlimitData,
203
204 RlimitStack,
206
207 RlimitCore,
209
210 RlimitRss,
213
214 RlimitNproc,
216
217 RlimitNofile,
220
221 RlimitMemlock,
223
224 RlimitAs,
226
227 RlimitLocks,
229
230 RlimitSigpending,
232
233 RlimitMsgqueue,
236
237 RlimitNice,
239
240 RlimitRtprio,
242
243 RlimitRttime,
247}
248
249impl Default for PosixRlimitType {
250 fn default() -> Self {
251 Self::RlimitCpu
252 }
253}
254
255#[derive(
256 Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
257)]
258#[builder(
259 default,
260 pattern = "owned",
261 setter(into, strip_option),
262 build_fn(error = "OciSpecError")
263)]
264#[getset(get_copy = "pub", set = "pub")]
265pub struct PosixRlimit {
267 #[serde(rename = "type")]
268 typ: PosixRlimitType,
270
271 #[serde(default)]
272 hard: u64,
274
275 #[serde(default)]
276 soft: u64,
278}
279
280#[derive(
281 Builder,
282 Clone,
283 CopyGetters,
284 Debug,
285 Default,
286 Deserialize,
287 Getters,
288 MutGetters,
289 Setters,
290 Eq,
291 PartialEq,
292 Serialize,
293)]
294#[serde(rename_all = "camelCase")]
295#[builder(
296 default,
297 pattern = "owned",
298 setter(into, strip_option),
299 build_fn(error = "OciSpecError")
300)]
301pub struct User {
303 #[serde(default)]
304 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
305 uid: u32,
307
308 #[serde(default)]
309 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
310 gid: u32,
312
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
315 umask: Option<u32>,
317
318 #[serde(default, skip_serializing_if = "Option::is_none")]
319 #[getset(get_mut = "pub", get = "pub", set = "pub")]
320 additional_gids: Option<Vec<u32>>,
323
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 #[getset(get_mut = "pub", get = "pub", set = "pub")]
326 username: Option<String>,
328}
329
330#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
331#[builder(
332 default,
333 pattern = "owned",
334 setter(into, strip_option),
335 build_fn(error = "OciSpecError")
336)]
337#[getset(get = "pub", set = "pub")]
338pub struct LinuxCapabilities {
341 #[serde(default, skip_serializing_if = "Option::is_none")]
342 bounding: Option<Capabilities>,
344
345 #[serde(default, skip_serializing_if = "Option::is_none")]
346 effective: Option<Capabilities>,
348
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 inheritable: Option<Capabilities>,
352
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 permitted: Option<Capabilities>,
356
357 #[serde(default, skip_serializing_if = "Option::is_none")]
358 ambient: Option<Capabilities>,
360}
361
362impl Default for LinuxCapabilities {
367 fn default() -> Self {
368 let audit_write = Capability::AuditWrite;
369 let cap_kill = Capability::Kill;
370 let net_bind = Capability::NetBindService;
371 let default_vec = vec![audit_write, cap_kill, net_bind]
372 .into_iter()
373 .collect::<Capabilities>();
374 LinuxCapabilities {
375 bounding: default_vec.clone().into(),
376 effective: default_vec.clone().into(),
377 inheritable: default_vec.clone().into(),
378 permitted: default_vec.clone().into(),
379 ambient: default_vec.into(),
380 }
381 }
382}
383
384#[derive(
385 Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
386)]
387#[builder(
388 default,
389 pattern = "owned",
390 setter(into, strip_option),
391 build_fn(error = "OciSpecError")
392)]
393#[getset(get_copy = "pub", set = "pub")]
394pub struct LinuxIOPriority {
396 #[serde(default)]
397 class: IOPriorityClass,
399
400 #[serde(default)]
401 priority: i64,
403}
404
405#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
406#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
407#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
408pub enum IOPriorityClass {
410 IoprioClassRt,
419 IoprioClassBe,
426 IoprioClassIdle,
430}
431
432impl Default for IOPriorityClass {
433 fn default() -> Self {
434 Self::IoprioClassBe
435 }
436}
437
438#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
439#[builder(
440 default,
441 pattern = "owned",
442 setter(into, strip_option),
443 build_fn(error = "OciSpecError")
444)]
445#[getset(get = "pub", set = "pub")]
446pub struct Scheduler {
449 policy: LinuxSchedulerPolicy,
451
452 #[serde(default, skip_serializing_if = "Option::is_none")]
453 nice: Option<i32>,
455
456 #[serde(default, skip_serializing_if = "Option::is_none")]
457 priority: Option<i32>,
459
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 flags: Option<Vec<LinuxSchedulerFlag>>,
463
464 #[serde(default, skip_serializing_if = "Option::is_none")]
466 runtime: Option<u64>,
469
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 deadline: Option<u64>,
473
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 period: Option<u64>,
477}
478
479impl Default for Scheduler {
481 fn default() -> Self {
482 Self {
483 policy: LinuxSchedulerPolicy::default(),
484 nice: None,
485 priority: None,
486 flags: None,
487 runtime: None,
488 deadline: None,
489 period: None,
490 }
491 }
492}
493
494#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
495#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
496#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
497pub enum LinuxSchedulerPolicy {
499 SchedOther,
501 SchedFifo,
503 SchedRr,
505 SchedBatch,
507 SchedIso,
509 SchedIdle,
511 SchedDeadline,
513}
514
515impl Default for LinuxSchedulerPolicy {
517 fn default() -> Self {
518 LinuxSchedulerPolicy::SchedOther
519 }
520}
521
522#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
523#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
524#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
525pub enum LinuxSchedulerFlag {
527 SchedResetOnFork,
529 SchedFlagReclaim,
531 SchedFlagDLOverrun,
533 SchedFlagKeepPolicy,
535 SchedFlagKeepParams,
537 SchedFlagUtilClampMin,
539 SchedFlagUtilClampMax,
541}
542
543impl Default for LinuxSchedulerFlag {
545 fn default() -> Self {
546 LinuxSchedulerFlag::SchedResetOnFork
547 }
548}
549
550#[derive(
551 Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,
552)]
553#[builder(
554 default,
555 pattern = "owned",
556 setter(into, strip_option),
557 build_fn(validate = "Self::validate", error = "OciSpecError")
558)]
559#[getset(get = "pub", set = "pub")]
560pub struct ExecCPUAffinity {
563 #[serde(
564 default,
565 skip_serializing_if = "Option::is_none",
566 deserialize_with = "deserialize"
567 )]
568 cpu_affinity_initial: Option<String>,
573
574 #[serde(
575 default,
576 skip_serializing_if = "Option::is_none",
577 deserialize_with = "deserialize"
578 )]
579 cpu_affinity_final: Option<String>,
584}
585
586impl ExecCPUAffinityBuilder {
587 fn validate(&self) -> Result<(), OciSpecError> {
588 if let Some(Some(ref s)) = self.cpu_affinity_initial {
589 validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
590 }
591
592 if let Some(Some(ref s)) = self.cpu_affinity_final {
593 validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
594 }
595
596 Ok(())
597 }
598}
599
600fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
601where
602 D: Deserializer<'de>,
603{
604 let value: Option<String> = Option::deserialize(deserializer)?;
605
606 if let Some(ref s) = value {
607 validate_cpu_affinity(s).map_err(de::Error::custom)?;
608 }
609
610 Ok(value)
611}
612
613fn exec_cpu_affinity_regex() -> &'static Regex {
614 static EXEC_CPU_AFFINITY_REGEX: OnceLock<Regex> = OnceLock::new();
615 EXEC_CPU_AFFINITY_REGEX.get_or_init(|| {
616 Regex::new(r"^(\d+(-\d+)?)(,\d+(-\d+)?)*$")
617 .expect("Failed to create regex for execCPUAffinity")
618 })
619}
620
621fn validate_cpu_affinity(s: &str) -> Result<(), String> {
622 if !exec_cpu_affinity_regex().is_match(s) {
623 return Err(format!("Invalid execCPUAffinity format: {}", s));
624 }
625
626 Ok(())
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use serde_json::json;
633
634 #[test]
636 fn posix_rlimit_type_enum_to_string() {
637 let type_a = PosixRlimitType::RlimitCpu;
638 assert_eq!(type_a.to_string(), "RLIMIT_CPU");
639
640 let type_b = PosixRlimitType::RlimitData;
641 assert_eq!(type_b.to_string(), "RLIMIT_DATA");
642
643 let type_c = PosixRlimitType::RlimitNofile;
644 assert_eq!(type_c.to_string(), "RLIMIT_NOFILE");
645 }
646
647 #[test]
648 fn posix_rlimit_type_string_to_enum() {
649 let posix_rlimit_type_str = "RLIMIT_CPU";
650 let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
651 assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitCpu);
652
653 let posix_rlimit_type_str = "RLIMIT_DATA";
654 let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
655 assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitData);
656
657 let posix_rlimit_type_str = "RLIMIT_NOFILE";
658 let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
659 assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitNofile);
660
661 let invalid_posix_rlimit_type_str = "x";
662 let unknown_rlimit = invalid_posix_rlimit_type_str.parse::<PosixRlimitType>();
663 assert!(unknown_rlimit.is_err());
664 }
665
666 #[test]
667 fn exec_cpu_affinity_valid_initial_final() {
668 let json = json!({"cpu_affinity_initial": "0-3,7", "cpu_affinity_final": "4-6,8"});
669 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
670 assert!(result.is_ok());
671
672 let json = json!({"cpu_affinity_initial": "0-3", "cpu_affinity_final": "4-6"});
673 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
674 assert!(result.is_ok());
675
676 let json = json!({"cpu_affinity_initial": "0", "cpu_affinity_final": "4"});
677 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
678 assert!(result.is_ok());
679 }
680
681 #[test]
682 fn exec_cpu_affinity_invalid_initial() {
683 let json = json!({"cpu_affinity_initial": "0-3,,7", "cpu_affinity_final": "4-6,8"});
684 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
685 assert!(result.is_err());
686 }
687
688 #[test]
689 fn exec_cpu_affinity_invalid_final() {
690 let json = json!({"cpu_affinity_initial": "0-3,7", "cpu_affinity_final": "4-6.,8"});
691 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
692 assert!(result.is_err());
693 }
694
695 #[test]
696 fn exec_cpu_affinity_valid_final() {
697 let json = json!({"cpu_affinity_final": "0,1,2,3"});
698 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
699 assert!(result.is_ok());
700 assert!(result.unwrap().cpu_affinity_initial.is_none());
701 }
702
703 #[test]
704 fn exec_cpu_affinity_valid_initial() {
705 let json = json!({"cpu_affinity_initial": "0-1,2-5"});
706 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
707 assert!(result.is_ok());
708 assert!(result.unwrap().cpu_affinity_final.is_none());
709 }
710
711 #[test]
712 fn exec_cpu_affinity_empty() {
713 let json = json!({});
714 let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
715 assert!(result.is_ok());
716 let affinity = result.unwrap();
717 assert!(affinity.cpu_affinity_initial.is_none());
718 assert!(affinity.cpu_affinity_final.is_none());
719 }
720
721 #[test]
722 fn test_build_valid_input() {
723 let affinity = ExecCPUAffinityBuilder::default()
724 .cpu_affinity_initial("0-3,7,8,9,10".to_string())
725 .cpu_affinity_final("4-6,8".to_string())
726 .build();
727 assert!(affinity.is_ok());
728 let affinity = affinity.unwrap();
729 assert_eq!(
730 affinity.cpu_affinity_initial,
731 Some("0-3,7,8,9,10".to_string())
732 );
733 assert_eq!(affinity.cpu_affinity_final, Some("4-6,8".to_string()));
734 }
735
736 #[test]
737 fn test_build_invalid_initial() {
738 let affinity = ExecCPUAffinityBuilder::default()
739 .cpu_affinity_initial("0-3,i".to_string())
740 .cpu_affinity_final("4-6,8".to_string())
741 .build();
742 let err = affinity.unwrap_err();
743 assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-3,i");
744
745 let affinity = ExecCPUAffinityBuilder::default()
746 .cpu_affinity_initial("-".to_string())
747 .cpu_affinity_final("4-6,8".to_string())
748 .build();
749 let err = affinity.unwrap_err();
750 assert_eq!(err.to_string(), "Invalid execCPUAffinity format: -");
751 }
752
753 #[test]
754 fn test_build_invalid_final() {
755 let affinity = ExecCPUAffinityBuilder::default()
756 .cpu_affinity_initial("0-3,7".to_string())
757 .cpu_affinity_final("0-l1".to_string())
758 .build();
759 let err = affinity.unwrap_err();
760 assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-l1");
761
762 let affinity = ExecCPUAffinityBuilder::default()
763 .cpu_affinity_initial("0-3,7".to_string())
764 .cpu_affinity_final(",1,2".to_string())
765 .build();
766 let err = affinity.unwrap_err();
767 assert_eq!(err.to_string(), "Invalid execCPUAffinity format: ,1,2");
768 }
769
770 #[test]
771 fn test_build_empty() {
772 let affinity = ExecCPUAffinityBuilder::default().build();
773 let affinity = affinity.unwrap();
774 assert!(affinity.cpu_affinity_initial.is_none());
775 assert!(affinity.cpu_affinity_final.is_none());
776 }
777}