287 lines
8.8 KiB
Rust
287 lines
8.8 KiB
Rust
/// Schroot context: execute commands in a schroot session
|
|
/// Not tested, will need more work!
|
|
use super::api::{ContextDriver, LineSink};
|
|
use std::io;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
|
|
pub struct SchrootDriver {
|
|
pub name: String,
|
|
pub session: std::sync::Mutex<Option<String>>,
|
|
pub parent: Option<Arc<super::api::Context>>,
|
|
}
|
|
|
|
use super::api::{Context, ContextConfig};
|
|
|
|
impl SchrootDriver {
|
|
fn parent(&self) -> Arc<Context> {
|
|
self.parent
|
|
.clone()
|
|
.unwrap_or_else(|| Arc::new(Context::new(ContextConfig::Local)))
|
|
}
|
|
|
|
fn ensure_session(&self) -> io::Result<String> {
|
|
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<String> {
|
|
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())
|
|
}
|
|
|
|
/// Build the `schroot` argument list executing `(program, args)` inside
|
|
/// the given session.
|
|
///
|
|
/// When `preserve_env` is set, `-p` is passed so the host environment is
|
|
/// preserved (historical behavior of `run()`, while `run_output()` does
|
|
/// not preserve it).
|
|
fn schroot_args(
|
|
session_id: &str,
|
|
preserve_env: bool,
|
|
program: &str,
|
|
args: &[String],
|
|
env: &[(String, String)],
|
|
cwd: Option<&str>,
|
|
) -> Vec<String> {
|
|
let mut command_args = Vec::new();
|
|
if preserve_env {
|
|
command_args.push("-p".to_string());
|
|
}
|
|
command_args.extend([
|
|
"-r".to_string(),
|
|
"-c".to_string(),
|
|
session_id.to_string(),
|
|
"--".to_string(),
|
|
]);
|
|
|
|
let (actual_program, actual_args) = Self::wrap_command(program, args, env, cwd);
|
|
command_args.push(actual_program);
|
|
command_args.extend(actual_args);
|
|
command_args
|
|
}
|
|
|
|
/// Wrap `(program, args)` in `sh -c` when a working directory or
|
|
/// environment variables are needed.
|
|
fn wrap_command(
|
|
program: &str,
|
|
args: &[String],
|
|
env: &[(String, String)],
|
|
cwd: Option<&str>,
|
|
) -> (String, Vec<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];
|
|
}
|
|
|
|
(actual_program, actual_args)
|
|
}
|
|
}
|
|
|
|
impl ContextDriver for SchrootDriver {
|
|
fn ensure_available(&self, src: &Path, _dest_root: &str) -> io::Result<PathBuf> {
|
|
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<Vec<PathBuf>> {
|
|
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<std::process::ExitStatus> {
|
|
let session_id = self.ensure_session()?;
|
|
let cmd_args = Self::schroot_args(&session_id, true, program, args, env, cwd);
|
|
self.parent().command("schroot").args(cmd_args).status()
|
|
}
|
|
|
|
fn run_captured(
|
|
&self,
|
|
program: &str,
|
|
args: &[String],
|
|
env: &[(String, String)],
|
|
cwd: Option<&str>,
|
|
sink: Arc<dyn LineSink>,
|
|
) -> io::Result<std::process::ExitStatus> {
|
|
let session_id = self.ensure_session()?;
|
|
let cmd_args = Self::schroot_args(&session_id, true, program, args, env, cwd);
|
|
// Forward the sink to the wrapping command so capture chains through
|
|
// the parent context driver (e.g. schroot over ssh).
|
|
let parent = self.parent();
|
|
let mut cmd = parent.command("schroot");
|
|
cmd.args(cmd_args).capture(sink);
|
|
cmd.status()
|
|
}
|
|
|
|
fn run_output(
|
|
&self,
|
|
program: &str,
|
|
args: &[String],
|
|
env: &[(String, String)],
|
|
cwd: Option<&str>,
|
|
) -> io::Result<std::process::Output> {
|
|
let session_id = self.ensure_session()?;
|
|
let cmd_args = Self::schroot_args(&session_id, false, program, args, env, cwd);
|
|
self.parent().command("schroot").args(cmd_args).output()
|
|
}
|
|
|
|
fn create_temp_dir(&self) -> io::Result<String> {
|
|
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<String> {
|
|
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<bool> {
|
|
let status = self.run(
|
|
"test",
|
|
&["-e".to_string(), path.to_string_lossy().to_string()],
|
|
&[],
|
|
None,
|
|
)?;
|
|
Ok(status.success())
|
|
}
|
|
}
|