summaryrefslogtreecommitdiff
path: root/utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'utils/src')
-rw-r--r--utils/src/shim.rs129
-rw-r--r--utils/src/split_path.rs142
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"
+ )
+ );
+ }
+}