Skip to main content

oci_spec/runtime/
miscellaneous.rs

1use crate::error::OciSpecError;
2use crate::runtime::LinuxIdMapping;
3use derive_builder::Builder;
4use getset::{CopyGetters, Getters, MutGetters, Setters};
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8#[derive(
9    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,
10)]
11#[builder(
12    default,
13    pattern = "owned",
14    setter(into, strip_option),
15    build_fn(error = "OciSpecError")
16)]
17/// Root contains information about the container's root filesystem on the
18/// host.
19pub struct Root {
20    /// Path is the absolute path to the container's root filesystem.
21    #[serde(default)]
22    #[getset(get = "pub", set = "pub")]
23    path: PathBuf,
24
25    /// Readonly makes the root filesystem for the container readonly before
26    /// the process is executed.
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    #[getset(get_copy = "pub", set = "pub")]
29    readonly: Option<bool>,
30}
31
32/// Default path for container root is "./rootfs" from config.json, with
33/// readonly true
34impl Default for Root {
35    fn default() -> Self {
36        Root {
37            path: PathBuf::from("rootfs"),
38            readonly: true.into(),
39        }
40    }
41}
42
43#[derive(
44    Builder,
45    Clone,
46    Debug,
47    Default,
48    Deserialize,
49    Eq,
50    Getters,
51    MutGetters,
52    Setters,
53    PartialEq,
54    Serialize,
55)]
56#[builder(
57    default,
58    pattern = "owned",
59    setter(into, strip_option),
60    build_fn(error = "OciSpecError", validate = "Self::validate")
61)]
62#[getset(get_mut = "pub", get = "pub", set = "pub")]
63/// Mount specifies a mount for a container.
64pub struct Mount {
65    /// Destination is the absolute path where the mount will be placed in
66    /// the container.
67    destination: PathBuf,
68
69    #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
70    /// Type specifies the mount kind.
71    typ: Option<String>,
72
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    /// Source specifies the source path of the mount.
75    source: Option<PathBuf>,
76
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    /// Options are fstab style mount options.
79    options: Option<Vec<String>>,
80
81    #[serde(
82        default,
83        skip_serializing_if = "Option::is_none",
84        rename = "uidMappings"
85    )]
86    /// UID mappings for ID-mapped mounts (Linux 5.12+).  
87    ///  
88    /// Specifies how to map UIDs from the source filesystem to the destination mount point.  
89    /// This allows changing file ownership without calling chown.  
90    ///  
91    /// **Important**: If specified, gid_mappings MUST also be specified.  
92    /// The mount options SHOULD include "idmap" or "ridmap".  
93    ///  
94    /// See: <https://github.com/opencontainers/runtime-spec/blob/main/config.md#posix-platform-mounts>
95    uid_mappings: Option<Vec<LinuxIdMapping>>,
96
97    #[serde(
98        default,
99        skip_serializing_if = "Option::is_none",
100        rename = "gidMappings"
101    )]
102    /// GID mappings for ID-mapped mounts (Linux 5.12+).
103    ///
104    /// Specifies how to map GIDs from the source filesystem to the destination mount point.
105    /// This allows changing file group ownership without calling chown.
106    ///
107    /// **Important**: If specified, `uid_mappings` MUST also be specified.
108    /// The mount options SHOULD include `"idmap"` or `"ridmap"`.
109    ///
110    /// See: <https://github.com/opencontainers/runtime-spec/blob/main/config.md#posix-platform-mounts>
111    gid_mappings: Option<Vec<LinuxIdMapping>>,
112}
113
114/// utility function to generate default config for mounts.
115pub fn get_default_mounts() -> Vec<Mount> {
116    vec![
117        Mount {
118            destination: PathBuf::from("/proc"),
119            typ: "proc".to_string().into(),
120            source: PathBuf::from("proc").into(),
121            options: None,
122            uid_mappings: None,
123            gid_mappings: None,
124        },
125        Mount {
126            destination: PathBuf::from("/dev"),
127            typ: "tmpfs".to_string().into(),
128            source: PathBuf::from("tmpfs").into(),
129            options: vec![
130                "nosuid".into(),
131                "strictatime".into(),
132                "mode=755".into(),
133                "size=65536k".into(),
134            ]
135            .into(),
136            uid_mappings: None,
137            gid_mappings: None,
138        },
139        Mount {
140            destination: PathBuf::from("/dev/pts"),
141            typ: "devpts".to_string().into(),
142            source: PathBuf::from("devpts").into(),
143            options: vec![
144                "nosuid".into(),
145                "noexec".into(),
146                "newinstance".into(),
147                "ptmxmode=0666".into(),
148                "mode=0620".into(),
149                "gid=5".into(),
150            ]
151            .into(),
152            uid_mappings: None,
153            gid_mappings: None,
154        },
155        Mount {
156            destination: PathBuf::from("/dev/shm"),
157            typ: "tmpfs".to_string().into(),
158            source: PathBuf::from("shm").into(),
159            options: vec![
160                "nosuid".into(),
161                "noexec".into(),
162                "nodev".into(),
163                "mode=1777".into(),
164                "size=65536k".into(),
165            ]
166            .into(),
167            uid_mappings: None,
168            gid_mappings: None,
169        },
170        Mount {
171            destination: PathBuf::from("/dev/mqueue"),
172            typ: "mqueue".to_string().into(),
173            source: PathBuf::from("mqueue").into(),
174            options: vec!["nosuid".into(), "noexec".into(), "nodev".into()].into(),
175            uid_mappings: None,
176            gid_mappings: None,
177        },
178        Mount {
179            destination: PathBuf::from("/sys"),
180            typ: "sysfs".to_string().into(),
181            source: PathBuf::from("sysfs").into(),
182            options: vec![
183                "nosuid".into(),
184                "noexec".into(),
185                "nodev".into(),
186                "ro".into(),
187            ]
188            .into(),
189            uid_mappings: None,
190            gid_mappings: None,
191        },
192        Mount {
193            destination: PathBuf::from("/sys/fs/cgroup"),
194            typ: "cgroup".to_string().into(),
195            source: PathBuf::from("cgroup").into(),
196            options: vec![
197                "nosuid".into(),
198                "noexec".into(),
199                "nodev".into(),
200                "relatime".into(),
201                "ro".into(),
202            ]
203            .into(),
204            uid_mappings: None,
205            gid_mappings: None,
206        },
207    ]
208}
209
210impl MountBuilder {
211    fn validate(&self) -> Result<(), OciSpecError> {
212        let uid_specified = self
213            .uid_mappings
214            .as_ref()
215            .and_then(|v| v.as_ref())
216            .map(|v| !v.is_empty())
217            .unwrap_or(false);
218
219        let gid_specified = self
220            .gid_mappings
221            .as_ref()
222            .and_then(|v| v.as_ref())
223            .map(|v| !v.is_empty())
224            .unwrap_or(false);
225
226        if uid_specified ^ gid_specified {
227            return Err(OciSpecError::Other(
228                "Mount.uidMappings and Mount.gidMappings must be specified together".to_string(),
229            ));
230        }
231
232        Ok(())
233    }
234}
235
236/// utility function to generate default rootless config for mounts.
237// TODO(saschagrunert): remove once clippy does not report this false positive any more. We cannot
238// use `inspect` instead of `map` because we need to mutate the mounts.
239// Ref: https://github.com/rust-lang/rust-clippy/issues/13185
240#[allow(clippy::manual_inspect)]
241pub fn get_rootless_mounts() -> Vec<Mount> {
242    let mut mounts = get_default_mounts();
243    mounts
244        .iter_mut()
245        .find(|m| m.destination.to_string_lossy() == "/dev/pts")
246        .map(|m| {
247            if let Some(opts) = &mut m.options {
248                opts.retain(|o| o != "gid=5")
249            }
250            m
251        });
252    mounts
253        .iter_mut()
254        .find(|m| m.destination.to_string_lossy() == "/sys")
255        .map(|m| {
256            m.typ = Some("none".to_string());
257            m.source = Some("/sys".into());
258            if let Some(o) = m.options.as_mut() {
259                o.push("rbind".to_string())
260            }
261            m
262        });
263    mounts
264}