use super::api::{Context, ContextCommand, ContextDriver}; use log::debug; use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; pub struct UnshareDriver { pub path: String, pub parent: Option>, } /// Recursively copy a directory and all its contents fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> { // Create the destination directory std::fs::create_dir_all(dest)?; // Iterate through the source directory for entry in std::fs::read_dir(src)? { let entry = entry?; let src_path = entry.path(); let dest_path = dest.join(entry.file_name()); if src_path.is_dir() { // Recursively copy subdirectories copy_dir_recursive(&src_path, &dest_path)?; } else { // Copy files std::fs::copy(&src_path, &dest_path)?; } } Ok(()) } impl ContextDriver for UnshareDriver { fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result { // Construct the destination path inside the chroot let dest_dir = Path::new(&self.path).join(dest_root.trim_start_matches('/')); debug!( "unshare/ensure_available: copy '{}' to '{}'", src.display(), dest_dir.display() ); // Ensure the destination directory exists std::fs::create_dir_all(&dest_dir)?; // Get the filename from the source path let filename = src .file_name() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid source path"))?; // Construct the full destination path let dest_path = dest_dir.join(filename); // Copy the file or directory into the chroot if src.is_dir() { copy_dir_recursive(src, &dest_path)?; debug!( "Copied directory {} to {}", src.display(), dest_path.display() ); } else { std::fs::copy(src, &dest_path)?; debug!("Copied file {} to {}", src.display(), dest_path.display()); } // Return the path as it appears inside the chroot (without the chroot prefix) Ok(Path::new(dest_root).join(filename)) } fn retrieve_path(&self, _src: &Path, _dest: &Path) -> io::Result<()> { // TODO: Implement chroot file retrieval logic Err(io::Error::new( io::ErrorKind::Unsupported, "retrieve_path not yet implemented for chroot", )) } fn list_files(&self, _path: &Path) -> io::Result> { // TODO: Implement chroot file listing logic Err(io::Error::new( io::ErrorKind::Unsupported, "list_files not yet implemented for chroot", )) } fn run( &self, program: &str, args: &[String], env: &[(String, String)], cwd: Option<&str>, ) -> io::Result { self.command(program, args, env, cwd).status() } fn run_output( &self, program: &str, args: &[String], env: &[(String, String)], cwd: Option<&str>, ) -> io::Result { self.command(program, args, env, cwd).output() } fn create_temp_dir(&self) -> io::Result { // Create a temporary directory inside the chroot let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let work_dir_name = format!("pkh-build-{}", timestamp); let work_dir_inside_chroot = format!("/tmp/{}", work_dir_name); // Create the directory on the host filesystem let host_path = Path::new(&self.path).join("tmp").join(&work_dir_name); std::fs::create_dir_all(&host_path)?; debug!( "Created work directory: {} (host: {})", work_dir_inside_chroot, host_path.display() ); // Return the path as it appears inside the chroot Ok(work_dir_inside_chroot) } fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> { let host_src = Path::new(&self.path).join(src.to_string_lossy().trim_start_matches('/')); let host_dest = Path::new(&self.path).join(dest.to_string_lossy().trim_start_matches('/')); self.parent().copy_path(&host_src, &host_dest) } fn read_file(&self, path: &Path) -> io::Result { let host_path = Path::new(&self.path).join(path.to_string_lossy().trim_start_matches('/')); self.parent().read_file(&host_path) } fn write_file(&self, path: &Path, content: &str) -> io::Result<()> { let host_path = Path::new(&self.path).join(path.to_string_lossy().trim_start_matches('/')); self.parent().write_file(&host_path, content) } } impl UnshareDriver { fn parent(&self) -> &Context { self.parent .as_ref() .expect("UnshareDriver requires a parent context") } fn command( &self, program: &str, args: &[String], env: &[(String, String)], cwd: Option<&str>, ) -> ContextCommand<'_> { let mut cmd = self.parent().command("sudo"); cmd.args(env.iter().map(|(k, v)| format!("{k}={v}"))); cmd.arg("unshare").arg("-R").arg(&self.path); if let Some(dir) = cwd { cmd.arg("-w").arg(dir); } cmd.arg(program).args(args); cmd } }