Skip to main content

libcontainer/rootfs/
utils.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3
4use nix::mount::MsFlags;
5use nix::sys::stat::SFlag;
6use oci_spec::runtime::{LinuxDevice, LinuxDeviceBuilder, LinuxDeviceType, Mount};
7
8use super::mount::MountError;
9use crate::syscall::linux::{self, MountOption, MountRecursive};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct MountOptionConfig {
13    /// Mount Flags.
14    pub flags: MsFlags,
15
16    /// Mount data options applied to the mount (e.g. `lowerdir=...`).
17    pub data: Vec<String>,
18
19    /// RecAttr represents mount properties to be applied recursively.
20    pub rec_attr: Option<linux::MountAttr>,
21}
22
23pub fn default_devices() -> Vec<LinuxDevice> {
24    vec![
25        LinuxDeviceBuilder::default()
26            .path(PathBuf::from("/dev/null"))
27            .typ(LinuxDeviceType::C)
28            .major(1)
29            .minor(3)
30            .file_mode(0o0666u32)
31            .build()
32            .unwrap(),
33        LinuxDeviceBuilder::default()
34            .path(PathBuf::from("/dev/zero"))
35            .typ(LinuxDeviceType::C)
36            .major(1)
37            .minor(5)
38            .file_mode(0o0666u32)
39            .build()
40            .unwrap(),
41        LinuxDeviceBuilder::default()
42            .path(PathBuf::from("/dev/full"))
43            .typ(LinuxDeviceType::C)
44            .major(1)
45            .minor(7)
46            .file_mode(0o0666u32)
47            .build()
48            .unwrap(),
49        LinuxDeviceBuilder::default()
50            .path(PathBuf::from("/dev/tty"))
51            .typ(LinuxDeviceType::C)
52            .major(5)
53            .minor(0)
54            .file_mode(0o0666u32)
55            .build()
56            .unwrap(),
57        LinuxDeviceBuilder::default()
58            .path(PathBuf::from("/dev/urandom"))
59            .typ(LinuxDeviceType::C)
60            .major(1)
61            .minor(9)
62            .file_mode(0o0666u32)
63            .build()
64            .unwrap(),
65        LinuxDeviceBuilder::default()
66            .path(PathBuf::from("/dev/random"))
67            .typ(LinuxDeviceType::C)
68            .major(1)
69            .minor(8)
70            .file_mode(0o0666u32)
71            .build()
72            .unwrap(),
73    ]
74}
75
76pub fn to_sflag(dev_type: LinuxDeviceType) -> SFlag {
77    match dev_type {
78        LinuxDeviceType::A => SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO,
79        LinuxDeviceType::B => SFlag::S_IFBLK,
80        LinuxDeviceType::C | LinuxDeviceType::U => SFlag::S_IFCHR,
81        LinuxDeviceType::P => SFlag::S_IFIFO,
82    }
83}
84
85pub fn parse_mount(m: &Mount) -> std::result::Result<MountOptionConfig, MountError> {
86    let mut flags = MsFlags::empty();
87    let mut data = Vec::new();
88    let mut mount_attr: Option<linux::MountAttr> = None;
89
90    if let Some(options) = &m.options() {
91        for option in options {
92            if let Ok(mount_attr_option) = linux::MountRecursive::from_str(option.as_str()) {
93                // Some options aren't corresponding to the mount flags.
94                // These options need `AT_RECURSIVE` options.
95                // ref: https://github.com/opencontainers/runtime-spec/blob/main/config.md#linux-mount-options
96                let (is_clear, flag) = match mount_attr_option {
97                    MountRecursive::Rdonly(is_clear, flag) => (is_clear, flag),
98                    MountRecursive::Nosuid(is_clear, flag) => (is_clear, flag),
99                    MountRecursive::Nodev(is_clear, flag) => (is_clear, flag),
100                    MountRecursive::Noexec(is_clear, flag) => (is_clear, flag),
101                    MountRecursive::Atime(is_clear, flag) => (is_clear, flag),
102                    MountRecursive::Relatime(is_clear, flag) => (is_clear, flag),
103                    MountRecursive::Noatime(is_clear, flag) => (is_clear, flag),
104                    MountRecursive::StrictAtime(is_clear, flag) => (is_clear, flag),
105                    MountRecursive::NoDiratime(is_clear, flag) => (is_clear, flag),
106                    MountRecursive::Nosymfollow(is_clear, flag) => (is_clear, flag),
107                };
108
109                if mount_attr.is_none() {
110                    mount_attr = Some(linux::MountAttr {
111                        attr_set: 0,
112                        attr_clr: 0,
113                        propagation: 0,
114                        userns_fd: 0,
115                    });
116                }
117
118                if let Some(mount_attr) = &mut mount_attr {
119                    if is_clear {
120                        mount_attr.attr_clr |= flag;
121                    } else {
122                        mount_attr.attr_set |= flag;
123                        if flag & linux::MOUNT_ATTR__ATIME == flag {
124                            // https://man7.org/linux/man-pages/man2/mount_setattr.2.html
125                            // cannot simply specify the access-time setting in attr_set, but must
126                            // also include MOUNT_ATTR__ATIME in the attr_clr field.
127                            mount_attr.attr_clr |= linux::MOUNT_ATTR__ATIME;
128                        }
129                    }
130                }
131                continue;
132            }
133
134            if let Some((is_clear, flag)) = match MountOption::from_str(option.as_ref()) {
135                Ok(v) => match v {
136                    MountOption::Defaults(is_clear, flag) => Some((is_clear, flag)),
137                    MountOption::Ro(is_clear, flag) => Some((is_clear, flag)),
138                    MountOption::Rw(is_clear, flag) => Some((is_clear, flag)),
139                    MountOption::Suid(is_clear, flag) => Some((is_clear, flag)),
140                    MountOption::Nosuid(is_clear, flag) => Some((is_clear, flag)),
141                    MountOption::Dev(is_clear, flag) => Some((is_clear, flag)),
142                    MountOption::Nodev(is_clear, flag) => Some((is_clear, flag)),
143                    MountOption::Exec(is_clear, flag) => Some((is_clear, flag)),
144                    MountOption::Noexec(is_clear, flag) => Some((is_clear, flag)),
145                    MountOption::Sync(is_clear, flag) => Some((is_clear, flag)),
146                    MountOption::Async(is_clear, flag) => Some((is_clear, flag)),
147                    MountOption::Dirsync(is_clear, flag) => Some((is_clear, flag)),
148                    MountOption::Remount(is_clear, flag) => Some((is_clear, flag)),
149                    MountOption::Mand(is_clear, flag) => Some((is_clear, flag)),
150                    MountOption::Nomand(is_clear, flag) => Some((is_clear, flag)),
151                    MountOption::Atime(is_clear, flag) => Some((is_clear, flag)),
152                    MountOption::Noatime(is_clear, flag) => Some((is_clear, flag)),
153                    MountOption::Diratime(is_clear, flag) => Some((is_clear, flag)),
154                    MountOption::Nodiratime(is_clear, flag) => Some((is_clear, flag)),
155                    MountOption::Bind(is_clear, flag) => Some((is_clear, flag)),
156                    MountOption::Rbind(is_clear, flag) => Some((is_clear, flag)),
157                    MountOption::Unbindable(is_clear, flag) => Some((is_clear, flag)),
158                    MountOption::Runbindable(is_clear, flag) => Some((is_clear, flag)),
159                    MountOption::Private(is_clear, flag) => Some((is_clear, flag)),
160                    MountOption::Rprivate(is_clear, flag) => Some((is_clear, flag)),
161                    MountOption::Shared(is_clear, flag) => Some((is_clear, flag)),
162                    MountOption::Rshared(is_clear, flag) => Some((is_clear, flag)),
163                    MountOption::Slave(is_clear, flag) => Some((is_clear, flag)),
164                    MountOption::Rslave(is_clear, flag) => Some((is_clear, flag)),
165                    MountOption::Relatime(is_clear, flag) => Some((is_clear, flag)),
166                    MountOption::Norelatime(is_clear, flag) => Some((is_clear, flag)),
167                    MountOption::Strictatime(is_clear, flag) => Some((is_clear, flag)),
168                    MountOption::Nostrictatime(is_clear, flag) => Some((is_clear, flag)),
169                },
170                Err(unknown) => {
171                    if unknown == "idmap" || unknown == "ridmap" {
172                        return Err(MountError::UnsupportedMountOption(unknown));
173                    }
174                    None
175                }
176            } {
177                if is_clear {
178                    flags &= !flag;
179                } else {
180                    flags |= flag;
181                }
182                continue;
183            }
184
185            data.push(option.as_str());
186        }
187    }
188    Ok(MountOptionConfig {
189        flags,
190        data: data.into_iter().map(|s| s.to_string()).collect(),
191        rec_attr: mount_attr,
192    })
193}
194
195#[cfg(test)]
196mod tests {
197    use anyhow::Result;
198    use oci_spec::runtime::MountBuilder;
199
200    use super::*;
201    use crate::syscall::linux::MountAttr;
202
203    #[test]
204    fn test_to_sflag() {
205        assert_eq!(
206            SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO,
207            to_sflag(LinuxDeviceType::A)
208        );
209        assert_eq!(SFlag::S_IFBLK, to_sflag(LinuxDeviceType::B));
210        assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::C));
211        assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::U));
212        assert_eq!(SFlag::S_IFIFO, to_sflag(LinuxDeviceType::P));
213    }
214
215    #[test]
216    fn test_parse_mount() -> Result<()> {
217        let mount_option_config = parse_mount(
218            &MountBuilder::default()
219                .destination(PathBuf::from("/proc"))
220                .typ("proc")
221                .source(PathBuf::from("proc"))
222                .build()?,
223        )?;
224        assert_eq!(
225            MountOptionConfig {
226                flags: MsFlags::empty(),
227                data: vec![],
228                rec_attr: None,
229            },
230            mount_option_config
231        );
232
233        let mount_option_config = parse_mount(
234            &MountBuilder::default()
235                .destination(PathBuf::from("/dev"))
236                .typ("tmpfs")
237                .source(PathBuf::from("tmpfs"))
238                .options(vec![
239                    "nosuid".to_string(),
240                    "strictatime".to_string(),
241                    "mode=755".to_string(),
242                    "size=65536k".to_string(),
243                ])
244                .build()?,
245        )?;
246        assert_eq!(
247            MountOptionConfig {
248                flags: MsFlags::MS_NOSUID | MsFlags::MS_STRICTATIME,
249                data: vec!["mode=755".to_string(), "size=65536k".to_string()],
250                rec_attr: None,
251            },
252            mount_option_config
253        );
254
255        let mount_option_config = parse_mount(
256            &MountBuilder::default()
257                .destination(PathBuf::from("/dev/pts"))
258                .typ("devpts")
259                .source(PathBuf::from("devpts"))
260                .options(vec![
261                    "nosuid".to_string(),
262                    "noexec".to_string(),
263                    "newinstance".to_string(),
264                    "ptmxmode=0666".to_string(),
265                    "mode=0620".to_string(),
266                    "gid=5".to_string(),
267                ])
268                .build()
269                .unwrap(),
270        )?;
271        assert_eq!(
272            MountOptionConfig {
273                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC,
274                data: vec![
275                    "newinstance".to_string(),
276                    "ptmxmode=0666".to_string(),
277                    "mode=0620".to_string(),
278                    "gid=5".to_string()
279                ],
280                rec_attr: None
281            },
282            mount_option_config
283        );
284
285        let mount_option_config = parse_mount(
286            &MountBuilder::default()
287                .destination(PathBuf::from("/dev/shm"))
288                .typ("tmpfs")
289                .source(PathBuf::from("shm"))
290                .options(vec![
291                    "nosuid".to_string(),
292                    "noexec".to_string(),
293                    "nodev".to_string(),
294                    "mode=1777".to_string(),
295                    "size=65536k".to_string(),
296                ])
297                .build()?,
298        )?;
299        assert_eq!(
300            MountOptionConfig {
301                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV,
302                data: vec!["mode=1777".to_string(), "size=65536k".to_string()],
303                rec_attr: None
304            },
305            mount_option_config
306        );
307
308        let mount_option_config = parse_mount(
309            &MountBuilder::default()
310                .destination(PathBuf::from("/dev/mqueue"))
311                .typ("mqueue")
312                .source(PathBuf::from("mqueue"))
313                .options(vec![
314                    "nosuid".to_string(),
315                    "noexec".to_string(),
316                    "nodev".to_string(),
317                ])
318                .build()
319                .unwrap(),
320        )?;
321        assert_eq!(
322            MountOptionConfig {
323                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV,
324                data: vec![],
325                rec_attr: None
326            },
327            mount_option_config
328        );
329
330        let mount_option_config = parse_mount(
331            &MountBuilder::default()
332                .destination(PathBuf::from("/sys"))
333                .typ("sysfs")
334                .source(PathBuf::from("sysfs"))
335                .options(vec![
336                    "nosuid".to_string(),
337                    "noexec".to_string(),
338                    "nodev".to_string(),
339                    "ro".to_string(),
340                ])
341                .build()?,
342        )?;
343        assert_eq!(
344            MountOptionConfig {
345                flags: MsFlags::MS_NOSUID
346                    | MsFlags::MS_NOEXEC
347                    | MsFlags::MS_NODEV
348                    | MsFlags::MS_RDONLY,
349                data: vec![],
350                rec_attr: None,
351            },
352            mount_option_config
353        );
354
355        let mount_option_config = parse_mount(
356            &MountBuilder::default()
357                .destination(PathBuf::from("/sys/fs/cgroup"))
358                .typ("cgroup")
359                .source(PathBuf::from("cgroup"))
360                .options(vec![
361                    "nosuid".to_string(),
362                    "noexec".to_string(),
363                    "nodev".to_string(),
364                    "relatime".to_string(),
365                    "ro".to_string(),
366                ])
367                .build()?,
368        )?;
369        assert_eq!(
370            MountOptionConfig {
371                flags: MsFlags::MS_NOSUID
372                    | MsFlags::MS_NOEXEC
373                    | MsFlags::MS_NODEV
374                    | MsFlags::MS_RDONLY
375                    | MsFlags::MS_RELATIME,
376                data: vec![],
377                rec_attr: None
378            },
379            mount_option_config,
380        );
381
382        // this case is just for coverage purpose
383        let mount_option_config = parse_mount(
384            &MountBuilder::default()
385                .options(vec![
386                    "defaults".to_string(),
387                    "ro".to_string(),
388                    "rw".to_string(),
389                    "suid".to_string(),
390                    "nosuid".to_string(),
391                    "dev".to_string(),
392                    "nodev".to_string(),
393                    "exec".to_string(),
394                    "noexec".to_string(),
395                    "sync".to_string(),
396                    "async".to_string(),
397                    "dirsync".to_string(),
398                    "remount".to_string(),
399                    "mand".to_string(),
400                    "nomand".to_string(),
401                    "atime".to_string(),
402                    "noatime".to_string(),
403                    "diratime".to_string(),
404                    "nodiratime".to_string(),
405                    "bind".to_string(),
406                    "rbind".to_string(),
407                    "unbindable".to_string(),
408                    "runbindable".to_string(),
409                    "private".to_string(),
410                    "rprivate".to_string(),
411                    "shared".to_string(),
412                    "rshared".to_string(),
413                    "slave".to_string(),
414                    "rslave".to_string(),
415                    "relatime".to_string(),
416                    "norelatime".to_string(),
417                    "strictatime".to_string(),
418                    "nostrictatime".to_string(),
419                ])
420                .build()?,
421        )?;
422        assert_eq!(
423            MountOptionConfig {
424                flags: MsFlags::MS_NOSUID
425                    | MsFlags::MS_NODEV
426                    | MsFlags::MS_NOEXEC
427                    | MsFlags::MS_REMOUNT
428                    | MsFlags::MS_DIRSYNC
429                    | MsFlags::MS_NOATIME
430                    | MsFlags::MS_NODIRATIME
431                    | MsFlags::MS_BIND
432                    | MsFlags::MS_UNBINDABLE,
433                data: vec![],
434                rec_attr: None,
435            },
436            mount_option_config
437        );
438
439        // this case is just for coverage purpose
440        let mount_option_config = parse_mount(
441            &MountBuilder::default()
442                .options(vec![
443                    "rro".to_string(),
444                    "rrw".to_string(),
445                    "rnosuid".to_string(),
446                    "rsuid".to_string(),
447                    "rnodev".to_string(),
448                    "rdev".to_string(),
449                    "rnoexec".to_string(),
450                    "rexec".to_string(),
451                    "rnodiratime".to_string(),
452                    "rdiratime".to_string(),
453                    "rrelatime".to_string(),
454                    "rnorelatime".to_string(),
455                    "rnoatime".to_string(),
456                    "ratime".to_string(),
457                    "rstrictatime".to_string(),
458                    "rnostrictatime".to_string(),
459                    "rnosymfollow".to_string(),
460                    "rsymfollow".to_string(),
461                ])
462                .build()?,
463        )?;
464        assert_eq!(
465            MountOptionConfig {
466                flags: MsFlags::empty(),
467                data: vec![],
468                rec_attr: Some(MountAttr::all())
469            },
470            mount_option_config
471        );
472
473        Ok(())
474    }
475}