diff options
Diffstat (limited to 'utils/src')
| -rw-r--r-- | utils/src/shim.rs | 129 | ||||
| -rw-r--r-- | utils/src/split_path.rs | 142 |
2 files changed, 271 insertions, 0 deletions
diff --git a/utils/src/shim.rs b/utils/src/shim.rs new file mode 100644 index 0000000..504335c --- /dev/null +++ b/utils/src/shim.rs @@ -0,0 +1,129 @@ +use anyhow::Context; +use nix::errno::Errno; +use nix::mount::{mount, MsFlags}; +use nix::sys::wait::{waitid, Id, WaitPidFlag}; +use nix::unistd::Pid; +use std::env; +use std::fs::{create_dir_all, metadata, remove_dir_all, remove_file, OpenOptions}; +use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::os::unix::process::CommandExt; +use std::path::Path; +use std::process::{Command, Stdio}; + +fn unscrew_dev_shm() -> anyhow::Result<()> { + log::trace!("Unscrewing /dev/shm..."); + + let dev_shm = Path::new("/dev/shm"); + + if dev_shm.is_symlink() { + remove_file(dev_shm).context("When removing /dev/shm symlink")?; + } else if dev_shm.is_dir() { + remove_dir_all(dev_shm).context("When removing old /dev/shm")?; + } + + create_dir_all("/dev/shm").context("When creating new /dev/shm")?; + mount( + Some("/run/shm"), + "/dev/shm", + None::<&str>, + MsFlags::MS_MOVE, + None::<&str>, + ) + .context("When relocating /dev/shm")?; + mount( + Some("/dev/shm"), + "/run/shm", + None::<&str>, + MsFlags::MS_BIND, + None::<&str>, + ) + .context("When bind mounting /run/shm to /dev/shm")?; + + Ok(()) +} + +fn real_main() -> anyhow::Result<()> { + if metadata("/dev/shm") + .context("When checking /dev/shm")? + .is_symlink() + { + unscrew_dev_shm()?; + } else { + log::trace!("/dev/shm is not a symlink, leaving as-is..."); + }; + + log::trace!("Remounting / shared..."); + + mount( + None::<&str>, + "/", + None::<&str>, + MsFlags::MS_REC | MsFlags::MS_SHARED, + None::<&str>, + ) + .context("When remounting /")?; + + log::trace!("Remounting /nix/store read-only..."); + + mount( + Some("/nix/store"), + "/nix/store", + None::<&str>, + MsFlags::MS_BIND, + None::<&str>, + ) + .context("When bind mounting /nix/store")?; + + mount( + Some("/nix/store"), + "/nix/store", + None::<&str>, + MsFlags::MS_BIND | MsFlags::MS_REMOUNT | MsFlags::MS_RDONLY, + None::<&str>, + ) + .context("When remounting /nix/store read-only")?; + + log::trace!("Running activation script..."); + + let kmsg_fd = OpenOptions::new() + .write(true) + .open("/dev/kmsg") + .context("When opening /dev/kmsg")? + .into_raw_fd(); + + let child = Command::new("/nix/var/nix/profiles/system/activate") + .env("LANG", "C.UTF-8") + // SAFETY: we just opened this + .stdout(unsafe { Stdio::from_raw_fd(kmsg_fd) }) + .stderr(unsafe { Stdio::from_raw_fd(kmsg_fd) }) + .spawn() + .context("When activating")?; + + let pid = Pid::from_raw(child.id() as i32); + + // If the child catches SIGCHLD, `waitid` will wait for it to exit, then return ECHILD. + // Why? Because POSIX is terrible. + let result = waitid(Id::Pid(pid), WaitPidFlag::WEXITED); + match result { + Ok(_) | Err(Errno::ECHILD) => {} + Err(e) => return Err(e).context("When waiting"), + }; + + log::trace!("Spawning real systemd..."); + + // if things go right, we will never return from here + Err( + Command::new("/nix/var/nix/profiles/system/systemd/lib/systemd/systemd") + .arg0(env::args_os().next().expect("arg0 missing")) + .args(env::args_os().skip(1)) + .exec() + .into(), + ) +} + +fn main() { + env::set_var("RUST_BACKTRACE", "1"); + kernlog::init().expect("Failed to set up logger..."); + let result = real_main(); + log::error!("Error: {:?}", result); +} diff --git a/utils/src/split_path.rs b/utils/src/split_path.rs new file mode 100644 index 0000000..16063ab --- /dev/null +++ b/utils/src/split_path.rs @@ -0,0 +1,142 @@ +use std::{ + env, + ffi::{OsStr, OsString}, + io::{self, Write}, + os::unix::prelude::{OsStrExt, OsStringExt}, + path::{Path, PathBuf}, +}; + +use clap::Parser; + +#[derive(Parser, Debug)] +struct Args { + #[arg(long)] + automount_root: PathBuf, + + #[arg(long)] + include_interop: bool, +} + +const SINGLE_QUOTE: u8 = b'\''; +const DOUBLE_QUOTE: u8 = b'"'; + +fn shell_escape(s: &OsStr) -> OsString { + // a shameless ripoff of the Python algorithm: + // https://github.com/python/cpython/blob/f1f3af7b8245e61a2e0abef03b2c6c5902ed7df8/Lib/shlex.py#L323 + let mut result = Vec::new(); + + result.push(SINGLE_QUOTE); + + for &byte in s.as_bytes() { + result.push(byte); + if byte == SINGLE_QUOTE { + result.push(DOUBLE_QUOTE); + result.push(SINGLE_QUOTE); + result.push(DOUBLE_QUOTE); + result.push(SINGLE_QUOTE); + } + } + + result.push(SINGLE_QUOTE); + + OsString::from_vec(result) +} + +fn build_export(var: &str, paths: &[PathBuf]) -> OsString { + let mut result = OsString::new(); + result.push("export "); + result.push(var); + result.push("="); + result.push(shell_escape( + &env::join_paths(paths).expect("paths must be valid"), + )); + result.push("\n"); + result +} + +fn do_split_paths(path: &OsStr, automount_root: &Path, include_interop: bool) -> OsString { + let mut native = vec![]; + let mut interop = vec![]; + + for part in env::split_paths(&path) { + if part.starts_with(automount_root) { + interop.push(part); + } else { + native.push(part); + } + } + + if include_interop { + native.extend(interop.clone()); + }; + + let mut result = OsString::new(); + result.push(build_export("PATH", &native)); + result.push(build_export("WSLPATH", &interop)); + result +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let path = env::var_os("PATH").expect("PATH is not set, aborting"); + + io::stdout() + .lock() + .write_all(do_split_paths(&path, &args.automount_root, args.include_interop).as_bytes())?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::{ffi::OsString, path::Path}; + + use crate::do_split_paths; + + #[test] + fn simple() { + assert_eq!( + do_split_paths( + &OsString::from("/good/foo:/bad/foo"), + Path::new("/bad"), + false + ), + OsString::from("export PATH='/good/foo'\nexport WSLPATH='/bad/foo'\n") + ); + } + + #[test] + fn exactly_one() { + assert_eq!( + do_split_paths(&OsString::from("/good/foo"), Path::new("/bad"), true), + OsString::from("export PATH='/good/foo'\nexport WSLPATH=''\n") + ); + } + + #[test] + fn include_interop() { + assert_eq!( + do_split_paths( + &OsString::from("/good/foo:/bad/foo"), + Path::new("/bad"), + true + ), + OsString::from("export PATH='/good/foo:/bad/foo'\nexport WSLPATH='/bad/foo'\n") + ); + } + + #[test] + fn spicy_escapes() { + assert_eq!( + do_split_paths( + &OsString::from("/good/foo'bar:/bad/foo"), + Path::new("/bad"), + true + ), + OsString::from( + "export PATH='/good/foo'\"'\"'bar:/bad/foo'\nexport WSLPATH='/bad/foo'\n" + ) + ); + } +} |
