diff --git a/distrod/distrod/src/main.rs b/distrod/distrod/src/main.rs index c397a8a..17bf810 100644 --- a/distrod/distrod/src/main.rs +++ b/distrod/distrod/src/main.rs @@ -215,11 +215,15 @@ fn enable_wsl_exec_hook(opts: EnableOpts) -> Result<()> { fn disable_wsl_exec_hook(_opts: DisableOpts) -> Result<()> { shell_hook::disable_default_shell_hook() .with_context(|| "Failed to disable the hook to the default shell.")?; + if let Err(e) = distro::cleanup_distro_rootfs("/") { + log::warn!("Failed to clean up the rootfs: {:?}", e); + } log::info!("Distrod has been disabled. Now systemd will not start automatically."); - autostart::disable_autostart_on_windows_boot( + if let Err(e) = autostart::disable_autostart_on_windows_boot( &wsl_interop::get_distro_name().with_context(|| "Failed to get the distro name.")?, - ) - .with_context(|| "Failed to disable the autostart on Windows boot.")?; + ) { + log::warn!("Failed to disable the autostart on Windows boot.: {:?}", e); + } Ok(()) } diff --git a/distrod/libs/src/container.rs b/distrod/libs/src/container.rs index fa8aac6..6626853 100644 --- a/distrod/libs/src/container.rs +++ b/distrod/libs/src/container.rs @@ -3,14 +3,14 @@ use nix::sched::CloneFlags; use nix::unistd::{chown, Gid, Uid}; use nix::NixPath; use passfd::FdPassingExt; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::fs::{self, File}; use std::io::Write; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; use std::os::unix::prelude::OsStrExt; -use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; +use std::process::Command; use crate::mount_info::{get_mount_entries, MountEntry}; use crate::multifork::{CommandByMultiFork, Waiter}; @@ -62,8 +62,9 @@ impl Container { let (fd_channel_host, fd_channel_child) = UnixStream::pair()?; { - let mut command = CommandByMultiFork::new(&init[0]); + let mut command = Command::new(&init[0]); command.args(&init[1..]); + let mut command = CommandByMultiFork::new(command); let fds_to_keep = vec![fd_channel_child.as_raw_fd()]; command.pre_second_fork(move || { daemonize(&fds_to_keep) @@ -111,34 +112,13 @@ impl Container { Ok(()) } - pub fn exec_command( - &self, - program: S, - args: I, - wd: Option

, - arg0: Option, - cred: Option<&Credential>, - ) -> Result - where - I: IntoIterator, - S: AsRef, - T1: AsRef, - T2: AsRef, - P: AsRef, - { + pub fn exec_command(&self, command: Command, cred: Option<&Credential>) -> Result { log::debug!("Container::exec_command."); if self.init_pid.is_none() { bail!("This container is not launched yet."); } - let mut command = CommandByMultiFork::new(&program); - command.args(args); - if let Some(arg0) = arg0 { - command.arg0(arg0); - } - if let Some(wd) = wd { - command.current_dir(wd); - } + let mut command = CommandByMultiFork::new(command); command.pre_second_fork(|| { enter_namespace(self.init_procfile.as_ref().unwrap()) .with_context(|| "Failed to enter the init's namespace")?; @@ -153,7 +133,7 @@ impl Container { .with_context(|| "Failed to request a proxy process.")?; command .spawn() - .with_context(|| format!("Container::exec_command failed: {:?}", &program.as_ref()))?; + .with_context(|| "Container::exec_command failed")?; log::debug!("Double fork done."); Ok(waiter) } diff --git a/distrod/libs/src/distro.rs b/distrod/libs/src/distro.rs index e60d4fa..20d94b7 100644 --- a/distrod/libs/src/distro.rs +++ b/distrod/libs/src/distro.rs @@ -1,12 +1,14 @@ use anyhow::{anyhow, bail, Context, Result}; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fs::{self, File}; use std::io::{BufReader, BufWriter, Write}; use std::os::linux::fs::MetadataExt; +use std::os::unix::prelude::CommandExt; use std::path::{Path, PathBuf}; +use std::process::Command; use crate::container::Container; -use crate::distrod_config::DistrodConfig; +use crate::distrod_config::{self, DistrodConfig}; use crate::envfile::EnvFile; use crate::mount_info::get_mount_entries; pub use crate::multifork::Waiter; @@ -106,8 +108,23 @@ impl Distro { P: AsRef, { log::debug!("Distro::exec_command."); + let mut command = Command::new(command.as_ref()); + command + .args(args) + // Adding the path to distrod bin allows us to hook "chsh" command to show the message + // to ask users to run "distrod enable" command. + .env( + "PATH", + add_distrod_bin_to_path(std::env::var("PATH").unwrap_or_default()), + ); + if let Some(wd) = wd { + command.current_dir(wd.as_ref()); + } + if let Some(arg0) = arg0 { + command.arg0(arg0.as_ref()); + } self.container - .exec_command(command, args, wd, arg0, cred) + .exec_command(command, cred) .with_context(|| "Failed to exec command in the container") } @@ -198,6 +215,18 @@ pub fn initialize_distro_rootfs>( Ok(()) } +pub fn cleanup_distro_rootfs>(path: P) -> Result<()> { + let metadata = fs::metadata(path.as_ref())?; + if !metadata.is_dir() { + bail!("The given path is not a directory: '{:?}'", path.as_ref()); + } + + cleanup_etc_environment_file(path.as_ref()) + .with_context(|| "Failed to cleanup /etc/environment")?; + + Ok(()) +} + fn setup_etc_environment_file>(rootfs_path: P) -> Result<()> { let env_file_path = rootfs_path.as_ref().join("etc/environment"); let mut env_file = EnvFile::open(&env_file_path) @@ -208,12 +237,75 @@ fn setup_etc_environment_file>(rootfs_path: P) -> Result<()> { for (key, value) in &wsl_envs { env_file.put(&key.to_string_lossy(), value.to_string_lossy().to_string()); } + + // Put the Distrod's bin dir in PATH + // This allows us to hook "chsh" command to show the message to ask users to run "distrod enable" command + if env_file + .get("PATH") + .map(|path| path.contains(distrod_config::get_distrod_bin_dir_path())) + != Some(true) + { + env_file.put( + "PATH", + add_distrod_bin_to_path(env_file.get("PATH").unwrap_or("")) + .to_string_lossy() + .to_string(), + ); + } + + env_file + .save() + .with_context(|| "Failed to save the environment file.")?; + Ok(()) +} + +fn cleanup_etc_environment_file>(rootfs_path: P) -> Result<()> { + let env_file_path = rootfs_path.as_ref().join("etc/environment"); + let mut env_file = EnvFile::open(&env_file_path) + .with_context(|| format!("Failed to open '{:?}'.", &&env_file_path))?; + + // Set the WSL envs in the default environment variables + let wsl_envs = collect_wsl_env_vars().with_context(|| "Failed to collect WSL envs.")?; + let keys: Vec<_> = wsl_envs.keys().collect(); + for key in keys { + env_file.remove(&key.to_string_lossy()); + } + + // Put the Distrod's bin dir in PATH + // This allows us to hook "chsh" command to show the message to ask users to run "distrod enable" command + if env_file + .get("PATH") + .map(|path| path.contains(distrod_config::get_distrod_bin_dir_path())) + == Some(true) + { + env_file.put( + "PATH", + remove_distrod_bin_from_path(env_file.get("PATH").unwrap_or("")), + ); + } + env_file .save() .with_context(|| "Failed to save the environment file.")?; Ok(()) } +fn add_distrod_bin_to_path>(path: S) -> OsString { + let mut result = OsString::from(distrod_config::get_distrod_bin_dir_path()); + result.push(":"); + result.push(path); + result +} + +fn remove_distrod_bin_from_path(path: &str) -> String { + let mut distrod_bin_path = distrod_config::get_distrod_bin_dir_path().to_owned(); + distrod_bin_path.push(':'); + let result = path.replace(&distrod_bin_path, ""); + distrod_bin_path.pop(); + distrod_bin_path.insert(0, ':'); + result.replace(&distrod_bin_path, "") +} + fn get_distro_run_info_file(create: bool, write: bool) -> Result> { let mut json = fs::OpenOptions::new(); json.read(true); diff --git a/distrod/libs/src/distrod_config.rs b/distrod/libs/src/distrod_config.rs index 1968bf6..b83098a 100644 --- a/distrod/libs/src/distrod_config.rs +++ b/distrod/libs/src/distrod_config.rs @@ -69,7 +69,15 @@ pub fn get_alias_dir() -> &'static str { DISTROD_ALIAS_DIR.as_str() } -static DISTROD_BIN_PATH: Lazy = Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "distrod")); +static DISTROD_BIN_DIR: Lazy = Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "bin")); + +/// The path to the distrod binary. +pub fn get_distrod_bin_dir_path() -> &'static str { + DISTROD_BIN_DIR.as_str() +} + +static DISTROD_BIN_PATH: Lazy = + Lazy::new(|| format!("{}/{}", DISTROD_BIN_DIR.as_str(), "distrod")); /// The path to the distrod binary. pub fn get_distrod_bin_path() -> &'static str { @@ -77,7 +85,7 @@ pub fn get_distrod_bin_path() -> &'static str { } static DISTROD_EXEC_BIN_PATH: Lazy = - Lazy::new(|| format!("{}/{}", DISTROD_ROOT_DIR, "distrod-exec")); + Lazy::new(|| format!("{}/{}", DISTROD_BIN_DIR.as_str(), "distrod-exec")); /// The path to the distrod-exec binary. pub fn get_distrod_exec_bin_path() -> &'static str { diff --git a/distrod/libs/src/envfile.rs b/distrod/libs/src/envfile.rs index fffab07..e47bfc6 100644 --- a/distrod/libs/src/envfile.rs +++ b/distrod/libs/src/envfile.rs @@ -67,6 +67,10 @@ impl EnvFile { self.envs.insert(key.to_owned(), vec![(usize::MAX, value)]); } + pub fn remove(&mut self, key: &str) { + self.envs.remove(key); + } + pub fn save(&mut self) -> Result<()> { let mut lines = self .envs diff --git a/distrod/libs/src/multifork.rs b/distrod/libs/src/multifork.rs index 8220cb6..582211b 100644 --- a/distrod/libs/src/multifork.rs +++ b/distrod/libs/src/multifork.rs @@ -3,11 +3,11 @@ use nix::fcntl::OFlag; use nix::libc::c_int; use nix::sys::signal; use std::convert::From; -use std::ffi::OsStr; use std::fs::File; use std::io::{Read, Write}; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::os::unix::io::FromRawFd; +use std::os::unix::prelude::CommandExt; use std::process::Command; pub struct CommandByMultiFork<'a> { @@ -18,9 +18,9 @@ pub struct CommandByMultiFork<'a> { } impl<'a> CommandByMultiFork<'a> { - pub fn new>(program: S) -> CommandByMultiFork<'a> { + pub fn new(command: Command) -> CommandByMultiFork<'a> { CommandByMultiFork { - command: Command::new(program), + command, pre_second_fork: None, proxy_process: None, does_triple_fork: false, @@ -32,6 +32,15 @@ impl<'a> CommandByMultiFork<'a> { self } + // Define proxy function to allow it to be called before pre_second_fork for readability. + pub unsafe fn pre_exec(&mut self, f: F) -> &mut CommandByMultiFork<'a> + where + F: FnMut() -> std::io::Result<()> + Send + Sync + 'static, + { + self.command.pre_exec(f); + self + } + pub fn pre_second_fork(&mut self, f: F) -> &mut CommandByMultiFork<'a> where F: FnMut() -> Result<()> + 'a, @@ -97,12 +106,6 @@ impl<'a> Deref for CommandByMultiFork<'a> { } } -impl<'a> DerefMut for CommandByMultiFork<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.command - } -} - impl<'a> From for CommandByMultiFork<'a> { fn from(command: Command) -> Self { CommandByMultiFork { @@ -198,8 +201,9 @@ mod tests { #[test] fn test_insert_proxy() { - let mut doublefork = CommandByMultiFork::new("/bin/bash"); - doublefork.args(&["-c", "sleep 1; exit 42"]); + let mut command = Command::new("/bin/bash"); + command.args(&["-c", "sleep 1; exit 42"]); + let mut doublefork = CommandByMultiFork::new(command); let mut waiter = doublefork.insert_waiter_proxy().unwrap(); let _ = doublefork.spawn().unwrap(); let exit_code = waiter.wait(); @@ -208,11 +212,12 @@ mod tests { #[test] fn test_inserted_proxy_ignore_signal() { - let mut doublefork = CommandByMultiFork::new("/bin/bash"); - doublefork.args(&[ + let mut command = Command::new("/bin/bash"); + command.args(&[ "-c", "trap '' SIGINT; kill -SIGINT $PPID; sleep 1; exit 42;", ]); + let mut doublefork = CommandByMultiFork::new(command); let mut waiter = doublefork.insert_waiter_proxy().unwrap(); let _ = doublefork.spawn().unwrap(); let exit_code = waiter.wait(); diff --git a/distrod_packer/distrod_packer b/distrod_packer/distrod_packer index 09157ab..a4990e5 100755 --- a/distrod_packer/distrod_packer +++ b/distrod_packer/distrod_packer @@ -62,7 +62,7 @@ def make_distrod_distribution(workspace_path: str, output_dst: str): make_distrod_project_static_windows(workspace_path, work_dir, "portproxy") copy_distrod_distribution_resources(work_dir) set_permissions(work_dir) - set_suid(work_dir + get_distrod_dir_in_container("distrod-exec")) + set_suid(work_dir + get_bin_dir_in_container("distrod-exec")) def make_distrod_project_static_linux(workspace_path, work_dir, bin_name): @@ -81,12 +81,12 @@ def make_distrod_project_static_windows(workspace_path, work_dir, bin_name): def copy_target_binary(bin_name: str, workspace_path: str, work_dir: str) -> str: shutil.copy(f"{workspace_path}/target/release/{bin_name}", - work_dir + get_distrod_dir_in_container(bin_name)) - return get_distrod_dir_in_container(bin_name) + work_dir + get_bin_dir_in_container(bin_name)) + return get_bin_dir_in_container(bin_name) def copy_dependency_libs(bin_name: str, work_dir: str): - distrod_bin = work_dir + get_distrod_dir_in_container(bin_name) + distrod_bin = work_dir + get_bin_dir_in_container(bin_name) ld, libs = extract_dependency_libs(distrod_bin) ld_path = work_dir + get_ld_dir_in_container(os.path.basename(ld)) shutil.copy(ld, ld_path) @@ -132,11 +132,12 @@ def make_minimum_mountpoints(work_dir: str): def make_distrod_distribution_dirs(work_dir: str): dirs = [ get_distrod_dir_in_container(), + get_distrod_dir_in_container("bin"), get_distrod_dir_in_container("alias"), get_distrod_dir_in_container("lib"), get_distrod_dir_in_container("ld")] for dir in dirs: - os.makedirs(work_dir + dir) + os.makedirs(work_dir + dir, exist_ok=True) def set_permissions(work_dir: str): @@ -180,6 +181,13 @@ def get_resources_dir(inner_path: Optional[str] = None) -> str: return path +def get_bin_dir_in_container(inner_path: Optional[str] = None) -> str: + path = "bin" + if inner_path: + path = f"{path}/{inner_path}" + return get_distrod_dir_in_container(path) + + def get_lib_dir_in_container(inner_path: Optional[str] = None) -> str: path = "lib" if inner_path: diff --git a/distrod_packer/resources/bin/chsh b/distrod_packer/resources/bin/chsh new file mode 100755 index 0000000..2da27f9 --- /dev/null +++ b/distrod_packer/resources/bin/chsh @@ -0,0 +1,8 @@ +#!/bin/sh + +ORIG_CHSH=""$(which -a chsh | tail -n 1)"" +if [ -t 0 ]; then + echo "[Distrod] Run 'sudo /opt/distrod/distrod enable' after chsh succeeds." >&2 + echo "[Distrod] It's necessary for Systemd to work as the init process." >&2 +fi +"$ORIG_CHSH" "$@" diff --git a/distrod_packer/resources/etc/portproxy.service b/distrod_packer/resources/etc/portproxy.service index 3fa5d9e..4c0c168 100644 --- a/distrod_packer/resources/etc/portproxy.service +++ b/distrod_packer/resources/etc/portproxy.service @@ -7,7 +7,7 @@ Wants=network-online.target systemd-networkd-wait-online.service Restart=on-failure RestartSec=15 -ExecStart=/bin/sh -c '/opt/distrod/portproxy.exe proxy $(/opt/distrod/portproxy show ipv4) -t $(cat /opt/distrod/conf/tcp4_ports)' +ExecStart=/bin/sh -c '/opt/distrod/bin/portproxy.exe proxy $(/opt/distrod/bin/portproxy show ipv4) -t $(cat /opt/distrod/conf/tcp4_ports)' [Install] WantedBy=multi-user.target