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(
115 rename = "execCPUAffinity",
116 default,
117 skip_serializing_if = "Option::is_none"
118 )]
119 #[getset(get = "pub", set = "pub")]
120 exec_cpu_affinity: Option<ExecCPUAffinity>,
122}
123
124impl Default for Process {
126 fn default() -> Self {
127 Process {
128 terminal: false.into(),
130 console_size: Default::default(),
132 user: Default::default(),
134 args: vec!["sh".to_string()].into(),
136 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 cwd: "/".into(),
144 no_new_privileges: true.into(),
146 apparmor_profile: Default::default(),
148 selinux_label: Default::default(),
150 scheduler: Default::default(),
152 capabilities: Some(Default::default()),
154 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 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")]
181pub struct Box {
184 #[serde(default)]
185 height: u64,
187
188 #[serde(default)]
189 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")]
196pub enum PosixRlimitType {
198 RlimitCpu,
200
201 RlimitFsize,
203
204 RlimitData,
207
208 RlimitStack,
210
211 RlimitCore,
213
214 RlimitRss,
217
218 RlimitNproc,
220
221 RlimitNofile,
224
225 RlimitMemlock,
227
228 RlimitAs,
230
231 RlimitLocks,
233
234 RlimitSigpending,
236
237 RlimitMsgqueue,
240
241 RlimitNice,
243
244 RlimitRtprio,
246
247 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")]
269pub struct PosixRlimit {
271 #[serde(rename = "type")]
272 typ: PosixRlimitType,
274
275 #[serde(default)]
276 hard: u64,
278
279 #[serde(default)]
280 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)]
305pub struct User {
307 #[serde(default)]
308 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
309 uid: u32,
311
312 #[serde(default)]
313 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
314 gid: u32,
316
317 #[serde(default, skip_serializing_if = "Option::is_none")]
318 #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
319 umask: Option<u32>,
321
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 #[getset(get_mut = "pub", get = "pub", set = "pub")]
324 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: 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")]
342pub struct LinuxCapabilities {
345 #[serde(default, skip_serializing_if = "Option::is_none")]
346 bounding: Option<Capabilities>,
348
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 effective: Option<Capabilities>,
352
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 inheritable: Option<Capabilities>,
356
357 #[serde(default, skip_serializing_if = "Option::is_none")]
358 permitted: Option<Capabilities>,
360
361 #[serde(default, skip_serializing_if = "Option::is_none")]
362 ambient: Option<Capabilities>,
364}
365
366impl 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")]
398pub struct LinuxIOPriority {
400 #[serde(default)]
401 class: IOPriorityClass,
403
404 #[serde(default)]
405 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")]
412pub enum IOPriorityClass {
414 IoprioClassRt,
423 IoprioClassBe,
430 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")]
450pub struct Scheduler {
453 policy: LinuxSchedulerPolicy,
455
456 #[serde(default, skip_serializing_if = "Option::is_none")]
457 nice: Option<i32>,
459
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 priority: Option<i32>,
463
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 flags: Option<Vec<LinuxSchedulerFlag>>,
467
468 #[serde(default, skip_serializing_if = "Option::is_none")]
470 runtime: Option<u64>,
473
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 deadline: Option<u64>,
477
478 #[serde(default, skip_serializing_if = "Option::is_none")]
479 period: Option<u64>,
481}
482
483impl 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")]
501pub enum LinuxSchedulerPolicy {
503 SchedOther,
505 SchedFifo,
507 SchedRr,
509 SchedBatch,
511 SchedIso,
513 SchedIdle,
515 SchedDeadline,
517}
518
519impl 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")]
529pub enum LinuxSchedulerFlag {
531 SchedResetOnFork,
533 SchedFlagReclaim,
535 SchedFlagDLOverrun,
537 SchedFlagKeepPolicy,
539 SchedFlagKeepParams,
541 SchedFlagUtilClampMin,
543 SchedFlagUtilClampMax,
545}
546
547impl 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")]
564pub struct ExecCPUAffinity {
567 #[serde(
568 default,
569 skip_serializing_if = "Option::is_none",
570 deserialize_with = "deserialize"
571 )]
572 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: 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 #[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}