Add mounting of system directories when chrooting

main
trivernis 2 years ago committed by Michal Stopyra
parent 35b2ed5782
commit 735a34e2d2

37
Cargo.lock generated

@ -1564,6 +1564,17 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "loopdev"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bfa0855b04611e38acaff718542e9e809cddfc16535d39f9d9c694ab19f7388"
dependencies = [
"bindgen",
"errno",
"libc",
]
[[package]] [[package]]
name = "lscolors" name = "lscolors"
version = "0.12.0" version = "0.12.0"
@ -3168,6 +3179,17 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smart-default"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "smawk" name = "smawk"
version = "0.3.1" version = "0.3.1"
@ -3272,6 +3294,20 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "sys-mount"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1af10c09a6d1f65753e52772a4621e00da8b1d772d0f24595b60ccd36d6b51"
dependencies = [
"bitflags",
"libc",
"loopdev",
"smart-default",
"thiserror",
"tracing",
]
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.26.4" version = "0.26.4"
@ -3522,6 +3558,7 @@ dependencies = [
"rusty-value", "rusty-value",
"serde", "serde",
"serde_json", "serde_json",
"sys-mount",
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml",

@ -26,6 +26,7 @@ paste = "1.0.9"
rusty-value = { version = "0.6.0", features = ["derive", "json"] } rusty-value = { version = "0.6.0", features = ["derive", "json"] }
serde = { version = "1.0.145", features = ["derive"] } serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.86" serde_json = "1.0.86"
sys-mount = "1.5.1"
thiserror = "1.0.37" thiserror = "1.0.37"
tokio = { version = "1.21.2", features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] } tokio = { version = "1.21.2", features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] }
toml = "0.5.9" toml = "0.5.9"

@ -1,4 +1,10 @@
let REQUIRES_CHROOT = true;
# Applies all system changes of `install-base` # Applies all system changes of `install-base`
def main [cfg] { def main [cfg] {
echo "Executing up task `install-base` with config" $cfg echo "Executing up task `install-base` with config" $cfg
ls /etc
cat /etc/mtab
pacman -S --noconfirm neofetch
neofetch
exit 1
} }

@ -90,9 +90,21 @@ pub enum ChrootError {
#[error("Failed to unshare FS resources with parent: {0}")] #[error("Failed to unshare FS resources with parent: {0}")]
Unshare(io::Error), Unshare(io::Error),
#[error("Failed to create chroot dir: {0}")]
CreateChroot(io::Error),
#[error("Failed to chroot: {0}")] #[error("Failed to chroot: {0}")]
Chroot(io::Error), Chroot(io::Error),
#[error("Failed to mount directory {0} in chroot: {1}")]
Mount(PathBuf, io::Error),
#[error("Failed to create symlink {0} in chroot: {1}")]
Link(PathBuf, io::Error),
#[error("Failed to remove symlink in chroot: {0}")]
Unlink(io::Error),
#[error("Failed to change process working directory: {0}")] #[error("Failed to change process working directory: {0}")]
ChDir(io::Error), ChDir(io::Error),
} }

@ -0,0 +1,176 @@
use std::path::{Path, PathBuf};
use sys_mount::{FilesystemType, Mount, MountBuilder, MountFlags, UnmountDrop, UnmountFlags};
use tokio::fs;
use crate::error::ChrootError;
pub enum Mapping {
Device(PathBuf),
Dir(PathBuf, DirOpts),
Special(PathBuf, PathBuf, DirOpts),
}
#[derive(Default)]
pub struct DirOpts {
fs_type: Option<String>,
mount_flags: Option<MountFlags>,
required: bool,
unmount_flags: Option<UnmountFlags>,
}
impl DirOpts {
pub fn fs_type<S: Into<String>>(mut self, fs_type: S) -> Self {
self.fs_type = Some(fs_type.into());
self
}
pub fn flags(mut self, flags: MountFlags) -> Self {
self.mount_flags = Some(flags);
self
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn unmount_flags(mut self, flags: UnmountFlags) -> Self {
self.unmount_flags = Some(flags);
self
}
}
pub enum MappingHandle {
Mount(UnmountDrop<Mount>),
}
impl Mapping {
fn dev<P: Into<PathBuf>>(path: P) -> Self {
Self::Device(path.into())
}
fn dir<P: Into<PathBuf>>(path: P, opt: DirOpts) -> Self {
Self::Dir(path.into(), opt)
}
fn special<P1: Into<PathBuf>, P2: Into<PathBuf>>(src: P1, dst: P2, opt: DirOpts) -> Self {
Self::Special(src.into(), dst.into(), opt)
}
pub async fn create_mapping(&self, root_path: &Path) -> Result<MappingHandle, ChrootError> {
match &self {
Mapping::Device(d) => self.map_dev(d, &root_path.join(d)).await,
Mapping::Dir(d, opt) => self.map_dir(d, &root_path.join(d), opt).await,
Mapping::Special(src, dst, opt) => self.map_dir(src, &root_path.join(dst), opt).await,
}
}
async fn map_dev(&self, src: &Path, dst: &Path) -> Result<MappingHandle, ChrootError> {
if !dst.exists() {
if let Some(parent) = dst.parent() {
fs::create_dir_all(&parent)
.await
.map_err(|e| ChrootError::Mount(dst.to_owned(), e))?;
}
fs::write(&dst, "")
.await
.map_err(|e| ChrootError::Mount(dst.to_owned(), e))?;
}
let mount = MountBuilder::default()
.flags(MountFlags::BIND)
.mount_autodrop(src, dst, UnmountFlags::empty())
.map_err(|e| ChrootError::Mount(dst.to_owned(), e))?;
Ok(MappingHandle::Mount(mount))
}
async fn map_dir(
&self,
src: &Path,
dst: &Path,
opt: &DirOpts,
) -> Result<MappingHandle, ChrootError> {
if !dst.exists() {
fs::create_dir_all(&dst)
.await
.map_err(|e| ChrootError::Mount(dst.to_owned(), e))?;
}
let fs_type =
FilesystemType::Manual(opt.fs_type.as_ref().map(String::as_str).unwrap_or(""));
let flags = opt.mount_flags.unwrap_or_else(|| MountFlags::empty());
let mount = MountBuilder::default()
.fstype(fs_type)
.flags(flags)
.mount_autodrop(src, dst, opt.unmount_flags.unwrap_or(UnmountFlags::empty()))
.map_err(|e| ChrootError::Mount(src.to_owned(), e))?;
Ok(MappingHandle::Mount(mount))
}
}
pub fn default_mappings() -> Vec<Mapping> {
static NOSUID: MountFlags = MountFlags::NOSUID;
static NOEXEC: MountFlags = MountFlags::NOEXEC;
static NODEV: MountFlags = MountFlags::NODEV;
static STRICTATIME: MountFlags = MountFlags::STRICTATIME;
vec![
Mapping::dir(
"proc",
DirOpts::default()
.fs_type("proc")
.flags(NOSUID | NOEXEC | NODEV),
),
Mapping::dir(
"sys",
DirOpts::default()
.fs_type("sysfs")
.flags(NOSUID | NOEXEC | NODEV)
.unmount_flags(UnmountFlags::DETACH),
),
Mapping::dir(
"sys/firmware/efi/efivars",
DirOpts::default()
.fs_type("efivarfs")
.flags(NOSUID | NOEXEC | NODEV)
.optional(),
),
Mapping::special(
"udev",
"dev",
DirOpts::default().fs_type("devtmpfs").flags(NOSUID),
),
Mapping::special(
"devpts",
"dev/pts",
DirOpts::default().fs_type("devpts").flags(NOSUID | NOEXEC),
),
Mapping::special(
"shm",
"dev/shm",
DirOpts::default().fs_type("tmpfs").flags(NOSUID | NODEV),
),
Mapping::dev("/dev/full"),
Mapping::dev("/dev/null"),
Mapping::dev("/dev/random"),
Mapping::dev("/dev/tty"),
Mapping::dev("/dev/urandom"),
Mapping::dev("/dev/zero"),
Mapping::dir(
"/run",
DirOpts::default().fs_type("tmpfs").flags(NOSUID | NODEV),
),
Mapping::dir(
"/tmp",
DirOpts::default()
.fs_type("tmpfs")
.flags(STRICTATIME | NODEV | NOSUID),
),
]
}

@ -1,34 +1,34 @@
use std::{ use std::{
ffi::{c_int, CString}, ffi::{c_int, CString, OsString},
io, io,
os::unix::prelude::OsStrExt, os::unix::prelude::OsStrExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use libc::CLONE_FS; use libc::CLONE_FS;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
mod mapping;
pub mod setup;
pub mod teardown;
use crate::error::ChrootError; use crate::error::ChrootError;
pub struct ChrootedTask { use self::mapping::MappingHandle;
root_path: PathBuf,
}
impl ChrootedTask { pub struct Chroot {
/// Creates a new chrooted thread with the given path root_path: PathBuf,
pub fn new<P: Into<PathBuf>>(root_path: P) -> Self { _mappings: Vec<MappingHandle>,
Self {
root_path: root_path.into(),
}
} }
impl Chroot {
/// Runs the given future in a new chroot /// Runs the given future in a new chroot
pub fn run<F, T>(self, call: F) -> JoinHandle<Result<T, ChrootError>> pub fn run<F, T>(&self, call: F) -> JoinHandle<Result<T, ChrootError>>
where where
F: FnOnce() -> T + Send + 'static, F: FnOnce() -> T + Send + 'static,
T: Send + 'static, T: Send + 'static,
{ {
let root_path = self.root_path; let root_path = self.root_path.clone();
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || {
unsafe { unsafe {
init_chroot(&root_path)?; init_chroot(&root_path)?;
@ -41,12 +41,12 @@ impl ChrootedTask {
unsafe fn init_chroot(path: &Path) -> Result<(), ChrootError> { unsafe fn init_chroot(path: &Path) -> Result<(), ChrootError> {
handle_err_code(libc::unshare(CLONE_FS)).map_err(ChrootError::Unshare)?; handle_err_code(libc::unshare(CLONE_FS)).map_err(ChrootError::Unshare)?;
let path_str = CString::new(path.as_os_str().as_bytes().to_vec()).unwrap(); let path_str = c_str(path);
handle_err_code(libc::chroot( handle_err_code(libc::chroot(
path_str.as_bytes_with_nul().as_ptr() as *const libc::c_char path_str.as_bytes_with_nul().as_ptr() as *const libc::c_char
)) ))
.map_err(ChrootError::Chroot)?; .map_err(ChrootError::Chroot)?;
std::env::set_current_dir(path).map_err(ChrootError::ChDir)?; std::env::set_current_dir("/").map_err(ChrootError::ChDir)?;
std::env::set_var("PWD", "/"); std::env::set_var("PWD", "/");
Ok(()) Ok(())
@ -59,3 +59,7 @@ fn handle_err_code(code: c_int) -> Result<(), io::Error> {
Ok(()) Ok(())
} }
} }
pub(crate) fn c_str<O: Into<OsString>>(o: O) -> CString {
CString::new(o.into().as_bytes().to_vec()).unwrap()
}

@ -0,0 +1,31 @@
use std::path::PathBuf;
use tokio::fs;
use crate::error::ChrootError;
use super::{mapping::default_mappings, Chroot};
impl Chroot {
/// Creates a new chroot with the given path
pub async fn create<P: Into<PathBuf>>(root_path: P) -> Result<Self, ChrootError> {
let root_path = root_path.into();
if !root_path.exists() {
fs::create_dir_all(&root_path)
.await
.map_err(ChrootError::CreateChroot)?;
}
let default_mappings = default_mappings();
let mut handles = Vec::with_capacity(default_mappings.len());
for mapping in default_mappings {
let handle = mapping.create_mapping(&root_path).await?;
handles.push(handle);
}
Ok(Self {
root_path,
_mappings: handles,
})
}
}

@ -1,10 +1,13 @@
use std::process::Command;
use crate::{ use crate::{
distro::{distro_config::DistroConfig, OSConfig}, distro::{distro_config::DistroConfig, OSConfig},
error::AppResult, error::AppResult,
utils::ROOT_MNT,
}; };
use super::{ use super::{
base_task::ALL_BASE_TASKS, chrooting::ChrootedTask, custom_task::CustomTask, Task, TaskTrait, base_task::ALL_BASE_TASKS, chrooting::Chroot, custom_task::CustomTask, Task, TaskTrait,
}; };
pub struct TaskExecutor { pub struct TaskExecutor {
@ -62,13 +65,23 @@ impl TaskExecutor {
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
pub async fn execute(&mut self) -> AppResult<()> { pub async fn execute(&mut self) -> AppResult<()> {
self.tasks.sort_by(Task::compare); self.tasks.sort_by(Task::compare);
let chroot = Chroot::create(&*ROOT_MNT).await?;
chroot
.run(move || {
Command::new("pacman")
.args(["-S", "--noconfirm", "neofetch"])
.spawn()
.unwrap()
.wait()
.unwrap();
Command::new("neofetch").spawn().unwrap().wait().unwrap();
})
.await
.unwrap()?;
for task in &self.tasks { for task in &self.tasks {
if let Some(up_task) = task.up(&self.os_config)? { if let Some(up_task) = task.up(&self.os_config)? {
ChrootedTask::new("chroot/root") up_task.exec()?;
.run(move || up_task.exec())
.await
.unwrap()??;
} }
} }

@ -8,9 +8,16 @@ use crate::task::base_task::ALL_BASE_TASKS;
const DEFAULT_CONFIG_DIR: &str = "/etc"; const DEFAULT_CONFIG_DIR: &str = "/etc";
macro_rules! env_cfg {
($name:ident: $type:ident <- $env_key:literal default $default:expr) => {
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref CFG_PATH: PathBuf = env::var("TRM_CFG_PATH").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from(DEFAULT_CONFIG_DIR).join("tourmaline")); pub static ref $name: $type = env::var($env_key).map($type::from).unwrap_or_else(|_| $default);
} }
}
}
env_cfg!(CFG_PATH: PathBuf <- "TRM_CFG_PATH" default PathBuf::from(DEFAULT_CONFIG_DIR).join("tourmaline"));
env_cfg!(ROOT_MNT: PathBuf <- "TRM_ROOT_MNT" default PathBuf::from("/mnt"));
pub async fn generate_script_files<P: AsRef<Path>>(output: P) -> AppResult<()> { pub async fn generate_script_files<P: AsRef<Path>>(output: P) -> AppResult<()> {
let output = output.as_ref(); let output = output.as_ref();

Loading…
Cancel
Save