exp: cross #2
This commit is contained in:
@@ -1,19 +1,37 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::local::LocalDriver;
|
||||
use super::ssh::SshDriver;
|
||||
use super::schroot::SchrootDriver;
|
||||
use super::ssh::SshDriver;
|
||||
use super::unshare::UnshareDriver;
|
||||
|
||||
pub trait ContextDriver {
|
||||
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf>;
|
||||
fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()>;
|
||||
fn list_files(&self, path: &Path) -> io::Result<Vec<PathBuf>>;
|
||||
fn run(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::ExitStatus>;
|
||||
fn run_output(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::Output>;
|
||||
fn prepare_work_dir(&self) -> io::Result<String>;
|
||||
fn run(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::ExitStatus>;
|
||||
fn run_output(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::Output>;
|
||||
fn create_temp_dir(&self) -> io::Result<String>;
|
||||
fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()>;
|
||||
fn read_file(&self, path: &Path) -> io::Result<String>;
|
||||
fn write_file(&self, path: &Path, content: &str) -> io::Result<()>;
|
||||
}
|
||||
|
||||
/// Represents an execution environment (Local or via SSH).
|
||||
@@ -35,19 +53,55 @@ pub enum ContextConfig {
|
||||
#[serde(rename = "schroot")]
|
||||
Schroot {
|
||||
name: String,
|
||||
}
|
||||
parent: Option<String>,
|
||||
},
|
||||
#[serde(rename = "unshare")]
|
||||
Unshare {
|
||||
path: String,
|
||||
parent: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
config: ContextConfig,
|
||||
driver: RefCell<Option<Box<dyn ContextDriver>>>,
|
||||
pub config: ContextConfig,
|
||||
pub parent: Option<Arc<Context>>,
|
||||
driver: Mutex<Option<Box<dyn ContextDriver + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(config: ContextConfig) -> Self {
|
||||
let parent = match &config {
|
||||
ContextConfig::Schroot {
|
||||
parent: Some(parent_name),
|
||||
..
|
||||
}
|
||||
| ContextConfig::Unshare {
|
||||
parent: Some(parent_name),
|
||||
..
|
||||
} => {
|
||||
let config_lock = crate::context::manager::MANAGER.get_config();
|
||||
let parent_config = config_lock
|
||||
.contexts
|
||||
.get(parent_name)
|
||||
.cloned()
|
||||
.expect("Parent context not found");
|
||||
Some(Arc::new(Context::new(parent_config)))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
config,
|
||||
driver: RefCell::new(None),
|
||||
parent,
|
||||
driver: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_parent(config: ContextConfig, parent: Arc<Context>) -> Self {
|
||||
Self {
|
||||
config,
|
||||
parent: Some(parent),
|
||||
driver: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +111,7 @@ impl Context {
|
||||
program: program.as_ref().to_string_lossy().to_string(),
|
||||
args: Vec::new(),
|
||||
env: Vec::new(),
|
||||
cwd: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +120,14 @@ impl Context {
|
||||
/// If Local: Returns the absolute path of `src`.
|
||||
/// If Remote: Copies `src` to `dest_root` on the remote and returns the path to the copied entity.
|
||||
pub fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf> {
|
||||
self.driver().ensure_available(src, dest_root)
|
||||
self.driver()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.ensure_available(src, dest_root)
|
||||
}
|
||||
|
||||
pub fn prepare_work_dir(&self) -> io::Result<String> {
|
||||
self.driver().prepare_work_dir()
|
||||
pub fn create_temp_dir(&self) -> io::Result<String> {
|
||||
self.driver().as_ref().unwrap().create_temp_dir()
|
||||
}
|
||||
|
||||
/// Retrieve a file or directory from the context to the local filesystem.
|
||||
@@ -77,31 +135,59 @@ impl Context {
|
||||
/// The `src` path is on the context, `dest` is on the local machine.
|
||||
/// If `src` is a directory, it is copied recursively.
|
||||
pub fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
|
||||
self.driver().retrieve_path(src, dest)
|
||||
self.driver().as_ref().unwrap().retrieve_path(src, dest)
|
||||
}
|
||||
|
||||
/// List files in a directory on the context.
|
||||
pub fn list_files(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
self.driver().list_files(path)
|
||||
self.driver().as_ref().unwrap().list_files(path)
|
||||
}
|
||||
|
||||
fn driver(&self) -> Ref<Box<dyn ContextDriver>> {
|
||||
if self.driver.borrow().is_none() {
|
||||
let driver: Box<dyn ContextDriver> = match &self.config {
|
||||
pub fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
|
||||
self.driver().as_ref().unwrap().copy_path(src, dest)
|
||||
}
|
||||
|
||||
pub fn read_file(&self, path: &Path) -> io::Result<String> {
|
||||
self.driver().as_ref().unwrap().read_file(path)
|
||||
}
|
||||
|
||||
pub fn write_file(&self, path: &Path, content: &str) -> io::Result<()> {
|
||||
self.driver().as_ref().unwrap().write_file(path, content)
|
||||
}
|
||||
|
||||
pub fn driver(
|
||||
&self,
|
||||
) -> std::sync::MutexGuard<'_, Option<Box<dyn ContextDriver + Send + Sync>>> {
|
||||
let mut driver_lock = self.driver.lock().unwrap();
|
||||
if driver_lock.is_none() {
|
||||
let driver: Box<dyn ContextDriver + Send + Sync> = match &self.config {
|
||||
ContextConfig::Local => Box::new(LocalDriver),
|
||||
ContextConfig::Ssh { host, user, port } => Box::new(SshDriver {
|
||||
host: host.clone(),
|
||||
user: user.clone(),
|
||||
port: *port,
|
||||
}),
|
||||
ContextConfig::Schroot { name } => Box::new(SchrootDriver {
|
||||
ContextConfig::Schroot { name, .. } => Box::new(SchrootDriver {
|
||||
name: name.clone(),
|
||||
session: RefCell::new(None),
|
||||
session: std::sync::Mutex::new(None),
|
||||
parent: self.parent.clone(),
|
||||
}),
|
||||
ContextConfig::Unshare { path, .. } => Box::new(UnshareDriver {
|
||||
path: path.clone(),
|
||||
parent: self.parent.clone(),
|
||||
}),
|
||||
};
|
||||
*self.driver.borrow_mut() = Some(driver);
|
||||
*driver_lock = Some(driver);
|
||||
}
|
||||
driver_lock
|
||||
}
|
||||
|
||||
pub fn clone_raw(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
parent: self.parent.clone(),
|
||||
driver: std::sync::Mutex::new(None),
|
||||
}
|
||||
Ref::map(self.driver.borrow(), |opt| opt.as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +203,7 @@ pub struct ContextCommand<'a> {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
cwd: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ContextCommand<'a> {
|
||||
@@ -161,12 +248,27 @@ impl<'a> ContextCommand<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn current_dir<P: AsRef<OsStr>>(&mut self, dir: P) -> &mut Self {
|
||||
self.cwd = Some(dir.as_ref().to_string_lossy().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn status(&mut self) -> io::Result<std::process::ExitStatus> {
|
||||
self.context.driver().run(&self.program, &self.args, &self.env)
|
||||
self.context.driver().as_ref().unwrap().run(
|
||||
&self.program,
|
||||
&self.args,
|
||||
&self.env,
|
||||
self.cwd.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
// Capture output
|
||||
pub fn output(&mut self) -> io::Result<std::process::Output> {
|
||||
self.context.driver().run_output(&self.program, &self.args, &self.env)
|
||||
self.context.driver().as_ref().unwrap().run_output(
|
||||
&self.program,
|
||||
&self.args,
|
||||
&self.env,
|
||||
self.cwd.as_deref(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ impl ContextDriver for LocalDriver {
|
||||
src.canonicalize()
|
||||
}
|
||||
|
||||
fn prepare_work_dir(&self) -> io::Result<String> {
|
||||
// TODO: Fix that, we should not always use '..' as work directory locally
|
||||
Ok("..".to_string())
|
||||
fn create_temp_dir(&self) -> io::Result<String> {
|
||||
let temp_dir = tempfile::Builder::new().prefix("pkh-").tempdir()?;
|
||||
Ok(temp_dir.keep().to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
fn retrieve_path(&self, _src: &Path, _dest: &Path) -> io::Result<()> {
|
||||
@@ -30,11 +30,60 @@ impl ContextDriver for LocalDriver {
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn run(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::ExitStatus> {
|
||||
Command::new(program).args(args).envs(env.iter().map(|(k, v)| (k, v))).status()
|
||||
fn run(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::ExitStatus> {
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
|
||||
if let Some(dir) = cwd {
|
||||
cmd.current_dir(dir);
|
||||
}
|
||||
cmd.status()
|
||||
}
|
||||
|
||||
fn run_output(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::Output> {
|
||||
Command::new(program).args(args).envs(env.iter().map(|(k, v)| (k, v))).output()
|
||||
fn run_output(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::Output> {
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args(args).envs(env.iter().map(|(k, v)| (k, v)));
|
||||
if let Some(dir) = cwd {
|
||||
cmd.current_dir(dir);
|
||||
}
|
||||
cmd.output()
|
||||
}
|
||||
|
||||
fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
|
||||
copy_dir_recursive(src, dest)
|
||||
}
|
||||
|
||||
fn read_file(&self, path: &Path) -> io::Result<String> {
|
||||
std::fs::read_to_string(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &Path, content: &str) -> io::Result<()> {
|
||||
std::fs::write(path, content)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
if src.is_dir() {
|
||||
std::fs::create_dir_all(dest)?;
|
||||
for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let dest_path = dest.join(entry.file_name());
|
||||
copy_dir_recursive(&path, &dest_path)?;
|
||||
}
|
||||
} else {
|
||||
std::fs::copy(src, dest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,22 +4,39 @@ use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use super::api::{Context, ContextConfig};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub current_context: Option<String>,
|
||||
pub context: String,
|
||||
pub contexts: HashMap<String, ContextConfig>,
|
||||
}
|
||||
|
||||
pub struct ContextManager {
|
||||
config_path: PathBuf,
|
||||
config: Config,
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let mut contexts = HashMap::new();
|
||||
contexts.insert("local".to_string(), ContextConfig::Local);
|
||||
Self {
|
||||
context: "local".to_string(),
|
||||
contexts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextManager {
|
||||
context: RwLock<Arc<Context>>,
|
||||
config_path: PathBuf,
|
||||
config: RwLock<Config>,
|
||||
}
|
||||
|
||||
pub static MANAGER: std::sync::LazyLock<ContextManager> =
|
||||
std::sync::LazyLock::new(|| ContextManager::new().expect("Cannot setup context manager"));
|
||||
|
||||
impl ContextManager {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
fn new() -> io::Result<Self> {
|
||||
let proj_dirs = ProjectDirs::from("com", "pkh", "pkh").ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
@@ -31,67 +48,101 @@ impl ContextManager {
|
||||
let config_path = config_dir.join("contexts.json");
|
||||
|
||||
let config = if config_path.exists() {
|
||||
// Load existing configuration file
|
||||
let content = fs::read_to_string(&config_path)?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
|
||||
} else {
|
||||
let mut cfg = Config::default();
|
||||
cfg.contexts.insert("local".to_string(), ContextConfig::Local);
|
||||
cfg.current_context = Some("local".to_string());
|
||||
cfg
|
||||
// Create a new configuration file
|
||||
Config::default()
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
context: RwLock::new(Arc::new(Self::make_context(
|
||||
config.context.as_str(),
|
||||
&config,
|
||||
))),
|
||||
config_path,
|
||||
config,
|
||||
config: RwLock::new(config),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> std::sync::RwLockReadGuard<'_, Config> {
|
||||
self.config.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn with_path(path: PathBuf) -> Self {
|
||||
let config = Config::default();
|
||||
Self {
|
||||
context: RwLock::new(Arc::new(Self::make_context("local", &config))),
|
||||
config_path: path,
|
||||
config: Config::default(),
|
||||
config: RwLock::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> io::Result<()> {
|
||||
let content = serde_json::to_string_pretty(&self.config)
|
||||
let config = self.config.read().unwrap();
|
||||
let content = serde_json::to_string_pretty(&*config)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
fs::write(&self.config_path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_context(name: &str, config: &Config) -> Context {
|
||||
let context_config = config
|
||||
.contexts
|
||||
.get(name)
|
||||
.cloned()
|
||||
.expect("Context not found in config");
|
||||
Context::new(context_config)
|
||||
}
|
||||
|
||||
pub fn list_contexts(&self) -> Vec<String> {
|
||||
self.config.contexts.keys().cloned().collect()
|
||||
self.config
|
||||
.read()
|
||||
.unwrap()
|
||||
.contexts
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_context(&self, name: &str) -> Option<Context> {
|
||||
self.config.contexts.get(name).map(|cfg| Context::new(cfg.clone()))
|
||||
}
|
||||
|
||||
pub fn add_context(&mut self, name: &str, config: ContextConfig) -> io::Result<()> {
|
||||
self.config.contexts.insert(name.to_string(), config);
|
||||
pub fn add_context(&self, name: &str, config: ContextConfig) -> io::Result<()> {
|
||||
self.config
|
||||
.write()
|
||||
.unwrap()
|
||||
.contexts
|
||||
.insert(name.to_string(), config);
|
||||
self.save()
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, name: &str) -> io::Result<()> {
|
||||
if self.config.contexts.remove(name).is_some() {
|
||||
if self.config.current_context.as_deref() == Some(name) {
|
||||
self.config.current_context = Some("local".to_string());
|
||||
if !self.config.contexts.contains_key("local") {
|
||||
self.config
|
||||
.contexts
|
||||
.insert("local".to_string(), ContextConfig::Local);
|
||||
}
|
||||
pub fn remove_context(&self, name: &str) -> io::Result<()> {
|
||||
let mut config = self.config.write().unwrap();
|
||||
if name == "local" {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Cannot remove local context",
|
||||
));
|
||||
}
|
||||
if config.contexts.remove(name).is_some() {
|
||||
// If we are removing the current context, fallback to local
|
||||
if name == config.context {
|
||||
config.context = "local".to_string();
|
||||
self.set_current_ephemeral(Self::make_context("local", &config));
|
||||
}
|
||||
|
||||
drop(config); // Drop write lock before saving
|
||||
self.save()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_current(&mut self, name: &str) -> io::Result<()> {
|
||||
if self.config.contexts.contains_key(name) {
|
||||
self.config.current_context = Some(name.to_string());
|
||||
pub fn set_current(&self, name: &str) -> io::Result<()> {
|
||||
let mut config = self.config.write().unwrap();
|
||||
if config.contexts.contains_key(name) {
|
||||
config.context = name.to_string();
|
||||
self.set_current_ephemeral(Self::make_context(name, &config));
|
||||
drop(config); // Drop write lock before saving
|
||||
self.save()?;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -102,16 +153,15 @@ impl ContextManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Context {
|
||||
self.config
|
||||
.current_context
|
||||
.as_deref()
|
||||
.and_then(|name| self.config.contexts.get(name))
|
||||
.map(|cfg| Context::new(cfg.clone()))
|
||||
.unwrap_or_else(|| Context::new(ContextConfig::Local))
|
||||
pub fn set_current_ephemeral(&self, context: Context) {
|
||||
*self.context.write().unwrap() = context.into();
|
||||
}
|
||||
|
||||
pub fn current_name(&self) -> Option<String> {
|
||||
self.config.current_context.clone()
|
||||
pub fn current(&self) -> Arc<Context> {
|
||||
self.context.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn current_name(&self) -> String {
|
||||
self.config.read().unwrap().context.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
mod api;
|
||||
mod local;
|
||||
mod manager;
|
||||
mod ssh;
|
||||
mod schroot;
|
||||
mod ssh;
|
||||
mod unshare;
|
||||
|
||||
pub use api::{Context, ContextCommand, ContextConfig};
|
||||
pub use manager::ContextManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn current_context() -> Context {
|
||||
match ContextManager::new() {
|
||||
Ok(mgr) => mgr.current(),
|
||||
Err(_) => Context::new(ContextConfig::Local),
|
||||
}
|
||||
pub fn manager() -> &'static ContextManager {
|
||||
&manager::MANAGER
|
||||
}
|
||||
|
||||
pub fn current() -> Arc<Context> {
|
||||
manager::MANAGER.current()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -38,7 +41,7 @@ mod tests {
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let path = temp_file.path().to_path_buf();
|
||||
|
||||
let mut mgr = ContextManager::with_path(path.clone());
|
||||
let mgr = ContextManager::with_path(path.clone());
|
||||
|
||||
// Add
|
||||
let ssh_cfg = ContextConfig::Ssh {
|
||||
@@ -48,7 +51,7 @@ mod tests {
|
||||
};
|
||||
mgr.add_context("myserver", ssh_cfg.clone()).unwrap();
|
||||
|
||||
assert!(mgr.get_context("myserver").is_some());
|
||||
assert!(mgr.list_contexts().contains(&"myserver".to_string()));
|
||||
|
||||
// List
|
||||
let list = mgr.list_contexts();
|
||||
@@ -56,11 +59,11 @@ mod tests {
|
||||
|
||||
// Set Current
|
||||
mgr.set_current("myserver").unwrap();
|
||||
assert_eq!(mgr.current_name(), Some("myserver".to_string()));
|
||||
assert_eq!(mgr.current_name(), "myserver".to_string());
|
||||
|
||||
// Remove
|
||||
mgr.remove_context("myserver").unwrap();
|
||||
assert!(mgr.get_context("myserver").is_none());
|
||||
assert!(!mgr.list_contexts().contains(&"myserver".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -69,7 +72,7 @@ mod tests {
|
||||
let config_path = temp_dir.path().join("contexts.json");
|
||||
|
||||
{
|
||||
let mut mgr = ContextManager::with_path(config_path.clone());
|
||||
let mgr = ContextManager::with_path(config_path.clone());
|
||||
mgr.add_context("persistent", ContextConfig::Local).unwrap();
|
||||
mgr.set_current("persistent").unwrap();
|
||||
}
|
||||
@@ -77,10 +80,64 @@ mod tests {
|
||||
let content = fs::read_to_string(&config_path).unwrap();
|
||||
let loaded_config: super::manager::Config = serde_json::from_str(&content).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
loaded_config.current_context,
|
||||
Some("persistent".to_string())
|
||||
);
|
||||
assert_eq!(loaded_config.context, "persistent".to_string());
|
||||
assert!(loaded_config.contexts.contains_key("persistent"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_fallback_on_removal() {
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let path = temp_file.path().to_path_buf();
|
||||
let mgr = ContextManager::with_path(path);
|
||||
|
||||
// 1. Add and set a context
|
||||
mgr.add_context("temp", ContextConfig::Local).unwrap();
|
||||
mgr.set_current("temp").unwrap();
|
||||
assert_eq!(mgr.current_name(), "temp");
|
||||
|
||||
// 2. Remove it
|
||||
mgr.remove_context("temp").unwrap();
|
||||
|
||||
// 3. Should have fallen back to local
|
||||
assert_eq!(mgr.current_name(), "local");
|
||||
assert!(mgr.list_contexts().contains(&"local".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_file_ops() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let ctx = super::manager().current();
|
||||
|
||||
let file_path = temp_dir.path().join("test.txt");
|
||||
let content = "hello world";
|
||||
|
||||
// 1. Write file
|
||||
ctx.write_file(&file_path, content).unwrap();
|
||||
|
||||
// 2. Read file
|
||||
let read_content = ctx.read_file(&file_path).unwrap();
|
||||
assert_eq!(read_content, content);
|
||||
|
||||
// 3. Copy path
|
||||
let dest_path = temp_dir.path().join("test_copy.txt");
|
||||
ctx.copy_path(&file_path, &dest_path).unwrap();
|
||||
let copied_content = ctx.read_file(&dest_path).unwrap();
|
||||
assert_eq!(copied_content, content);
|
||||
|
||||
// 4. Recursive copy
|
||||
let subdir = temp_dir.path().join("subdir");
|
||||
std::fs::create_dir_all(&subdir).unwrap();
|
||||
let subfile = subdir.join("subfile.txt");
|
||||
ctx.write_file(&subfile, "subcontent").unwrap();
|
||||
|
||||
let subdir_copy = temp_dir.path().join("subdir_copy");
|
||||
ctx.copy_path(&subdir, &subdir_copy).unwrap();
|
||||
|
||||
assert!(subdir_copy.exists());
|
||||
assert!(subdir_copy.join("subfile.txt").exists());
|
||||
assert_eq!(
|
||||
ctx.read_file(&subdir_copy.join("subfile.txt")).unwrap(),
|
||||
"subcontent"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use super::api::ContextDriver;
|
||||
use std::cell::RefCell;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use log::debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct SchrootDriver {
|
||||
pub name: String,
|
||||
pub session: RefCell<Option<String>>,
|
||||
pub session: std::sync::Mutex<Option<String>>,
|
||||
pub parent: Option<Arc<super::api::Context>>,
|
||||
}
|
||||
|
||||
impl ContextDriver for SchrootDriver {
|
||||
fn ensure_available(&self, _src: &Path, _dest_root: &str) -> io::Result<PathBuf> {
|
||||
// TODO: Implement schroot file transfer logic
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"ensure_available not yet implemented for schroot",
|
||||
))
|
||||
fn ensure_available(&self, src: &Path, _dest_root: &str) -> io::Result<PathBuf> {
|
||||
src.canonicalize()
|
||||
}
|
||||
|
||||
fn retrieve_path(&self, _src: &Path, _dest: &Path) -> io::Result<()> {
|
||||
@@ -34,78 +30,226 @@ impl ContextDriver for SchrootDriver {
|
||||
))
|
||||
}
|
||||
|
||||
fn run(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::ExitStatus> {
|
||||
fn run(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::ExitStatus> {
|
||||
// Initialize session on first run
|
||||
if self.session.borrow().is_none() {
|
||||
let session_output = std::process::Command::new("schroot")
|
||||
.arg("-b")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.output()?;
|
||||
|
||||
let mut session_lock = self.session.lock().unwrap();
|
||||
if session_lock.is_none() {
|
||||
let session_output = if let Some(parent) = &self.parent {
|
||||
parent
|
||||
.command("schroot")
|
||||
.arg("-b")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.output()?
|
||||
} else {
|
||||
std::process::Command::new("schroot")
|
||||
.arg("-b")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.output()?
|
||||
};
|
||||
|
||||
if !session_output.status.success() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to create schroot session: {}",
|
||||
String::from_utf8_lossy(&session_output.stderr))
|
||||
));
|
||||
return Err(io::Error::other(format!(
|
||||
"Failed to create schroot session: {}",
|
||||
String::from_utf8_lossy(&session_output.stderr)
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
let session_id = String::from_utf8(session_output.stdout)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
*self.session.borrow_mut() = Some(session_id);
|
||||
|
||||
*session_lock = Some(session_id);
|
||||
}
|
||||
drop(session_lock);
|
||||
|
||||
let session_id = self.session.borrow();
|
||||
let session_id = session_id.as_ref().unwrap();
|
||||
let session_lock = self.session.lock().unwrap();
|
||||
let session_id = session_lock.as_ref().unwrap();
|
||||
|
||||
let mut cmd = std::process::Command::new("sudo");
|
||||
cmd
|
||||
.args(env.iter().map(|(k, v)| format!("{k}={v}")))
|
||||
.arg("schroot")
|
||||
.arg("-p") // Preserve environment
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(session_id)
|
||||
.arg("--")
|
||||
.arg(program)
|
||||
.args(args);
|
||||
|
||||
debug!("Executing: {:?}", cmd);
|
||||
if let Some(parent) = &self.parent {
|
||||
let mut cmd = parent.command("sudo");
|
||||
cmd.envs(env.iter().cloned());
|
||||
|
||||
cmd.status()
|
||||
}
|
||||
|
||||
fn run_output(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::Output> {
|
||||
let mut cmd = std::process::Command::new("sudo");
|
||||
cmd
|
||||
.arg("schroot")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.arg("--");
|
||||
|
||||
// Handle env variables for schroot by wrapping in env command
|
||||
if !env.is_empty() {
|
||||
cmd.arg("env");
|
||||
for (k, v) in env {
|
||||
cmd.arg(format!("{}={}", k, v));
|
||||
if let Some(dir) = cwd {
|
||||
cmd.arg("schroot")
|
||||
.arg("-p")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(session_id)
|
||||
.arg("--")
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("cd {} && {} {}", dir, program, args.join(" ")));
|
||||
} else {
|
||||
cmd.arg("schroot")
|
||||
.arg("-p")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(session_id)
|
||||
.arg("--")
|
||||
.arg(program)
|
||||
.args(args);
|
||||
}
|
||||
cmd.status()
|
||||
} else {
|
||||
let mut cmd = std::process::Command::new("sudo");
|
||||
cmd.args(env.iter().map(|(k, v)| format!("{k}={v}")));
|
||||
|
||||
if let Some(dir) = cwd {
|
||||
cmd.arg("schroot")
|
||||
.arg("-p")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(session_id)
|
||||
.arg("--")
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("cd {} && {} {}", dir, program, args.join(" ")));
|
||||
} else {
|
||||
cmd.arg("schroot")
|
||||
.arg("-p")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(session_id)
|
||||
.arg("--")
|
||||
.arg(program)
|
||||
.args(args);
|
||||
}
|
||||
cmd.status()
|
||||
}
|
||||
|
||||
cmd.arg(program).args(args);
|
||||
|
||||
cmd.output()
|
||||
}
|
||||
|
||||
fn prepare_work_dir(&self) -> io::Result<String> {
|
||||
// TODO: Implement schroot work dir creation
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"prepare_work_dir not yet implemented for schroot",
|
||||
))
|
||||
fn run_output(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::Output> {
|
||||
if let Some(parent) = &self.parent {
|
||||
let mut cmd = parent.command("sudo");
|
||||
cmd.envs(env.iter().cloned());
|
||||
|
||||
if let Some(dir) = cwd {
|
||||
cmd.arg("schroot")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.arg("--")
|
||||
.arg("sh")
|
||||
.arg("-c");
|
||||
|
||||
let mut cmd_str = String::new();
|
||||
cmd_str.push_str(&format!("cd {} && {} {}", dir, program, args.join(" ")));
|
||||
cmd.arg(cmd_str);
|
||||
} else {
|
||||
cmd.arg("schroot")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.arg("--");
|
||||
|
||||
cmd.arg(program).args(args);
|
||||
}
|
||||
cmd.output()
|
||||
} else {
|
||||
let mut cmd = std::process::Command::new("sudo");
|
||||
if let Some(dir) = cwd {
|
||||
cmd.arg("schroot")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.arg("--")
|
||||
.arg("sh")
|
||||
.arg("-c");
|
||||
|
||||
let mut cmd_str = String::new();
|
||||
for (k, v) in env {
|
||||
cmd_str.push_str(&format!("{}={} ", k, v));
|
||||
}
|
||||
cmd_str.push_str(&format!("cd {} && {} {}", dir, program, args.join(" ")));
|
||||
cmd.arg(cmd_str);
|
||||
} else {
|
||||
cmd.arg("schroot")
|
||||
.arg("-r")
|
||||
.arg("-c")
|
||||
.arg(&self.name)
|
||||
.arg("--");
|
||||
|
||||
if !env.is_empty() {
|
||||
cmd.arg("env");
|
||||
for (k, v) in env {
|
||||
cmd.arg(format!("{}={}", k, v));
|
||||
}
|
||||
}
|
||||
cmd.arg(program).args(args);
|
||||
}
|
||||
cmd.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<()> {
|
||||
// TODO: change that command, it's not safe
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
/// Context driver: Copies over SFTP with ssh2, executes commands over ssh2 channels
|
||||
use super::api::ContextDriver;
|
||||
use log::debug;
|
||||
use ssh2;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::io::{self, Read};
|
||||
use std::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
@@ -90,7 +92,13 @@ impl ContextDriver for SshDriver {
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn run(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::ExitStatus> {
|
||||
fn run(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::ExitStatus> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let mut channel = sess.channel_session().map_err(io::Error::other)?;
|
||||
|
||||
@@ -98,7 +106,14 @@ impl ContextDriver for SshDriver {
|
||||
// TODO: No, use ssh2 channel.set_env
|
||||
let mut cmd_line = String::new();
|
||||
for (key, value) in env {
|
||||
cmd_line.push_str(&format!("export {}='{}'; ", key, value.replace("'", "'\\''")));
|
||||
cmd_line.push_str(&format!(
|
||||
"export {}='{}'; ",
|
||||
key,
|
||||
value.replace("'", "'\\''")
|
||||
));
|
||||
}
|
||||
if let Some(dir) = cwd {
|
||||
cmd_line.push_str(&format!("cd {} && ", dir));
|
||||
}
|
||||
cmd_line.push_str(program);
|
||||
for arg in args {
|
||||
@@ -124,14 +139,27 @@ impl ContextDriver for SshDriver {
|
||||
Ok(ExitStatus::from_raw(code))
|
||||
}
|
||||
|
||||
fn run_output(&self, program: &str, args: &[String], env: &[(String, String)]) -> io::Result<std::process::Output> {
|
||||
fn run_output(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::Output> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let mut channel = sess.channel_session().map_err(io::Error::other)?;
|
||||
|
||||
// Construct command line with env vars
|
||||
let mut cmd_line = String::new();
|
||||
for (key, value) in env {
|
||||
cmd_line.push_str(&format!("export {}='{}'; ", key, value.replace("'", "'\\''")));
|
||||
cmd_line.push_str(&format!(
|
||||
"export {}='{}'; ",
|
||||
key,
|
||||
value.replace("'", "'\\''")
|
||||
));
|
||||
}
|
||||
if let Some(dir) = cwd {
|
||||
cmd_line.push_str(&format!("cd {} && ", dir));
|
||||
}
|
||||
cmd_line.push_str(program);
|
||||
for arg in args {
|
||||
@@ -164,7 +192,7 @@ impl ContextDriver for SshDriver {
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_work_dir(&self) -> io::Result<String> {
|
||||
fn create_temp_dir(&self) -> io::Result<String> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let mut channel = sess.channel_session().map_err(io::Error::other)?;
|
||||
|
||||
@@ -182,6 +210,40 @@ impl ContextDriver for SshDriver {
|
||||
|
||||
Ok(stdout.trim().to_string())
|
||||
}
|
||||
|
||||
fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let mut channel = sess.channel_session().map_err(io::Error::other)?;
|
||||
// TODO: use sftp
|
||||
let cmd = format!("cp -a {:?} {:?}", src, dest);
|
||||
debug!("Executing remote copy: {}", cmd);
|
||||
channel.exec(&cmd).map_err(io::Error::other)?;
|
||||
channel.wait_close().map_err(io::Error::other)?;
|
||||
if channel.exit_status().unwrap_or(-1) != 0 {
|
||||
return Err(io::Error::other(format!("Remote copy failed: {}", cmd)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_file(&self, path: &Path) -> io::Result<String> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let sftp = sess.sftp().map_err(io::Error::other)?;
|
||||
let mut remote_file = sftp.open(path).map_err(io::Error::other)?;
|
||||
let mut content = String::new();
|
||||
remote_file.read_to_string(&mut content)?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &Path, content: &str) -> io::Result<()> {
|
||||
let sess = connect_ssh(&self.host, self.user.as_deref(), self.port)?;
|
||||
let sftp = sess.sftp().map_err(io::Error::other)?;
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = sftp.mkdir(parent, 0o755);
|
||||
}
|
||||
let mut remote_file = sftp.create(path).map_err(io::Error::other)?;
|
||||
remote_file.write_all(content.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SshDriver {
|
||||
|
||||
177
src/context/unshare.rs
Normal file
177
src/context/unshare.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
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<Arc<super::api::Context>>,
|
||||
}
|
||||
|
||||
/// 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<PathBuf> {
|
||||
// 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<Vec<PathBuf>> {
|
||||
// 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<std::process::ExitStatus> {
|
||||
self.command(program, args, env, cwd).status()
|
||||
}
|
||||
|
||||
fn run_output(
|
||||
&self,
|
||||
program: &str,
|
||||
args: &[String],
|
||||
env: &[(String, String)],
|
||||
cwd: Option<&str>,
|
||||
) -> io::Result<std::process::Output> {
|
||||
self.command(program, args, env, cwd).output()
|
||||
}
|
||||
|
||||
fn create_temp_dir(&self) -> io::Result<String> {
|
||||
// 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<String> {
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user