diff --git a/Cargo.lock b/Cargo.lock index 3906026..629b9cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,31 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" @@ -16,9 +36,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.67" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cfg-if" @@ -26,17 +46,60 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "libc" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -58,6 +121,129 @@ dependencies = [ name = "nix-user-chroot" version = "1.2.2" dependencies = [ + "env_logger", "libc", + "log", "nix", + "serde", + "serde_derive", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", ] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 7122ada..84b8208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,10 @@ documentation = "https://github.com/nix-community/nix-user-chroot" repository = "https://github.com/nix-community/nix-user-chroot" [dependencies] -nix = "0.23.1" +env_logger = "0.9" libc = "0.2.117" +log = "0.4" +nix = "0.23.1" +serde = "1.0" +serde_derive = "1.0" +toml = "0.5" diff --git a/src/main.rs b/src/main.rs index 66a62e1..c917a78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,23 @@ -use nix::mount::{mount, MsFlags}; -use nix::sched::{unshare, CloneFlags}; -use nix::sys::signal::{kill, Signal}; -use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; -use nix::unistd::{self, fork, ForkResult}; -use std::env; -use std::ffi::OsStr; -use std::fs; -use std::io; -use std::io::prelude::*; -use std::os::unix::fs::symlink; -use std::os::unix::process::CommandExt; -use std::path::{Path, PathBuf}; -use std::process; -use std::string::String; +use std::{ + borrow::ToOwned, + collections::{HashMap, HashSet}, + env, + ffi::{OsStr, OsString}, + fs::{self, DirEntry}, + io::{self, Write}, + os::unix::{fs::symlink, process::CommandExt}, + path::{Path, PathBuf}, + process, +}; + +use nix::{ + mount::{mount, umount, MsFlags}, + sched::{unshare, CloneFlags}, + sys::signal::{kill, Signal}, + sys::wait::{waitpid, WaitPidFlag, WaitStatus}, + unistd::{self, fork, ForkResult}, +}; +use serde_derive::Deserialize; mod mkdtemp; @@ -26,7 +31,7 @@ fn bind_mount(source: &Path, dest: &Path) { MsFlags::MS_BIND | MsFlags::MS_REC | MsFlags::MS_PRIVATE, NONE, ) { - eprintln!( + log::error!( "failed to bind mount {} to {}: {}", source.display(), dest.display(), @@ -35,17 +40,187 @@ fn bind_mount(source: &Path, dest: &Path) { } } +/// When constructing the chroot, the mounts we make either mirror the +/// directory structure in `/` or are explicit user provided mounts that do +/// not necessarily have a source location that mirrors the mount location +/// (i.e. `/home/foo/my/special/groups` -> `/etc/group`). +/// +/// We represent the former as [`DirEntry`]s and the latter as regular +/// [`Path`]s. +/// +/// We do this instead of just passing around [`PathBuf`]s because `DirEntry`s +/// (which we get when we iterate over `/` recursively as part of mirroring it) +/// have additional guarantees like `file_name` being infallible. +#[derive(Debug, Clone, Copy)] +pub enum DirEntryOrExplicitMount<'a> { + /// Assumed to share it's directory with the `rootdir` of the [`RunChroot`] + /// its used with (this is not enforced, however). + DirEntry(&'a DirEntry), + /// Assumed to have a different directory than the `rootdir` of the + /// [`RunChroot`] it's used with. + /// + /// For example to mount `/home/foo/bar` to `/bin/bar` you would pass an + /// `ExplicitMount("/home/foo/bar")` to a `RunChroot` with `rootdir` + /// `"/bin"`. + /// + /// This path *can* be `/`. + ExplicitMount { + src: &'a Path, + dst_file_name: Option<&'a OsStr>, + }, +} + +impl<'a> From<&'a DirEntry> for DirEntryOrExplicitMount<'a> { + fn from(de: &'a DirEntry) -> DirEntryOrExplicitMount { + DirEntryOrExplicitMount::DirEntry(de) + } +} + +impl<'a> DirEntryOrExplicitMount<'a> { + fn explicit_mount_with_dest_file_name( + mount: &'a Path, + dst_file: &'a (impl AsRef + 'a), + ) -> Self { + DirEntryOrExplicitMount::ExplicitMount { + src: mount, + dst_file_name: dst_file.as_ref().file_name(), + } + } +} + +impl DirEntryOrExplicitMount<'_> { + fn file_name(&self) -> Option { + use DirEntryOrExplicitMount::*; + + match self { + DirEntry(d) => Some(d.file_name()), + ExplicitMount { dst_file_name, .. } => dst_file_name.map(|p| p.to_owned()), + } + } + + fn path(&self) -> PathBuf { + use DirEntryOrExplicitMount::*; + + match self { + DirEntry(d) => d.path(), + ExplicitMount { src, .. } => (*src).to_owned(), + } + } + + fn metadata(&self) -> io::Result { + use DirEntryOrExplicitMount::*; + + match self { + DirEntry(d) => d.metadata(), + ExplicitMount { src, .. } => src.symlink_metadata(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct PathConfig<'a> { + excludes: ExcludePaths<'a>, + #[serde(borrow)] + profile: HashMap<&'a Path, &'a Path>, + #[serde(borrow)] + absolute: HashMap<&'a Path, &'a Path>, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ExcludePaths<'a> { + #[serde(borrow)] + paths: HashSet<&'a Path>, +} + pub struct RunChroot<'a> { rootdir: &'a Path, + nixdir: &'a Path, } impl<'a> RunChroot<'a> { - fn new(rootdir: &'a Path) -> Self { - Self { rootdir } + fn new(rootdir: &'a Path, nixdir: &'a Path) -> Self { + Self { rootdir, nixdir } + } + + fn with_rootdir(&self, rootdir: &'a Path) -> Self { + Self { + rootdir, + nixdir: self.nixdir, + } + } + + /// Recursively resolves a symlink, replacing references to `/nix` with + /// `self.nixdir` as it goes. + /// + /// `stop_at_first_non_nix_path` stops when it sees a path (symlink or not) + /// that isn't in `/nix`. This exists for [`mirror_symlink`] which + /// intentionally does not resolve symlinks all the way down when mirroring + /// them into the chroot. + fn resolve_nix_path( + &self, + p: PathBuf, + stop_at_first_non_nix_path: bool, + ) -> io::Result { + if p.is_symlink() { + let mut target = fs::read_link(&p)?; + if !target.is_absolute() { + // need to resolve relative symlinks: + target = p.parent().unwrap().join(target); + } + + // replace `/nix` with the actual profile path: + let p = if let Ok(rest) = target.strip_prefix("/nix") { + self.nixdir.join(rest) + } else { + if stop_at_first_non_nix_path { + return Ok(target); + } + + target + }; + + self.resolve_nix_path(p, stop_at_first_non_nix_path) + } else if p.exists() { + Ok(p) + } else { + // peel off components of the path, seeing if at some point we + // hit a symlink containing `/nix` which would explain why we + // couldn't stat the file + let mut i = 0; + let mut path = p.clone(); + + // NOTE: this is the bad N^2 way of doing this; we should actually + // resolve the path from the root onwards + while path.pop() { + i += 1; + + if path.is_symlink() + && path + .read_link() + .map(|p| p.starts_with("/nix")) + .unwrap_or(false) + { + // if we did find a parent that's a symlink, resolve it: + let actual_parent = self.resolve_nix_path(path, stop_at_first_non_nix_path)?; + + // append the components we stripped off to the resolved parent: + let parts = p.iter().collect::>(); + let stripped = &parts[parts.len() - i..]; + let path = actual_parent.join(stripped.iter().collect::()); + + // and try again: + return self.resolve_nix_path(path, stop_at_first_non_nix_path); + } + } + + Err(io::ErrorKind::NotFound.into()) + } } - fn bind_mount_directory(&self, entry: &fs::DirEntry) { - let mountpoint = self.rootdir.join(entry.file_name()); + // We assume `entry` exists and is actually a directory (not a file or symlink), + fn bind_mount_directory<'p>(&self, entry: impl Into>) { + let entry = entry.into(); + let mountpoint = self.rootdir.join(entry.file_name().unwrap_or_default()); // if the destination doesn't exist we can proceed as normal if !mountpoint.exists() { @@ -55,26 +230,47 @@ impl<'a> RunChroot<'a> { } } + log::info!( + "BIND DIRECTORY {} -> {}", + entry.path().display(), + mountpoint.display() + ); + bind_mount(&entry.path(), &mountpoint) } else { // otherwise, if the dest is also a dir, we can recurse into it // and mount subdirectory siblings of existing paths if mountpoint.is_dir() { - let dir = fs::read_dir(entry.path()).unwrap_or_else(|err| { - panic!("failed to list dir {}: {}", entry.path().display(), err) - }); - - let child = RunChroot::new(&mountpoint); + let dir = match fs::read_dir(entry.path()) { + Ok(dir) => dir, + Err(err) if err.kind() == io::ErrorKind::PermissionDenied => { + log::warn!( + "don't have persmission to access directory {}, skipping...", + entry.path().display() + ); + return; + } + Err(err) => panic!("failed to list dir {}: {}", entry.path().display(), err), + }; + + let child = self.with_rootdir(&mountpoint); for entry in dir { let entry = entry.expect("error while listing subdir"); - child.bind_mount_direntry(&entry); + child.bind_mount_entry(&entry); } } } } - fn bind_mount_file(&self, entry: &fs::DirEntry) { - let mountpoint = self.rootdir.join(entry.file_name()); + // We assume `entry` exists and is actually a file (not a directory or symlink). + fn bind_mount_file<'p>(&self, entry: impl Into>) { + let entry = entry.into(); + let mountpoint = self.rootdir.join(entry.file_name().unwrap_or_default()); + log::info!( + "BIND FILE {} -> {}", + entry.path().display(), + mountpoint.display() + ); if mountpoint.exists() { return; } @@ -84,24 +280,62 @@ impl<'a> RunChroot<'a> { bind_mount(&entry.path(), &mountpoint) } - fn mirror_symlink(&self, entry: &fs::DirEntry) { - let link_path = self.rootdir.join(entry.file_name()); + // We assume `entry` exists and either points to a path that exists *or* + // points to a `/nix` path (which we'll attempt to resolve against `self.nixdir`). + fn mirror_symlink<'p>(&self, entry: impl Into>) { + let entry = entry.into(); + let link_path = self.rootdir.join(entry.file_name().unwrap_or_default()); if link_path.exists() { return; } let path = entry.path(); - let target = fs::read_link(&path) + + // stops resolving the symlink at the first non-nix path + let target = self + .resolve_nix_path(path.clone(), true) .unwrap_or_else(|err| panic!("failed to resolve symlink {}: {}", &path.display(), err)); - symlink(&target, &link_path).unwrap_or_else(|_| { + + log::info!( + "MIRROR SYMLINK {} -> {}", + target.display(), + link_path.display() + ); + + symlink(&target, &link_path).unwrap_or_else(|err| { panic!( - "failed to create symlink {} -> {}", + "failed to create symlink {} -> {} ({err:?})", &link_path.display(), &target.display() ) }); } - fn bind_mount_direntry(&self, entry: &fs::DirEntry) { + fn bind_mount_entry<'p>(&self, entry: impl Into>) { + use DirEntryOrExplicitMount::*; + let mut entry = entry.into(); + + // resolve any `/nix`s now so we can actually stat the file + // + // as with `mirror_symlink`, stop once we hit a non-nix path + let adj_path; + let dst_file_name; + if entry.path().starts_with("/nix") { + adj_path = self.resolve_nix_path(entry.path(), true).unwrap(); + entry = match entry { + DirEntry(d) => { + dst_file_name = d.file_name(); + ExplicitMount { + src: &*adj_path, + dst_file_name: Some(&dst_file_name), + } + } + ExplicitMount { dst_file_name, .. } => ExplicitMount { + src: &*adj_path, + dst_file_name, + }, + }; + } + let path = entry.path(); let stat = entry .metadata() @@ -109,14 +343,16 @@ impl<'a> RunChroot<'a> { if stat.is_dir() { self.bind_mount_directory(entry); - } else if stat.is_file() { + } else if stat.is_file() || path == Path::new("/dev/null") { self.bind_mount_file(entry); } else if stat.file_type().is_symlink() { self.mirror_symlink(entry); + } else { + panic!("don't know what to do with: {}", path.display()) } } - fn run_chroot(&self, nixdir: &Path, cmd: &str, args: &[String]) { + fn run_chroot(&self, cmd: &str, args: &[String], path_config: Option>) { let cwd = env::current_dir().expect("cannot get current working directory"); let uid = unistd::getuid(); @@ -126,7 +362,7 @@ impl<'a> RunChroot<'a> { // create /run/opengl-driver/lib in chroot, to behave like NixOS // (needed for nix pkgs with OpenGL or CUDA support to work) - let ogldir = nixdir.join("var/nix/opengl-driver/lib"); + let ogldir = self.nixdir.join("var/nix/opengl-driver/lib"); if ogldir.is_dir() { let ogl_mount = self.rootdir.join("run/opengl-driver/lib"); fs::create_dir_all(&ogl_mount) @@ -134,16 +370,108 @@ impl<'a> RunChroot<'a> { bind_mount(&ogldir, &ogl_mount); } + // TODO: test mounting in something to `/`; should work + // TODO: test `cargo` or something else where the symlink's name is actually important (both as an explicit bind mount and an incidental one to make sure the logic is right) + + // mount in explicit mounts (profile relative, absolute, and placeholders to "reserve" the excludes): + if let Some(ref c) = path_config { + let user = unistd::User::from_uid(uid).unwrap().unwrap(); + let profile_dir = self + .nixdir + .join("var/nix/profiles/per-user") + .join(&user.name) + .join("profile"); + let profile_dir = self.resolve_nix_path(profile_dir, false); + + let explicit_mounts = c.profile + .iter() + .map(|(s, d)| (*s, *d)) + .filter(|(s, d)| if profile_dir.is_ok() { + true + } else { + eprintln!("Warning: couldn't find a profile for user `{}`; skipping profile mount `{}` -> `{}`", &user.name, s.display(), d.display()); + false + }) + .map(|(mut prof_p, chroot_p)| { + // to allow for both "absolute" and relative paths in the profile relative mounts + if prof_p.is_absolute() { + prof_p = prof_p.strip_prefix("/").unwrap() + } + + (prof_p, chroot_p) + }) + .map(|(prof_p, chroot_p)| (profile_dir.as_ref().unwrap().join(prof_p), chroot_p)) + .chain( + // TODO: this should actually probably happen first. + c.excludes.paths + .iter() + .map(|&ex| (PathBuf::from("/dev/null"), ex)) + ) + .chain( + c.absolute + .iter() + .map(|(s, d)| (*s, *d)) + .inspect(|(src, _)| { + if !src.is_absolute() { + panic!("Explicit mount sources (excluding profile mounts) must be absolute paths! `{}` is not absolute.", src.display()) + } + }) + .map(|(src, dest)| { + (src.to_owned(), dest) + }) + ) + .inspect(|(_, dest)| { + if !dest.is_absolute() { + panic!("All explicit mount destinations must be absolute paths! `{}` is not absolute.", dest.display()) + } + }); + + for (src, dest) in explicit_mounts { + if let Ok(src) = self.resolve_nix_path(src.clone(), true) { + log::info!("EXPLICIT {} -> {}", src.display(), dest.display()); + + let adjusted_dest = dest + .strip_prefix("/") // we have guarantees that `dest` is absolute + .unwrap() + .parent() + .map(ToOwned::to_owned) + .unwrap_or_default(); + let parent = self.rootdir.join(adjusted_dest); + + fs::create_dir_all(&parent).unwrap(); + + let parent = self.with_rootdir(&parent); + parent.bind_mount_entry( + DirEntryOrExplicitMount::explicit_mount_with_dest_file_name(&*src, &dest), + ); + } else { + eprintln!( + "warning: explicit mount source `{}` doesn't seem to exist!", + src.display() + ); + } + } + } + // bind the rest of / stuff into rootdir let nix_root = PathBuf::from("/"); - let dir = fs::read_dir(&nix_root).expect("failed to list /nix directory"); + let dir = fs::read_dir(&nix_root).expect("failed to list / directory"); for entry in dir { - let entry = entry.expect("error while listing from /nix directory"); + let entry = entry.expect("error while listing from / directory"); // do not bind mount an existing nix installation - if entry.file_name() == OsStr::new("nix") { + if entry.file_name() == "nix" { continue; } - self.bind_mount_direntry(&entry); + self.bind_mount_entry(&entry); + } + + // remove the placeholders we used for the excludes + if let Some(c) = path_config { + for &p in c.excludes.paths.iter() { + let mount = self.rootdir.join(p.strip_prefix("/").unwrap()); + log::info!("UNBIND {}", mount.display()); + umount(&mount).unwrap(); + } } // mount the store @@ -151,13 +479,19 @@ impl<'a> RunChroot<'a> { fs::create_dir(&nix_mount) .unwrap_or_else(|err| panic!("failed to create {}: {}", &nix_mount.display(), err)); mount( - Some(nixdir), + Some(self.nixdir), &nix_mount, Some("none"), MsFlags::MS_BIND | MsFlags::MS_REC, NONE, ) - .unwrap_or_else(|err| panic!("failed to bind mount {} to /nix: {}", nixdir.display(), err)); + .unwrap_or_else(|err| { + panic!( + "failed to bind mount {} to /nix: {}", + self.nixdir.display(), + err + ) + }); // chroot unistd::chroot(self.rootdir) @@ -171,6 +505,8 @@ impl<'a> RunChroot<'a> { let _ = file.write_all(b"deny"); } + // println!("cap: {}", std::fs::read_to_string(format!("/proc/self/status")).unwrap()); + let mut uid_map = fs::File::create("/proc/self/uid_map").expect("failed to open /proc/self/uid_map"); uid_map @@ -232,6 +568,12 @@ fn wait_for_child(rootdir: &Path, child_pid: unistd::Pid) -> ! { } fn main() { + let mut builder = env_logger::Builder::new(); + builder + .filter_level(log::LevelFilter::Warn) + .parse_default_env() + .init(); + let args: Vec = env::args().collect(); if args.len() < 3 { eprintln!("Usage: {} \n", args[0]); @@ -244,9 +586,20 @@ fn main() { let nixdir = fs::canonicalize(&args[1]) .unwrap_or_else(|err| panic!("failed to resolve nix directory {}: {}", &args[1], err)); + let path_config_file_path = nixdir.join("etc/nix-user-chroot/path-config.toml"); + let config_file; + let config_file = if path_config_file_path.exists() { + config_file = fs::read_to_string(path_config_file_path).unwrap(); + Some(toml::from_str(&*config_file).unwrap()) + } else { + None + }; + match unsafe { fork() } { Ok(ForkResult::Parent { child, .. }) => wait_for_child(&rootdir, child), - Ok(ForkResult::Child) => RunChroot::new(&rootdir).run_chroot(&nixdir, &args[2], &args[3..]), + Ok(ForkResult::Child) => { + RunChroot::new(&rootdir, &nixdir).run_chroot(&args[2], &args[3..], config_file) + } Err(e) => { eprintln!("fork failed: {}", e); }