/// Schroot context: execute commands in a schroot session /// Not tested, will need more work! use super::api::ContextDriver; use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; pub struct SchrootDriver { pub name: String, pub session: std::sync::Mutex>, pub parent: Option>, } use super::api::{Context, ContextConfig}; impl SchrootDriver { fn parent(&self) -> Arc { self.parent .clone() .unwrap_or_else(|| Arc::new(Context::new(ContextConfig::Local))) } fn ensure_session(&self) -> io::Result { let mut session_lock = self.session.lock().unwrap(); if let Some(id) = session_lock.as_ref() { return Ok(id.clone()); } // Create new session let output = self .parent() .command("schroot") .arg("-b") .arg("-c") .arg(&self.name) .output()?; if !output.status.success() { return Err(io::Error::other(format!( "Failed to create schroot session: {}", String::from_utf8_lossy(&output.stderr) ))); } let session_id = String::from_utf8(output.stdout) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? .trim() .to_string(); *session_lock = Some(session_id.clone()); Ok(session_id) } fn get_session_location(&self, session_id: &str) -> io::Result { let output = self .parent() .command("schroot") .arg("--location") .arg("-c") .arg(session_id) .output()?; if !output.status.success() { return Err(io::Error::other(format!( "Failed to get schroot location: {}", String::from_utf8_lossy(&output.stderr) ))); } Ok(String::from_utf8(output.stdout) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? .trim() .to_string()) } } impl ContextDriver for SchrootDriver { fn ensure_available(&self, src: &Path, _dest_root: &str) -> io::Result { src.canonicalize() } fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()> { let session_id = self.ensure_session()?; let location = self.get_session_location(&session_id)?; let path_in_chroot = src.strip_prefix("/").unwrap_or(src); let host_src = Path::new(&location).join(path_in_chroot); self.parent().retrieve_path(&host_src, dest) } fn list_files(&self, path: &Path) -> io::Result> { let session_id = self.ensure_session()?; let location = self.get_session_location(&session_id)?; let path_in_chroot = path.strip_prefix("/").unwrap_or(path); let host_path = Path::new(&location).join(path_in_chroot); let files = self.parent().list_files(&host_path)?; let mut chroot_files = Vec::new(); // TODO: Check if we *need* to strip the prefix. // If we don't, we can just return `files`. for file in files { if let Ok(rel) = file.strip_prefix(&location) { chroot_files.push(Path::new("/").join(rel)); } else { chroot_files.push(file); } } Ok(chroot_files) } fn run( &self, program: &str, args: &[String], env: &[(String, String)], cwd: Option<&str>, ) -> io::Result { let session_id = self.ensure_session()?; // Construct the schroot command // schroot -p -r -c session_id -- program args... // If cwd is specified, we wrap in sh -c "cd cwd && ..." let mut command_args = vec![ "-p".to_string(), "-r".to_string(), "-c".to_string(), session_id, "--".to_string(), ]; let mut actual_program = program.to_string(); let mut actual_args = args.to_vec(); // Simplest: Wrap everything in `sh -c` if CWD or ENV is needed. if cwd.is_some() || !env.is_empty() { let mut shell_cmd = String::new(); if let Some(dir) = cwd { shell_cmd.push_str(&format!("cd {} && ", dir)); } if !env.is_empty() { shell_cmd.push_str("env "); for (k, v) in env { shell_cmd.push_str(&format!("{}={} ", k, v)); } } shell_cmd.push_str(&format!("{} {}", program, args.join(" "))); actual_program = "sh".to_string(); actual_args = vec!["-c".to_string(), shell_cmd]; } command_args.push(actual_program); command_args.extend(actual_args); self.parent().command("schroot").args(command_args).status() } fn run_output( &self, program: &str, args: &[String], env: &[(String, String)], cwd: Option<&str>, ) -> io::Result { let session_id = self.ensure_session()?; let mut command_args = vec![ "-r".to_string(), "-c".to_string(), session_id, "--".to_string(), ]; let mut actual_program = program.to_string(); let mut actual_args = args.to_vec(); if cwd.is_some() || !env.is_empty() { let mut shell_cmd = String::new(); if let Some(dir) = cwd { shell_cmd.push_str(&format!("cd {} && ", dir)); } if !env.is_empty() { shell_cmd.push_str("env "); for (k, v) in env { shell_cmd.push_str(&format!("{}={} ", k, v)); } } shell_cmd.push_str(&format!("{} {}", program, args.join(" "))); actual_program = "sh".to_string(); actual_args = vec!["-c".to_string(), shell_cmd]; } command_args.push(actual_program); command_args.extend(actual_args); self.parent().command("schroot").args(command_args).output() } fn create_temp_dir(&self) -> io::Result { let output = self.run_output("mktemp", &["-d".to_string()], &[], None)?; if !output.status.success() { return Err(io::Error::other("schroot mktemp failed")); } Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> { let status = self.run( "cp", &[ "-a".to_string(), src.to_string_lossy().to_string(), dest.to_string_lossy().to_string(), ], &[], None, )?; if !status.success() { return Err(io::Error::other("schroot copy failed")); } Ok(()) } fn read_file(&self, path: &Path) -> io::Result { let output = self.run_output("cat", &[path.to_string_lossy().to_string()], &[], None)?; if !output.status.success() { return Err(io::Error::other(format!( "schroot read failed: {}", String::from_utf8_lossy(&output.stderr) ))); } String::from_utf8(output.stdout).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } fn write_file(&self, path: &Path, content: &str) -> io::Result<()> { let status = self.run( "sh", &[ "-c".to_string(), format!( "echo -ne '{}' > '{}'", content.replace("'", "'\\''"), path.to_string_lossy() ), ], &[], None, )?; if !status.success() { return Err(io::Error::other("schroot write failed")); } Ok(()) } fn exists(&self, path: &Path) -> io::Result { let status = self.run( "test", &["-e".to_string(), path.to_string_lossy().to_string()], &[], None, )?; Ok(status.success()) } }