This commit is contained in:
2025-04-05 19:00:59 +02:00
parent 0fa9eddd1c
commit 21893ce225
18 changed files with 2458 additions and 36 deletions

View File

@@ -1,2 +1,3 @@
pub mod buildah;
pub mod nerdctl;
pub mod nerdctl;
pub mod rfs;

280
src/virt/rfs/builder.rs Normal file
View File

@@ -0,0 +1,280 @@
use std::collections::HashMap;
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::{Mount, MountType, StoreSpec},
};
/// Builder for RFS mount operations
#[derive(Clone)]
pub struct RfsBuilder {
/// Source path or URL
source: String,
/// Target mount point
target: String,
/// Mount type
mount_type: MountType,
/// Mount options
options: HashMap<String, String>,
/// Mount ID
mount_id: Option<String>,
/// Debug mode
debug: bool,
}
impl RfsBuilder {
/// Create a new RFS builder
///
/// # Arguments
///
/// * `source` - Source path or URL
/// * `target` - Target mount point
/// * `mount_type` - Mount type
///
/// # Returns
///
/// * `Self` - New RFS builder
pub fn new(source: &str, target: &str, mount_type: MountType) -> Self {
Self {
source: source.to_string(),
target: target.to_string(),
mount_type,
options: HashMap::new(),
mount_id: None,
debug: false,
}
}
/// Add a mount option
///
/// # Arguments
///
/// * `key` - Option key
/// * `value` - Option value
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
/// Add multiple mount options
///
/// # Arguments
///
/// * `options` - Map of option keys to values
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_options(mut self, options: HashMap<&str, &str>) -> Self {
for (key, value) in options {
self.options.insert(key.to_string(), value.to_string());
}
self
}
/// Set debug mode
///
/// # Arguments
///
/// * `debug` - Whether to enable debug output
///
/// # Returns
///
/// * `Self` - Updated RFS builder for method chaining
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
/// Mount the filesystem
///
/// # Returns
///
/// * `Result<Mount, RfsError>` - Mount information or error
pub fn mount(self) -> Result<Mount, RfsError> {
// Build the command string
let mut cmd = String::from("mount -t ");
cmd.push_str(&self.mount_type.to_string());
// Add options if any
if !self.options.is_empty() {
cmd.push_str(" -o ");
let mut first = true;
for (key, value) in &self.options {
if !first {
cmd.push_str(",");
}
cmd.push_str(key);
cmd.push_str("=");
cmd.push_str(value);
first = false;
}
}
// Add source and target
cmd.push_str(" ");
cmd.push_str(&self.source);
cmd.push_str(" ");
cmd.push_str(&self.target);
// Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command
let result = execute_rfs_command(&args)?;
// Parse the output to get the mount ID
let mount_id = result.stdout.trim().to_string();
if mount_id.is_empty() {
return Err(RfsError::MountFailed("Failed to get mount ID".to_string()));
}
// Create and return the Mount struct
Ok(Mount {
id: mount_id,
source: self.source,
target: self.target,
fs_type: self.mount_type.to_string(),
options: self.options.iter().map(|(k, v)| format!("{}={}", k, v)).collect(),
})
}
/// Unmount the filesystem
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount(&self) -> Result<(), RfsError> {
// Execute the unmount command
let result = execute_rfs_command(&["unmount", &self.target])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount {}: {}", self.target, result.stderr)));
}
Ok(())
}
}
/// Builder for RFS pack operations
#[derive(Clone)]
pub struct PackBuilder {
/// Directory to pack
directory: String,
/// Output file
output: String,
/// Store specifications
store_specs: Vec<StoreSpec>,
/// Debug mode
debug: bool,
}
impl PackBuilder {
/// Create a new pack builder
///
/// # Arguments
///
/// * `directory` - Directory to pack
/// * `output` - Output file
///
/// # Returns
///
/// * `Self` - New pack builder
pub fn new(directory: &str, output: &str) -> Self {
Self {
directory: directory.to_string(),
output: output.to_string(),
store_specs: Vec::new(),
debug: false,
}
}
/// Add a store specification
///
/// # Arguments
///
/// * `store_spec` - Store specification
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_store_spec(mut self, store_spec: StoreSpec) -> Self {
self.store_specs.push(store_spec);
self
}
/// Add multiple store specifications
///
/// # Arguments
///
/// * `store_specs` - Store specifications
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_store_specs(mut self, store_specs: Vec<StoreSpec>) -> Self {
self.store_specs.extend(store_specs);
self
}
/// Set debug mode
///
/// # Arguments
///
/// * `debug` - Whether to enable debug output
///
/// # Returns
///
/// * `Self` - Updated pack builder for method chaining
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
/// Pack the directory
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn pack(self) -> Result<(), RfsError> {
// Build the command string
let mut cmd = String::from("pack -m ");
cmd.push_str(&self.output);
// Add store specs if any
if !self.store_specs.is_empty() {
cmd.push_str(" -s ");
let mut first = true;
for spec in &self.store_specs {
if !first {
cmd.push_str(",");
}
let spec_str = spec.to_string();
cmd.push_str(&spec_str);
first = false;
}
}
// Add directory
cmd.push_str(" ");
cmd.push_str(&self.directory);
// Split the command into arguments
let args: Vec<&str> = cmd.split_whitespace().collect();
// Execute the command
let result = execute_rfs_command(&args)?;
// Check for errors
if !result.success {
return Err(RfsError::PackFailed(format!("Failed to pack {}: {}", self.directory, result.stderr)));
}
Ok(())
}
}

62
src/virt/rfs/cmd.rs Normal file
View File

@@ -0,0 +1,62 @@
use crate::process::{run_command, CommandResult};
use super::error::RfsError;
use std::thread_local;
use std::cell::RefCell;
// Thread-local storage for debug flag
thread_local! {
static DEBUG: RefCell<bool> = RefCell::new(false);
}
/// Set the thread-local debug flag
pub fn set_thread_local_debug(debug: bool) {
DEBUG.with(|d| {
*d.borrow_mut() = debug;
});
}
/// Get the current thread-local debug flag
pub fn thread_local_debug() -> bool {
DEBUG.with(|d| {
*d.borrow()
})
}
/// Execute an RFS command with the given arguments
///
/// # Arguments
///
/// * `args` - Command arguments
///
/// # Returns
///
/// * `Result<CommandResult, RfsError>` - Command result or error
pub fn execute_rfs_command(args: &[&str]) -> Result<CommandResult, RfsError> {
let debug = thread_local_debug();
// Construct the command string
let mut cmd = String::from("rfs");
for arg in args {
cmd.push(' ');
cmd.push_str(arg);
}
if debug {
println!("Executing RFS command: {}", cmd);
}
// Execute the command
let result = run_command(&cmd)
.map_err(|e| RfsError::CommandFailed(format!("Failed to execute RFS command: {}", e)))?;
if debug {
println!("RFS command result: {:?}", result);
}
// Check if the command was successful
if !result.success && !result.stderr.is_empty() {
return Err(RfsError::CommandFailed(result.stderr));
}
Ok(result)
}

43
src/virt/rfs/error.rs Normal file
View File

@@ -0,0 +1,43 @@
use std::fmt;
use std::error::Error;
/// Error types for RFS operations
#[derive(Debug)]
pub enum RfsError {
/// Command execution failed
CommandFailed(String),
/// Invalid argument provided
InvalidArgument(String),
/// Mount operation failed
MountFailed(String),
/// Unmount operation failed
UnmountFailed(String),
/// List operation failed
ListFailed(String),
/// Pack operation failed
PackFailed(String),
/// Other error
Other(String),
}
impl fmt::Display for RfsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RfsError::CommandFailed(msg) => write!(f, "RFS command failed: {}", msg),
RfsError::InvalidArgument(msg) => write!(f, "Invalid argument: {}", msg),
RfsError::MountFailed(msg) => write!(f, "Mount failed: {}", msg),
RfsError::UnmountFailed(msg) => write!(f, "Unmount failed: {}", msg),
RfsError::ListFailed(msg) => write!(f, "List failed: {}", msg),
RfsError::PackFailed(msg) => write!(f, "Pack failed: {}", msg),
RfsError::Other(msg) => write!(f, "Other error: {}", msg),
}
}
}
impl Error for RfsError {}
impl From<std::io::Error> for RfsError {
fn from(error: std::io::Error) -> Self {
RfsError::Other(format!("IO error: {}", error))
}
}

15
src/virt/rfs/mod.rs Normal file
View File

@@ -0,0 +1,15 @@
mod cmd;
mod error;
mod mount;
mod pack;
mod builder;
mod types;
pub use error::RfsError;
pub use builder::{RfsBuilder, PackBuilder};
pub use types::{Mount, MountType, StoreSpec};
pub use mount::{list_mounts, unmount_all, unmount, get_mount_info};
pub use pack::{pack_directory, unpack, list_contents, verify};
// Re-export the execute_rfs_command function for use in other modules
pub(crate) use cmd::execute_rfs_command;

142
src/virt/rfs/mount.rs Normal file
View File

@@ -0,0 +1,142 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::Mount,
};
/// List all mounted filesystems
///
/// # Returns
///
/// * `Result<Vec<Mount>, RfsError>` - List of mounts or error
pub fn list_mounts() -> Result<Vec<Mount>, RfsError> {
// Execute the list command
let result = execute_rfs_command(&["list", "--json"])?;
// Parse the JSON output
match serde_json::from_str::<serde_json::Value>(&result.stdout) {
Ok(json) => {
if let serde_json::Value::Array(mounts_json) = json {
let mut mounts = Vec::new();
for mount_json in mounts_json {
// Extract mount ID
let id = match mount_json.get("id").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return Err(RfsError::ListFailed("Missing mount ID".to_string())),
};
// Extract source
let source = match mount_json.get("source").and_then(|v| v.as_str()) {
Some(source) => source.to_string(),
None => return Err(RfsError::ListFailed("Missing source".to_string())),
};
// Extract target
let target = match mount_json.get("target").and_then(|v| v.as_str()) {
Some(target) => target.to_string(),
None => return Err(RfsError::ListFailed("Missing target".to_string())),
};
// Extract filesystem type
let fs_type = match mount_json.get("type").and_then(|v| v.as_str()) {
Some(fs_type) => fs_type.to_string(),
None => return Err(RfsError::ListFailed("Missing filesystem type".to_string())),
};
// Extract options
let options = match mount_json.get("options").and_then(|v| v.as_array()) {
Some(options_array) => {
let mut options_vec = Vec::new();
for option_value in options_array {
if let Some(option_str) = option_value.as_str() {
options_vec.push(option_str.to_string());
}
}
options_vec
},
None => Vec::new(), // Empty vector if no options found
};
// Create Mount struct and add to vector
mounts.push(Mount {
id,
source,
target,
fs_type,
options,
});
}
Ok(mounts)
} else {
Err(RfsError::ListFailed("Expected JSON array".to_string()))
}
},
Err(e) => {
Err(RfsError::ListFailed(format!("Failed to parse mount list JSON: {}", e)))
}
}
}
/// Unmount a filesystem by target path
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount(target: &str) -> Result<(), RfsError> {
// Execute the unmount command
let result = execute_rfs_command(&["unmount", target])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount {}: {}", target, result.stderr)));
}
Ok(())
}
/// Unmount all filesystems
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unmount_all() -> Result<(), RfsError> {
// Execute the unmount all command
let result = execute_rfs_command(&["unmount", "--all"])?;
// Check for errors
if !result.success {
return Err(RfsError::UnmountFailed(format!("Failed to unmount all filesystems: {}", result.stderr)));
}
Ok(())
}
/// Get information about a mounted filesystem
///
/// # Arguments
///
/// * `target` - Target mount point
///
/// # Returns
///
/// * `Result<Mount, RfsError>` - Mount information or error
pub fn get_mount_info(target: &str) -> Result<Mount, RfsError> {
// Get all mounts
let mounts = list_mounts()?;
// Find the mount with the specified target
for mount in mounts {
if mount.target == target {
return Ok(mount);
}
}
// Mount not found
Err(RfsError::Other(format!("No mount found at {}", target)))
}

100
src/virt/rfs/pack.rs Normal file
View File

@@ -0,0 +1,100 @@
use super::{
error::RfsError,
cmd::execute_rfs_command,
types::StoreSpec,
builder::PackBuilder,
};
/// Pack a directory into a filesystem layer
///
/// # Arguments
///
/// * `directory` - Directory to pack
/// * `output` - Output file
/// * `store_specs` - Store specifications
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn pack_directory(directory: &str, output: &str, store_specs: &[StoreSpec]) -> Result<(), RfsError> {
// Create a new pack builder
let mut builder = PackBuilder::new(directory, output);
// Add store specs
for spec in store_specs {
builder = builder.with_store_spec(spec.clone());
}
// Pack the directory
builder.pack()
}
/// Unpack a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
/// * `directory` - Directory to unpack to
///
/// # Returns
///
/// * `Result<(), RfsError>` - Success or error
pub fn unpack(input: &str, directory: &str) -> Result<(), RfsError> {
// Execute the unpack command
let result = execute_rfs_command(&["unpack", "-m", input, directory])?;
// Check for errors
if !result.success {
return Err(RfsError::Other(format!("Failed to unpack {}: {}", input, result.stderr)));
}
Ok(())
}
/// List the contents of a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<String, RfsError>` - File listing or error
pub fn list_contents(input: &str) -> Result<String, RfsError> {
// Execute the list command
let result = execute_rfs_command(&["list", "-m", input])?;
// Check for errors
if !result.success {
return Err(RfsError::Other(format!("Failed to list contents of {}: {}", input, result.stderr)));
}
Ok(result.stdout)
}
/// Verify a filesystem layer
///
/// # Arguments
///
/// * `input` - Input file
///
/// # Returns
///
/// * `Result<bool, RfsError>` - Whether the layer is valid or error
pub fn verify(input: &str) -> Result<bool, RfsError> {
// Execute the verify command
let result = execute_rfs_command(&["verify", "-m", input])?;
// Check for errors
if !result.success {
// If the command failed but returned a specific error about verification,
// return false instead of an error
if result.stderr.contains("verification failed") {
return Ok(false);
}
return Err(RfsError::Other(format!("Failed to verify {}: {}", input, result.stderr)));
}
Ok(true)
}

117
src/virt/rfs/types.rs Normal file
View File

@@ -0,0 +1,117 @@
use std::collections::HashMap;
/// Represents a mounted filesystem
#[derive(Debug, Clone)]
pub struct Mount {
/// Mount ID
pub id: String,
/// Source path or URL
pub source: String,
/// Target mount point
pub target: String,
/// Filesystem type
pub fs_type: String,
/// Mount options
pub options: Vec<String>,
}
/// Types of mounts supported by RFS
#[derive(Debug, Clone)]
pub enum MountType {
/// Local filesystem
Local,
/// SSH remote filesystem
SSH,
/// S3 object storage
S3,
/// WebDAV remote filesystem
WebDAV,
/// Custom mount type
Custom(String),
}
impl MountType {
/// Convert mount type to string representation
pub fn to_string(&self) -> String {
match self {
MountType::Local => "local".to_string(),
MountType::SSH => "ssh".to_string(),
MountType::S3 => "s3".to_string(),
MountType::WebDAV => "webdav".to_string(),
MountType::Custom(s) => s.clone(),
}
}
/// Create a MountType from a string
pub fn from_string(s: &str) -> Self {
match s.to_lowercase().as_str() {
"local" => MountType::Local,
"ssh" => MountType::SSH,
"s3" => MountType::S3,
"webdav" => MountType::WebDAV,
_ => MountType::Custom(s.to_string()),
}
}
}
/// Store specification for packing operations
#[derive(Debug, Clone)]
pub struct StoreSpec {
/// Store type (e.g., "file", "s3")
pub spec_type: String,
/// Store options
pub options: HashMap<String, String>,
}
impl StoreSpec {
/// Create a new store specification
///
/// # Arguments
///
/// * `spec_type` - Store type (e.g., "file", "s3")
///
/// # Returns
///
/// * `Self` - New store specification
pub fn new(spec_type: &str) -> Self {
Self {
spec_type: spec_type.to_string(),
options: HashMap::new(),
}
}
/// Add an option to the store specification
///
/// # Arguments
///
/// * `key` - Option key
/// * `value` - Option value
///
/// # Returns
///
/// * `Self` - Updated store specification for method chaining
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.options.insert(key.to_string(), value.to_string());
self
}
/// Convert the store specification to a string
///
/// # Returns
///
/// * `String` - String representation of the store specification
pub fn to_string(&self) -> String {
let mut result = self.spec_type.clone();
if !self.options.is_empty() {
result.push_str(":");
let options: Vec<String> = self.options
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
result.push_str(&options.join(","));
}
result
}
}