mirror of
https://github.com/containers/youki
synced 2024-05-08 08:36:15 +02:00
263 lines
7.5 KiB
Rust
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);
|
|
}
|
|
}
|