1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-08 08:36:15 +02:00
youki/crates/container/src/rootfs/device.rs

263 lines
7.5 KiB
Rust

use super::utils::to_sflag;
use crate::syscall::{syscall::create_syscall, Syscall};
use crate::utils::{self, PathBufExt};
use anyhow::{bail, Context, Result};
use nix::{
fcntl::{open, OFlag},
mount::MsFlags,
sys::stat::{umask, Mode},
unistd::{close, Gid, Uid},
};
use oci_spec::runtime::LinuxDevice;
use std::path::{Path, PathBuf};
pub struct Device {
syscall: Box<dyn Syscall>,
}
impl Default for Device {
fn default() -> Self {
Self::new()
}
}
impl Device {
pub fn new() -> Device {
Device {
syscall: create_syscall(),
}
}
pub fn create_devices<'a, I>(&self, rootfs: &Path, devices: I, bind: bool) -> Result<()>
where
I: IntoIterator<Item = &'a LinuxDevice>,
{
let old_mode = umask(Mode::from_bits_truncate(0o000));
devices
.into_iter()
.map(|dev| {
if !dev.path().starts_with("/dev") {
bail!("{} is not a valid device path", dev.path().display());
}
if bind {
self.bind_dev(rootfs, dev)
} else {
self.mknod_dev(rootfs, dev)
}
})
.collect::<Result<Vec<_>>>()?;
umask(old_mode);
Ok(())
}
fn bind_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> {
let full_container_path = create_container_dev_path(rootfs, dev)
.with_context(|| format!("could not create container path for device {:?}", dev))?;
let fd = open(
&full_container_path,
OFlag::O_RDWR | OFlag::O_CREAT,
Mode::from_bits_truncate(0o644),
)?;
close(fd)?;
self.syscall.mount(
Some(dev.path()),
&full_container_path,
Some("bind"),
MsFlags::MS_BIND,
None,
)?;
Ok(())
}
fn mknod_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> {
fn makedev(major: i64, minor: i64) -> u64 {
((minor & 0xff)
| ((major & 0xfff) << 8)
| ((minor & !0xff) << 12)
| ((major & !0xfff) << 32)) as u64
}
let full_container_path = create_container_dev_path(rootfs, dev)
.with_context(|| format!("could not create container path for device {:?}", dev))?;
self.syscall.mknod(
&full_container_path,
to_sflag(dev.typ()),
Mode::from_bits_truncate(dev.file_mode().unwrap_or(0)),
makedev(dev.major(), dev.minor()),
)?;
self.syscall.chown(
&full_container_path,
dev.uid().map(Uid::from_raw),
dev.gid().map(Gid::from_raw),
)?;
Ok(())
}
}
fn create_container_dev_path(rootfs: &Path, dev: &LinuxDevice) -> Result<PathBuf> {
let relative_dev_path = dev
.path()
.as_relative()
.with_context(|| format!("could not convert {:?} to relative path", dev.path()))?;
let full_container_path = utils::secure_join(rootfs, relative_dev_path)
.with_context(|| format!("could not join {:?} with {:?}", rootfs, dev.path()))?;
crate::utils::create_dir_all(
&full_container_path
.parent()
.unwrap_or_else(|| Path::new("")),
)?;
Ok(full_container_path)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syscall::test::{ChownArgs, MknodArgs, MountArgs, TestHelperSyscall};
use crate::utils::TempDir;
use nix::{
sys::stat::SFlag,
unistd::{Gid, Uid},
};
use oci_spec::runtime::{LinuxDeviceBuilder, LinuxDeviceType};
use std::path::PathBuf;
#[test]
fn test_bind_dev() {
let tmp_dir = TempDir::new("/tmp/test_bind_dev").unwrap();
let device = Device::new();
assert!(device
.bind_dev(
tmp_dir.path(),
&LinuxDeviceBuilder::default()
.path(PathBuf::from("/null"))
.build()
.unwrap(),
)
.is_ok());
let want = MountArgs {
source: Some(PathBuf::from("/null")),
target: tmp_dir.path().join("null"),
fstype: Some("bind".to_string()),
flags: MsFlags::MS_BIND,
data: None,
};
let got = &device
.syscall
.as_any()
.downcast_ref::<TestHelperSyscall>()
.unwrap()
.get_mount_args()[0];
assert_eq!(want, *got);
}
#[test]
fn test_mknod_dev() {
let tmp_dir = TempDir::new("/tmp/test_mknod_dev").unwrap();
let device = Device::new();
assert!(device
.mknod_dev(
tmp_dir.path(),
&LinuxDeviceBuilder::default()
.path(PathBuf::from("/null"))
.major(1)
.minor(3)
.typ(LinuxDeviceType::C)
.file_mode(0o644u32)
.uid(1000u32)
.gid(1000u32)
.build()
.unwrap(),
)
.is_ok());
let want_mknod = MknodArgs {
path: tmp_dir.path().join("null"),
kind: SFlag::S_IFCHR,
perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH,
dev: 259,
};
let got_mknod = &device
.syscall
.as_any()
.downcast_ref::<TestHelperSyscall>()
.unwrap()
.get_mknod_args()[0];
assert_eq!(want_mknod, *got_mknod);
let want_chown = ChownArgs {
path: tmp_dir.path().join("null"),
owner: Some(Uid::from_raw(1000)),
group: Some(Gid::from_raw(1000)),
};
let got_chown = &device
.syscall
.as_any()
.downcast_ref::<TestHelperSyscall>()
.unwrap()
.get_chown_args()[0];
assert_eq!(want_chown, *got_chown);
}
#[test]
fn test_create_devices() {
let tmp_dir = TempDir::new("/tmp/test_create_devices").unwrap();
let device = Device::new();
let devices = vec![LinuxDeviceBuilder::default()
.path(PathBuf::from("/dev/null"))
.major(1)
.minor(3)
.typ(LinuxDeviceType::C)
.file_mode(0o644u32)
.uid(1000u32)
.gid(1000u32)
.build()
.unwrap()];
assert!(device
.create_devices(tmp_dir.path(), &devices, true)
.is_ok());
let want = MountArgs {
source: Some(PathBuf::from("/dev/null")),
target: tmp_dir.path().join("dev/null"),
fstype: Some("bind".to_string()),
flags: MsFlags::MS_BIND,
data: None,
};
let got = &device
.syscall
.as_any()
.downcast_ref::<TestHelperSyscall>()
.unwrap()
.get_mount_args()[0];
assert_eq!(want, *got);
assert!(device
.create_devices(tmp_dir.path(), &devices, false)
.is_ok());
let want = MknodArgs {
path: tmp_dir.path().join("dev/null"),
kind: SFlag::S_IFCHR,
perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH,
dev: 259,
};
let got = &device
.syscall
.as_any()
.downcast_ref::<TestHelperSyscall>()
.unwrap()
.get_mknod_args()[0];
assert_eq!(want, *got);
}
}