Files
pkh/src/context/unshare.rs
2025-12-20 00:06:07 +01:00

178 lines
5.4 KiB
Rust

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
}
}