container_device_interface/
container_edits.rs1use std::{collections::HashSet, str::FromStr};
2
3use anyhow::{anyhow, Context, Error, Result};
4use oci_spec::runtime::{self as oci, LinuxDeviceType};
5
6use crate::{
7 container_edits_unix::{device_info_from_path, DeviceType},
8 generate::config::Generator,
9 specs::config::{
10 ContainerEdits as CDIContainerEdits, DeviceNode as CDIDeviceNode, Hook as CDIHook,
11 IntelRdt as CDIIntelRdt, Mount as CDIMount,
12 },
13 utils::merge,
14};
15
16pub trait Validate {
17 fn validate(&self) -> Result<()>;
18}
19
20fn validate_envs(envs: &[String]) -> Result<()> {
21 if envs.iter().any(|v| !v.contains('=')) {
22 return Err(anyhow!("invalid environment variable: {:?}", envs));
23 }
24
25 Ok(())
26}
27
28#[derive(Clone, Default, Debug, Eq, PartialEq, Hash)]
36pub struct ContainerEdits {
37 pub container_edits: CDIContainerEdits,
38}
39
40impl ContainerEdits {
41 pub fn new() -> Self {
42 Self {
43 container_edits: CDIContainerEdits {
44 ..Default::default()
45 },
46 }
47 }
48
49 pub fn apply(&mut self, oci_spec: &mut oci::Spec) -> Result<()> {
52 let mut spec_gen: Generator = Generator::spec_gen(Some(oci_spec.clone()));
53
54 if let Some(envs) = &self.container_edits.env {
55 if !envs.is_empty() {
56 spec_gen.add_multiple_process_env(envs);
57 }
58 }
59
60 if let Some(device_nodes) = &self.container_edits.device_nodes {
61 for d in device_nodes {
62 let mut dn: DeviceNode = DeviceNode { node: d.clone() };
63
64 dn.fill_missing_info()
65 .context("filling missing info failed.")?;
66
67 let d = &dn.node;
68 let mut dev = dn.node.to_oci()?;
69 if let Some(process) = oci_spec.process_mut() {
70 let user = process.user_mut();
71 let gid = user.gid();
72 if gid > 0 {
73 dev.set_gid(Some(gid));
74 }
75
76 let uid = user.uid();
77 if uid > 0 {
78 dev.set_gid(Some(uid));
79 }
80 }
81
82 let dev_typ = dev.typ();
83 let typs = [LinuxDeviceType::B, LinuxDeviceType::C];
84 if typs.contains(&dev_typ) {
85 let perms = "rwm".to_owned();
86 let dev_access = if let Some(permissions) = &d.permissions {
87 permissions
88 } else {
89 &perms
90 };
91
92 let major = dev.major();
93 let minor = dev.minor();
94 spec_gen.add_linux_resources_device(
95 true,
96 dev_typ,
97 Some(major),
98 Some(minor),
99 Some(dev_access.clone()),
100 );
101 }
102
103 spec_gen.remove_device(&dev.path().display().to_string());
104 spec_gen.add_device(dev.clone());
105 }
106 }
107
108 if let Some(mounts) = &self.container_edits.mounts {
109 for m in mounts {
110 spec_gen.remove_mount(&m.container_path);
111 spec_gen.add_mount(m.to_oci()?);
112 }
113 }
114
115 if let Some(hooks) = &self.container_edits.hooks {
116 for h in hooks {
117 let hook_name = HookName::from_str(&h.hook_name)
118 .context(format!("no such hook with name: {:?}", &h.hook_name))?;
119 match hook_name {
120 HookName::Prestart => spec_gen.add_prestart_hook(h.to_oci()?),
121 HookName::CreateRuntime => spec_gen.add_createruntime_hook(h.to_oci()?),
122 HookName::CreateContainer => spec_gen.add_createcontainer_hook(h.to_oci()?),
123 HookName::StartContainer => spec_gen.add_startcontainer_hook(h.to_oci()?),
124 HookName::Poststart => spec_gen.add_poststart_hook(h.to_oci()?),
125 HookName::Poststop => spec_gen.add_poststop_hook(h.to_oci()?),
126 }
127 }
128 }
129
130 if let Some(intel_rdt) = &self.container_edits.intel_rdt {
131 if let Some(clos_id) = &intel_rdt.clos_id {
132 spec_gen.set_linux_intel_rdt_clos_id(clos_id.to_string());
133 }
135 }
136
137 if let Some(additional_gids) = &self.container_edits.additional_gids {
138 for gid in additional_gids {
139 if *gid > 0 {
140 spec_gen.add_process_additional_gid(*gid);
141 }
142 }
143 }
144
145 if let Some(ref spec) = spec_gen.config {
146 oci_spec.set_linux(spec.linux().clone());
147 oci_spec.set_mounts(spec.mounts().clone());
148 oci_spec.set_annotations(spec.annotations().clone());
149 oci_spec.set_hooks(spec.hooks().clone());
150 oci_spec.set_process(spec.process().clone());
151 }
152
153 Ok(())
154 }
155
156 pub fn append(&mut self, o: ContainerEdits) -> Result<()> {
158 let intel_rdt = if o.container_edits.intel_rdt.is_some() {
159 o.container_edits.intel_rdt
160 } else {
161 None
162 };
163
164 let ce = CDIContainerEdits {
165 env: merge(&mut self.container_edits.env, &o.container_edits.env),
166 device_nodes: merge(
167 &mut self.container_edits.device_nodes,
168 &o.container_edits.device_nodes,
169 ),
170 hooks: merge(&mut self.container_edits.hooks, &o.container_edits.hooks),
171 mounts: merge(&mut self.container_edits.mounts, &o.container_edits.mounts),
172 intel_rdt,
173 additional_gids: merge(
174 &mut self.container_edits.additional_gids,
175 &o.container_edits.additional_gids,
176 ),
177 };
178
179 self.container_edits = ce;
180
181 Ok(())
182 }
183}
184
185impl Validate for ContainerEdits {
187 fn validate(&self) -> Result<()> {
188 if let Some(envs) = &self.container_edits.env {
189 validate_envs(envs)
190 .context(format!("invalid container edits with envs: {:?}", envs))?;
191 }
192 if let Some(devices) = &self.container_edits.device_nodes {
193 for d in devices {
194 let dn = DeviceNode { node: d.clone() };
195 dn.validate()
196 .context(format!("invalid container edits with device: {:?}", &d))?;
197 }
198 }
199 if let Some(hooks) = &self.container_edits.hooks {
200 for h in hooks {
201 let hook = Hook { hook: h.clone() };
202 hook.validate()
203 .context(format!("invalid container edits with hook: {:?}", &h))?;
204 }
205 }
206 if let Some(mounts) = &self.container_edits.mounts {
207 for m in mounts {
208 let mnt = Mount { mount: m.clone() };
209 mnt.validate()
210 .context(format!("invalid container edits with mount: {:?}", &m))?;
211 }
212 }
213 if let Some(irdt) = &self.container_edits.intel_rdt {
214 let i_rdt = IntelRdt {
215 intel_rdt: irdt.clone(),
216 };
217 i_rdt
218 .validate()
219 .context(format!("invalid container edits with mount: {:?}", irdt))?;
220 }
221
222 Ok(())
223 }
224}
225
226pub struct DeviceNode {
228 pub node: CDIDeviceNode,
229}
230
231impl DeviceNode {
232 pub fn fill_missing_info(&mut self) -> Result<()> {
233 let host_path = self
234 .node
235 .host_path
236 .as_deref()
237 .unwrap_or_else(|| &self.node.path);
238
239 if let Some(device_type) = self.node.r#type.as_deref() {
240 if self.node.major.is_some() || device_type == DeviceType::Fifo.to_string() {
241 return Ok(());
242 }
243 }
244
245 let (dev_type, major, minor) = device_info_from_path(host_path)?;
246 match self.node.r#type.as_deref() {
247 None => self.node.r#type = Some(dev_type),
248 Some(node_type) if node_type != dev_type => {
249 return Err(anyhow!(
250 "CDI device ({}, {}), host type mismatch ({}, {})",
251 self.node.path,
252 host_path,
253 node_type,
254 dev_type
255 ));
256 }
257 _ => {}
258 }
259
260 if self.node.major.is_none()
261 && self.node.r#type.as_deref() != Some(&DeviceType::Fifo.to_string())
262 {
263 self.node.major = Some(major);
264 self.node.minor = Some(minor);
265 }
266
267 Ok(())
268 }
269}
270
271impl Validate for DeviceNode {
272 fn validate(&self) -> Result<()> {
273 let typs = vec!["b", "c", "u", "p", ""];
274 let valid_typs: HashSet<&str> = typs.into_iter().collect();
275
276 if self.node.path.is_empty() {
277 return Err(anyhow!("invalid (empty) device path"));
278 }
279
280 if let Some(typ) = &self.node.r#type {
281 if valid_typs.contains(&typ.as_str()) {
282 return Err(anyhow!(
283 "device {:?}: invalid type {:?}",
284 self.node.path,
285 typ
286 ));
287 }
288 }
289
290 if let Some(perms) = &self.node.permissions {
291 if !perms.chars().all(|c| matches!(c, 'r' | 'w' | 'm')) {
292 return Err(anyhow!(
293 "device {}: invalid permissions {}",
294 self.node.path,
295 perms
296 ));
297 }
298 }
299
300 Ok(())
301 }
302}
303
304#[derive(Debug, PartialEq, Eq, Hash)]
305enum HookName {
306 Prestart,
307 CreateRuntime,
308 CreateContainer,
309 StartContainer,
310 Poststart,
311 Poststop,
312}
313
314impl FromStr for HookName {
315 type Err = Error;
316 fn from_str(s: &str) -> Result<Self, Self::Err> {
317 match s {
318 "prestart" => Ok(Self::Prestart),
319 "createRuntime" => Ok(Self::CreateRuntime),
320 "createContainer" => Ok(Self::CreateContainer),
321 "startContainer" => Ok(Self::StartContainer),
322 "poststart" => Ok(Self::Poststart),
323 "poststop" => Ok(Self::Poststop),
324 _ => Err(anyhow!("no such hook")),
325 }
326 }
327}
328
329struct Hook {
330 hook: CDIHook,
331}
332
333impl Validate for Hook {
334 fn validate(&self) -> Result<()> {
335 HookName::from_str(&self.hook.hook_name)
336 .context(anyhow!("invalid hook name: {:?}", self.hook.hook_name))?;
337
338 if self.hook.path.is_empty() {
339 return Err(anyhow!(
340 "invalid hook {:?} with empty path",
341 self.hook.hook_name
342 ));
343 }
344 if let Some(envs) = &self.hook.env {
345 validate_envs(envs)
346 .context(anyhow!("hook {:?} with invalid env", &self.hook.hook_name))?;
347 }
348
349 Ok(())
350 }
351}
352
353struct Mount {
354 mount: CDIMount,
355}
356
357impl Validate for Mount {
358 fn validate(&self) -> Result<()> {
359 if self.mount.host_path.is_empty() {
360 return Err(anyhow!("invalid mount, empty host path"));
361 }
362
363 if self.mount.container_path.is_empty() {
364 return Err(anyhow!("invalid mount, empty container path"));
365 }
366
367 Ok(())
368 }
369}
370
371struct IntelRdt {
372 intel_rdt: CDIIntelRdt,
373}
374
375impl Validate for IntelRdt {
376 fn validate(&self) -> Result<()> {
377 if let Some(ref clos_id) = self.intel_rdt.clos_id {
378 if clos_id.len() >= 4096
379 || clos_id == "."
380 || clos_id == ".."
381 || clos_id.contains(&['/', '\n'][..])
382 {
383 return Err(anyhow!("invalid clos id".to_string()));
384 }
385 }
386
387 Ok(())
388 }
389}