mirror of
https://github.com/containers/youki
synced 2024-06-25 08:29:45 +02:00
This adds a few missing types and synchronizes them with the implementation in containrs. Optional types are now not required any more which means that all necessary code paths in youki needs to be adapted as well. Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
361 lines
11 KiB
Rust
361 lines
11 KiB
Rust
//! During kernel initialization, a minimal replica of the ramfs filesystem is loaded, called rootfs.
|
|
//! Most systems mount another filesystem over it
|
|
|
|
use std::fs::OpenOptions;
|
|
use std::fs::{canonicalize, create_dir_all, remove_file};
|
|
use std::os::unix::fs::symlink;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
use nix::errno::Errno;
|
|
use nix::fcntl::{open, OFlag};
|
|
use nix::mount::mount as nix_mount;
|
|
use nix::mount::MsFlags;
|
|
use nix::sys::stat::Mode;
|
|
use nix::sys::stat::{mknod, umask};
|
|
use nix::unistd::{chdir, chown, close, getcwd};
|
|
use nix::unistd::{Gid, Uid};
|
|
|
|
use crate::utils::PathBufExt;
|
|
use oci_spec::{LinuxDevice, LinuxDeviceType, Mount, Spec};
|
|
|
|
pub fn prepare_rootfs(spec: &Spec, rootfs: &Path, bind_devices: bool) -> Result<()> {
|
|
let mut flags = MsFlags::MS_REC;
|
|
|
|
let linux = spec.linux.as_ref().context("no linux in spec")?;
|
|
match linux
|
|
.rootfs_propagation
|
|
.as_ref()
|
|
.context("no rootfs_propagation in spec")?
|
|
.as_str()
|
|
{
|
|
"shared" => flags |= MsFlags::MS_SHARED,
|
|
"private" => flags |= MsFlags::MS_PRIVATE,
|
|
"slave" | "" => flags |= MsFlags::MS_SLAVE,
|
|
_ => panic!(),
|
|
}
|
|
|
|
nix_mount(None::<&str>, "/", None::<&str>, flags, None::<&str>)?;
|
|
|
|
log::debug!("mount root fs {:?}", rootfs);
|
|
nix_mount::<Path, Path, str, str>(
|
|
Some(rootfs),
|
|
rootfs,
|
|
None::<&str>,
|
|
MsFlags::MS_BIND | MsFlags::MS_REC,
|
|
None::<&str>,
|
|
)?;
|
|
|
|
for m in spec.mounts.as_ref().context("no mounts in spec")?.iter() {
|
|
let (flags, data) = parse_mount(m);
|
|
let ml = linux
|
|
.mount_label
|
|
.as_ref()
|
|
.context("no mount_label in spec")?;
|
|
if m.typ.as_ref().context("no type in mount spec")? == "cgroup" {
|
|
// skip
|
|
log::warn!("A feature of cgroup is unimplemented.");
|
|
} else if m.destination == PathBuf::from("/dev") {
|
|
mount_to_container(m, rootfs, flags & !MsFlags::MS_RDONLY, &data, ml)?;
|
|
} else {
|
|
mount_to_container(m, rootfs, flags, &data, ml)?;
|
|
}
|
|
}
|
|
|
|
let olddir = getcwd()?;
|
|
chdir(rootfs)?;
|
|
|
|
setup_default_symlinks(rootfs)?;
|
|
create_devices(
|
|
linux.devices.as_ref().context("no devices in spec")?,
|
|
bind_devices,
|
|
)?;
|
|
setup_ptmx(rootfs)?;
|
|
|
|
chdir(&olddir)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn setup_ptmx(rootfs: &Path) -> Result<()> {
|
|
if let Err(e) = remove_file(rootfs.join("dev/ptmx")) {
|
|
if e.kind() != ::std::io::ErrorKind::NotFound {
|
|
bail!("could not delete /dev/ptmx")
|
|
}
|
|
}
|
|
symlink("pts/ptmx", rootfs.join("dev/ptmx"))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn setup_default_symlinks(rootfs: &Path) -> Result<()> {
|
|
if Path::new("/proc/kcore").exists() {
|
|
symlink("/proc/kcore", "dev/kcore")?;
|
|
}
|
|
|
|
let defaults = [
|
|
("/proc/self/fd", "dev/fd"),
|
|
("/proc/self/fd/0", "dev/stdin"),
|
|
("/proc/self/fd/1", "dev/stdout"),
|
|
("/proc/self/fd/2", "dev/stderr"),
|
|
];
|
|
for &(src, dst) in defaults.iter() {
|
|
symlink(src, rootfs.join(dst))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn default_devices() -> Vec<LinuxDevice> {
|
|
vec![
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/null"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 1,
|
|
minor: 3,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/zero"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 1,
|
|
minor: 5,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/full"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 1,
|
|
minor: 7,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/tty"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 5,
|
|
minor: 0,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/urandom"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 1,
|
|
minor: 9,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
LinuxDevice {
|
|
path: PathBuf::from("/dev/random"),
|
|
typ: LinuxDeviceType::C,
|
|
major: 1,
|
|
minor: 8,
|
|
file_mode: Some(0o066),
|
|
uid: None,
|
|
gid: None,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn create_devices(devices: &[LinuxDevice], bind: bool) -> Result<()> {
|
|
let old_mode = umask(Mode::from_bits_truncate(0o000));
|
|
if bind {
|
|
let _ = default_devices()
|
|
.iter()
|
|
.chain(devices)
|
|
.map(|dev| {
|
|
if !dev.path.starts_with("/dev") {
|
|
panic!("{} is not a valid device path", dev.path.display());
|
|
}
|
|
bind_dev(dev)
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
} else {
|
|
default_devices()
|
|
.iter()
|
|
.chain(devices)
|
|
.map(|dev| {
|
|
if !dev.path.starts_with("/dev") {
|
|
panic!("{} is not a valid device path", dev.path.display());
|
|
}
|
|
mknod_dev(dev)
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
}
|
|
umask(old_mode);
|
|
Ok(())
|
|
}
|
|
|
|
fn bind_dev(dev: &LinuxDevice) -> Result<()> {
|
|
let fd = open(
|
|
&dev.path.as_in_container()?,
|
|
OFlag::O_RDWR | OFlag::O_CREAT,
|
|
Mode::from_bits_truncate(0o644),
|
|
)?;
|
|
close(fd)?;
|
|
nix_mount(
|
|
Some(&*dev.path.as_in_container()?),
|
|
&dev.path,
|
|
None::<&str>,
|
|
MsFlags::MS_BIND,
|
|
None::<&str>,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn mknod_dev(dev: &LinuxDevice) -> Result<()> {
|
|
fn makedev(major: i64, minor: i64) -> u64 {
|
|
((minor & 0xff)
|
|
| ((major & 0xfff) << 8)
|
|
| ((minor & !0xff) << 12)
|
|
| ((major & !0xfff) << 32)) as u64
|
|
}
|
|
|
|
mknod(
|
|
&dev.path.as_in_container()?,
|
|
dev.typ.to_sflag()?,
|
|
Mode::from_bits_truncate(dev.file_mode.unwrap_or(0)),
|
|
makedev(dev.major, dev.minor),
|
|
)?;
|
|
chown(
|
|
&dev.path.as_in_container()?,
|
|
dev.uid.map(Uid::from_raw),
|
|
dev.gid.map(Gid::from_raw),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn mount_to_container(
|
|
m: &Mount,
|
|
rootfs: &Path,
|
|
flags: MsFlags,
|
|
data: &str,
|
|
label: &str,
|
|
) -> Result<()> {
|
|
let typ = m.typ.as_ref().context("no type in mount spec")?;
|
|
let d = if !label.is_empty() && typ != "proc" && typ != "sysfs" {
|
|
if data.is_empty() {
|
|
format!("context=\"{}\"", label)
|
|
} else {
|
|
format!("{},context=\"{}\"", data, label)
|
|
}
|
|
} else {
|
|
data.to_string()
|
|
};
|
|
|
|
let dest_for_host = format!(
|
|
"{}{}",
|
|
rootfs.to_string_lossy().into_owned(),
|
|
m.destination.display()
|
|
);
|
|
let dest = Path::new(&dest_for_host);
|
|
|
|
let source = &m.source.as_ref().context("no source in mount spec")?;
|
|
let src = if typ == "bind" {
|
|
let src = canonicalize(source)?;
|
|
let dir = if src.is_file() {
|
|
Path::new(&dest).parent().unwrap()
|
|
} else {
|
|
Path::new(&dest)
|
|
};
|
|
create_dir_all(&dir).unwrap();
|
|
if src.is_file() {
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.open(&dest)
|
|
.unwrap();
|
|
}
|
|
src
|
|
} else {
|
|
create_dir_all(&dest).unwrap();
|
|
PathBuf::from(source)
|
|
};
|
|
|
|
if let Err(errno) = nix_mount(Some(&*src), dest, Some(&*typ.as_str()), flags, Some(&*d)) {
|
|
if !matches!(errno, Errno::EINVAL) {
|
|
bail!("mount of {} failed", m.destination.display());
|
|
}
|
|
nix_mount(Some(&*src), dest, Some(&*typ.as_str()), flags, Some(data))?;
|
|
}
|
|
if flags.contains(MsFlags::MS_BIND)
|
|
&& flags.intersects(
|
|
!(MsFlags::MS_REC
|
|
| MsFlags::MS_REMOUNT
|
|
| MsFlags::MS_BIND
|
|
| MsFlags::MS_PRIVATE
|
|
| MsFlags::MS_SHARED
|
|
| MsFlags::MS_SLAVE),
|
|
)
|
|
{
|
|
nix_mount(
|
|
Some(&*dest),
|
|
&*dest,
|
|
None::<&str>,
|
|
flags | MsFlags::MS_REMOUNT,
|
|
None::<&str>,
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_mount(m: &Mount) -> (MsFlags, String) {
|
|
let mut flags = MsFlags::empty();
|
|
let mut data = Vec::new();
|
|
if let Some(options) = &m.options {
|
|
for s in options {
|
|
if let Some((is_clear, flag)) = match s.as_str() {
|
|
"defaults" => Some((false, MsFlags::empty())),
|
|
"ro" => Some((false, MsFlags::MS_RDONLY)),
|
|
"rw" => Some((true, MsFlags::MS_RDONLY)),
|
|
"suid" => Some((true, MsFlags::MS_NOSUID)),
|
|
"nosuid" => Some((false, MsFlags::MS_NOSUID)),
|
|
"dev" => Some((true, MsFlags::MS_NODEV)),
|
|
"nodev" => Some((false, MsFlags::MS_NODEV)),
|
|
"exec" => Some((true, MsFlags::MS_NOEXEC)),
|
|
"noexec" => Some((false, MsFlags::MS_NOEXEC)),
|
|
"sync" => Some((false, MsFlags::MS_SYNCHRONOUS)),
|
|
"async" => Some((true, MsFlags::MS_SYNCHRONOUS)),
|
|
"dirsync" => Some((false, MsFlags::MS_DIRSYNC)),
|
|
"remount" => Some((false, MsFlags::MS_REMOUNT)),
|
|
"mand" => Some((false, MsFlags::MS_MANDLOCK)),
|
|
"nomand" => Some((true, MsFlags::MS_MANDLOCK)),
|
|
"atime" => Some((true, MsFlags::MS_NOATIME)),
|
|
"noatime" => Some((false, MsFlags::MS_NOATIME)),
|
|
"diratime" => Some((true, MsFlags::MS_NODIRATIME)),
|
|
"nodiratime" => Some((false, MsFlags::MS_NODIRATIME)),
|
|
"bind" => Some((false, MsFlags::MS_BIND)),
|
|
"rbind" => Some((false, MsFlags::MS_BIND | MsFlags::MS_REC)),
|
|
"unbindable" => Some((false, MsFlags::MS_UNBINDABLE)),
|
|
"runbindable" => Some((false, MsFlags::MS_UNBINDABLE | MsFlags::MS_REC)),
|
|
"private" => Some((false, MsFlags::MS_PRIVATE)),
|
|
"rprivate" => Some((false, MsFlags::MS_PRIVATE | MsFlags::MS_REC)),
|
|
"shared" => Some((false, MsFlags::MS_SHARED)),
|
|
"rshared" => Some((false, MsFlags::MS_SHARED | MsFlags::MS_REC)),
|
|
"slave" => Some((false, MsFlags::MS_SLAVE)),
|
|
"rslave" => Some((false, MsFlags::MS_SLAVE | MsFlags::MS_REC)),
|
|
"relatime" => Some((false, MsFlags::MS_RELATIME)),
|
|
"norelatime" => Some((true, MsFlags::MS_RELATIME)),
|
|
"strictatime" => Some((false, MsFlags::MS_STRICTATIME)),
|
|
"nostrictatime" => Some((true, MsFlags::MS_STRICTATIME)),
|
|
_ => None,
|
|
} {
|
|
if is_clear {
|
|
flags &= !flag;
|
|
} else {
|
|
flags |= flag;
|
|
}
|
|
} else {
|
|
data.push(s.as_str());
|
|
};
|
|
}
|
|
}
|
|
(flags, data.join(","))
|
|
}
|