use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use super::{
VirtiofsdConfig, VirtiofsdDaemon, get_virtiofsd_version, is_virtiofsd_available, start_daemon,
};
#[derive(Clone)]
pub struct RhaiVirtiofsdConfig {
inner: VirtiofsdConfig,
}
impl RhaiVirtiofsdConfig {
pub fn new() -> Self {
Self {
inner: VirtiofsdConfig::default(),
}
}
pub fn from_config(config: VirtiofsdConfig) -> Self {
Self { inner: config }
}
pub fn socket_path(mut self, path: String) -> Self {
self.inner.socket_path = PathBuf::from(path);
self
}
pub fn shared_dir(mut self, dir: String) -> Self {
self.inner.shared_dir = PathBuf::from(dir);
self
}
pub fn shared_dir_with_mount(mut self, host_dir: String, guest_dir: String) -> Self {
self.inner.shared_dir = PathBuf::from(host_dir);
self.inner.guest_mount = Some(PathBuf::from(guest_dir));
self
}
pub fn guest_mount(mut self, mount: String) -> Self {
self.inner.guest_mount = Some(PathBuf::from(mount));
self
}
pub fn readonly(mut self, readonly: bool) -> Self {
self.inner.readonly = readonly;
self
}
pub fn sandbox(mut self, sandbox: bool) -> Self {
self.inner.sandbox = sandbox;
self
}
pub fn thread_pool_size(mut self, size: i64) -> Self {
self.inner.thread_pool_size = Some(size as usize);
self
}
pub fn cache(mut self, cache: bool) -> Self {
self.inner.cache = cache;
self
}
pub fn extra_args(mut self, args: Array) -> Self {
self.inner.extra_args = args.into_iter().map(|v| v.to_string()).collect();
self
}
pub fn build(self) -> Result<RhaiVirtiofsdConfig, Box<EvalAltResult>> {
match self.inner.validate() {
Ok(()) => Ok(RhaiVirtiofsdConfig { inner: self.inner }),
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
e.to_string().into(),
rhai::Position::NONE,
))),
}
}
pub fn socket_path_get(&self) -> String {
self.inner.socket_path.to_string_lossy().to_string()
}
pub fn shared_dir_get(&self) -> String {
self.inner.shared_dir.to_string_lossy().to_string()
}
pub fn guest_mount_get(&self) -> String {
self.inner
.guest_mount
.as_ref()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default()
}
pub fn readonly_get(&self) -> bool {
self.inner.readonly
}
pub fn sandbox_get(&self) -> bool {
self.inner.sandbox
}
pub fn cache_get(&self) -> bool {
self.inner.cache
}
}
#[derive(Clone)]
pub struct RhaiVirtiofsdDaemon {
inner: Arc<Mutex<Option<VirtiofsdDaemon>>>,
pid: u32,
socket_path: String,
shared_dir: String,
}
impl RhaiVirtiofsdDaemon {
pub fn from_daemon(daemon: VirtiofsdDaemon) -> Self {
let pid = daemon.pid();
let socket_path = daemon.socket_path().to_string_lossy().to_string();
let shared_dir = daemon.shared_dir().to_string_lossy().to_string();
Self {
inner: Arc::new(Mutex::new(Some(daemon))),
pid,
socket_path,
shared_dir,
}
}
pub fn pid(&self) -> i64 {
self.pid as i64
}
pub fn socket_path(&self) -> String {
self.socket_path.clone()
}
pub fn shared_dir(&self) -> String {
self.shared_dir.clone()
}
pub fn is_running(&self) -> bool {
if let Ok(mut inner) = self.inner.lock() {
if let Some(ref mut daemon) = *inner {
daemon.is_running()
} else {
false
}
} else {
false
}
}
pub async fn wait_for_socket(&self) -> Result<(), String> {
if let Ok(inner) = self.inner.lock() {
if let Some(ref daemon) = *inner {
daemon.wait_for_socket().await.map_err(|e| e.to_string())
} else {
Err("Daemon not available".to_string())
}
} else {
Err("Failed to access daemon".to_string())
}
}
pub fn stop(&self) -> Result<(), String> {
if let Ok(mut inner) = self.inner.lock() {
if let Some(daemon) = inner.take() {
daemon.stop().map_err(|e| e.to_string())
} else {
Err("Daemon already stopped".to_string())
}
} else {
Err("Failed to access daemon".to_string())
}
}
pub fn to_map(&self) -> Map {
let mut map = Map::new();
map.insert("pid".into(), Dynamic::from(self.pid()));
map.insert("socket_path".into(), Dynamic::from(self.socket_path()));
map.insert("shared_dir".into(), Dynamic::from(self.shared_dir()));
map.insert("is_running".into(), Dynamic::from(self.is_running()));
map
}
}
pub fn virtiofsd_config() -> RhaiVirtiofsdConfig {
RhaiVirtiofsdConfig::new()
}
pub fn start_virtiofsd_daemon(
config: RhaiVirtiofsdConfig,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
start_daemon(config.inner)
.map(RhaiVirtiofsdDaemon::from_daemon)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
e.to_string().into(),
rhai::Position::NONE,
))
})
}
pub async fn start_virtiofsd_daemon_sync(
config: RhaiVirtiofsdConfig,
) -> Result<RhaiVirtiofsdDaemon, String> {
start_daemon(config.inner)
.map(RhaiVirtiofsdDaemon::from_daemon)
.map_err(|e| e.to_string())
}
pub fn virtiofsd_available() -> bool {
is_virtiofsd_available()
}
pub fn virtiofsd_version() -> Result<String, String> {
get_virtiofsd_version().map_err(|e| e.to_string())
}
pub fn share_directory(
host_dir: String,
socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
let config = RhaiVirtiofsdConfig::new()
.shared_dir(host_dir)
.socket_path(socket_path)
.build()?;
start_virtiofsd_daemon(config)
}
pub fn share_directory_with_mount(
host_dir: String,
guest_dir: String,
socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
let config = RhaiVirtiofsdConfig::new()
.shared_dir_with_mount(host_dir, guest_dir)
.socket_path(socket_path)
.build()?;
start_virtiofsd_daemon(config)
}
pub fn share_directory_readonly(
host_dir: String,
socket_path: String,
) -> Result<RhaiVirtiofsdDaemon, Box<EvalAltResult>> {
let config = RhaiVirtiofsdConfig::new()
.shared_dir(host_dir)
.socket_path(socket_path)
.readonly(true)
.build()?;
start_virtiofsd_daemon(config)
}
pub fn create_temp_share(
socket_path: String,
) -> Result<(String, RhaiVirtiofsdDaemon), Box<EvalAltResult>> {
let temp_dir = std::env::temp_dir();
let shared_dir = temp_dir.join(format!("virtiofsd_share_{}", std::process::id()));
std::fs::create_dir_all(&shared_dir).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to create temp directory: {}", e).into(),
rhai::Position::NONE,
))
})?;
let daemon = share_directory(shared_dir.to_string_lossy().to_string(), socket_path)?;
Ok((shared_dir.to_string_lossy().to_string(), daemon))
}
pub fn register_virtiofsd_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
engine.register_type::<RhaiVirtiofsdConfig>();
engine.register_fn("new_virtiofsd_config", RhaiVirtiofsdConfig::new);
engine.register_fn("socket_path", RhaiVirtiofsdConfig::socket_path);
engine.register_fn("shared_dir", RhaiVirtiofsdConfig::shared_dir);
engine.register_fn(
"shared_dir_with_mount",
RhaiVirtiofsdConfig::shared_dir_with_mount,
);
engine.register_fn("guest_mount", RhaiVirtiofsdConfig::guest_mount);
engine.register_fn("readonly", RhaiVirtiofsdConfig::readonly);
engine.register_fn("sandbox", RhaiVirtiofsdConfig::sandbox);
engine.register_fn("thread_pool_size", RhaiVirtiofsdConfig::thread_pool_size);
engine.register_fn("cache", RhaiVirtiofsdConfig::cache);
engine.register_fn("extra_args", RhaiVirtiofsdConfig::extra_args);
engine.register_fn("build", RhaiVirtiofsdConfig::build);
engine.register_get("socket_path_str", |cfg: &mut RhaiVirtiofsdConfig| {
cfg.socket_path_get()
});
engine.register_get("shared_dir_str", |cfg: &mut RhaiVirtiofsdConfig| {
cfg.shared_dir_get()
});
engine.register_get("guest_mount_str", |cfg: &mut RhaiVirtiofsdConfig| {
cfg.guest_mount_get()
});
engine.register_get("readonly_val", |cfg: &mut RhaiVirtiofsdConfig| {
cfg.readonly_get()
});
engine.register_get("sandbox_val", |cfg: &mut RhaiVirtiofsdConfig| {
cfg.sandbox_get()
});
engine.register_get("cache_val", |cfg: &mut RhaiVirtiofsdConfig| cfg.cache_get());
engine.register_type::<RhaiVirtiofsdDaemon>();
engine.register_fn("pid", |d: &mut RhaiVirtiofsdDaemon| d.pid());
engine.register_fn("socket_path", |d: &mut RhaiVirtiofsdDaemon| d.socket_path());
engine.register_fn("shared_dir", |d: &mut RhaiVirtiofsdDaemon| d.shared_dir());
engine.register_fn("is_running", |d: &mut RhaiVirtiofsdDaemon| d.is_running());
engine.register_fn("stop", |d: &mut RhaiVirtiofsdDaemon| d.stop());
engine.register_fn("to_map", |d: &mut RhaiVirtiofsdDaemon| d.to_map());
engine.register_fn("virtiofsd_config", virtiofsd_config);
engine.register_fn("start_virtiofsd_daemon", start_virtiofsd_daemon);
engine.register_fn("virtiofsd_available", virtiofsd_available);
engine.register_fn("virtiofsd_version", virtiofsd_version);
engine.register_fn("share_directory", share_directory);
engine.register_fn("share_directory_with_mount", share_directory_with_mount);
engine.register_fn("share_directory_readonly", share_directory_readonly);
engine.register_fn("create_temp_share", create_temp_share);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rhai::Engine;
#[test]
fn test_rhai_config() {
let temp_dir = std::env::temp_dir().join("virtiofs_rhai_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let mut engine = Engine::new();
register_virtiofsd_module(&mut engine).unwrap();
let script = format!(
r#"
let config = virtiofsd_config()
.socket_path("/tmp/test.sock")
.shared_dir("{}")
.readonly(true)
.build();
config
"#,
temp_dir.display()
);
let config = engine.eval::<RhaiVirtiofsdConfig>(&script);
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.socket_path_get(), "/tmp/test.sock");
assert_eq!(
config.shared_dir_get(),
temp_dir.to_string_lossy().to_string()
);
assert!(config.readonly_get());
std::fs::remove_dir(&temp_dir).ok();
}
#[test]
fn test_rhai_engine_registration() {
let mut engine = Engine::new();
assert!(register_virtiofsd_module(&mut engine).is_ok());
let result = engine.eval::<bool>("virtiofsd_available()");
assert!(result.is_ok() || result.is_err()); }
#[tokio::test]
async fn test_rhai_basic_usage() {
let script = r#"
let config = virtiofsd_config()
.socket_path("/tmp/test.sock")
.shared_dir("/tmp")
.readonly(true);
print("Config created successfully");
print("Socket path: " + config.socket_path_str);
print("Shared dir: " + config.shared_dir_str);
if config.readonly_val {
print("Readonly: true");
} else {
print("Readonly: false");
}
"#;
let mut engine = Engine::new();
register_virtiofsd_module(&mut engine).unwrap();
let result = engine.eval::<()>(script);
assert!(result.is_ok());
}
}