#[cfg(target_os = "linux")]
pub mod rhai;
#[cfg(target_os = "linux")]
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
use std::process::{Child, Command};
#[cfg(target_os = "linux")]
use std::time::Duration;
#[cfg(target_os = "linux")]
use thiserror::Error;
#[cfg(target_os = "linux")]
use tokio::time::sleep;
#[cfg(target_os = "linux")]
#[derive(Error, Debug)]
pub enum VirtiofsdError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("virtiofsd binary not found")]
BinaryNotFound,
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Daemon failed to start: {0}")]
DaemonStartFailed(String),
#[error("Daemon not running")]
DaemonNotRunning,
#[error("Socket path does not exist: {0}")]
SocketNotFound(PathBuf),
#[error("Shared directory does not exist: {0}")]
SharedDirectoryNotFound(PathBuf),
#[error("Permission denied for operation: {0}")]
PermissionDenied(String),
}
#[cfg(target_os = "linux")]
#[derive(Debug, Clone)]
pub struct VirtiofsdConfig {
pub socket_path: PathBuf,
pub shared_dir: PathBuf,
pub guest_mount: Option<PathBuf>,
pub readonly: bool,
pub sandbox: bool,
pub thread_pool_size: Option<usize>,
pub cache: bool,
pub extra_args: Vec<String>,
pub startup_timeout: Duration,
}
#[cfg(target_os = "linux")]
impl Default for VirtiofsdConfig {
fn default() -> Self {
Self {
socket_path: PathBuf::from("/tmp/virtiofsd.sock"),
shared_dir: PathBuf::from("/tmp/shared"),
guest_mount: None,
readonly: false,
sandbox: true,
thread_pool_size: None,
cache: true,
extra_args: Vec::new(),
startup_timeout: Duration::from_secs(10),
}
}
}
#[cfg(not(target_os = "linux"))]
use thiserror::Error;
#[cfg(not(target_os = "linux"))]
use std::path::{Path, PathBuf};
#[cfg(not(target_os = "linux"))]
#[derive(Error, Debug)]
pub enum VirtiofsdError {
#[error("virtiofsd is only supported on Linux")]
NotSupported,
}
#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone, Default)]
pub struct VirtiofsdConfig {
pub socket_path: PathBuf,
pub shared_dir: PathBuf,
pub guest_mount: Option<PathBuf>,
}
#[cfg(not(target_os = "linux"))]
pub struct VirtiofsdConfigBuilder {
config: VirtiofsdConfig,
}
#[cfg(not(target_os = "linux"))]
impl VirtiofsdConfigBuilder {
pub fn new() -> Self { Self { config: VirtiofsdConfig::default() } }
pub fn socket_path<P: AsRef<Path>>(mut self, path: P) -> Self { self.config.socket_path = path.as_ref().to_path_buf(); self }
pub fn shared_dir<P: AsRef<Path>>(mut self, dir: P) -> Self { self.config.shared_dir = dir.as_ref().to_path_buf(); self }
pub fn shared_dir_with_mount<HP: AsRef<Path>, GP: AsRef<Path>>(mut self, host_dir: HP, guest_dir: GP) -> Self {
self.config.shared_dir = host_dir.as_ref().to_path_buf();
self.config.guest_mount = Some(guest_dir.as_ref().to_path_buf());
self
}
pub fn guest_mount<P: AsRef<Path>>(mut self, mount: P) -> Self { self.config.guest_mount = Some(mount.as_ref().to_path_buf()); self }
pub fn readonly(self, _readonly: bool) -> Self { self }
pub fn sandbox(self, _sandbox: bool) -> Self { self }
pub fn thread_pool_size(self, _size: usize) -> Self { self }
pub fn cache(self, _cache: bool) -> Self { self }
pub fn extra_args(self, _args: Vec<String>) -> Self { self }
pub fn startup_timeout(self, _timeout: std::time::Duration) -> Self { self }
pub fn build(self) -> Result<VirtiofsdConfig, VirtiofsdError> { Ok(self.config) }
}
#[cfg(not(target_os = "linux"))]
impl VirtiofsdConfig {
pub fn builder() -> VirtiofsdConfigBuilder { VirtiofsdConfigBuilder::new() }
}
#[cfg(not(target_os = "linux"))]
pub struct VirtiofsdDaemon {}
#[cfg(not(target_os = "linux"))]
impl VirtiofsdDaemon {
pub fn pid(&self) -> u32 { 0 }
pub fn socket_path(&self) -> &Path { Path::new("") }
pub fn shared_dir(&self) -> &Path { Path::new("") }
pub fn is_running(&mut self) -> bool { false }
pub fn stop(self) -> Result<(), VirtiofsdError> { Ok(()) }
}
#[cfg(not(target_os = "linux"))]
pub fn is_virtiofsd_available() -> bool { false }
#[cfg(not(target_os = "linux"))]
pub fn get_virtiofsd_version() -> Result<String, VirtiofsdError> {
Err(VirtiofsdError::NotSupported)
}
#[cfg(not(target_os = "linux"))]
pub fn start_daemon(_config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
Err(VirtiofsdError::NotSupported)
}
#[cfg(not(target_os = "linux"))]
pub async fn start_daemon_async(_config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
Err(VirtiofsdError::NotSupported)
}
#[cfg(target_os = "linux")]
impl VirtiofsdConfig {
pub fn builder() -> VirtiofsdConfigBuilder {
VirtiofsdConfigBuilder::new()
}
pub fn validate(&self) -> Result<(), VirtiofsdError> {
if !self.shared_dir.exists() {
return Err(VirtiofsdError::SharedDirectoryNotFound(
self.shared_dir.clone(),
));
}
if let Some(parent) = self.socket_path.parent() {
if !parent.exists() {
return Err(VirtiofsdError::InvalidConfiguration(format!(
"Socket parent directory does not exist: {}",
parent.display()
)));
}
}
Ok(())
}
pub fn build_args(&self) -> Vec<String> {
let mut args = Vec::new();
args.push("--socket-path".to_string());
args.push(self.socket_path.to_string_lossy().to_string());
args.push("--shared-dir".to_string());
args.push(self.shared_dir.to_string_lossy().to_string());
if self.readonly {
args.push("--readonly".to_string());
}
if !self.sandbox {
args.push("--sandbox".to_string());
args.push("none".to_string());
}
if let Some(size) = self.thread_pool_size {
args.push("--thread-pool-size".to_string());
args.push(size.to_string());
}
if !self.cache {
args.push("--no-cache".to_string());
}
args.extend(self.extra_args.clone());
args
}
}
#[cfg(target_os = "linux")]
#[derive(Debug, Clone)]
pub struct VirtiofsdConfigBuilder {
config: VirtiofsdConfig,
}
#[cfg(target_os = "linux")]
impl VirtiofsdConfigBuilder {
pub fn new() -> Self {
Self {
config: VirtiofsdConfig::default(),
}
}
pub fn socket_path<P: AsRef<Path>>(mut self, path: P) -> Self {
self.config.socket_path = path.as_ref().to_path_buf();
self
}
pub fn shared_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.config.shared_dir = dir.as_ref().to_path_buf();
self
}
pub fn shared_dir_with_mount<HP: AsRef<Path>, GP: AsRef<Path>>(
mut self,
host_dir: HP,
guest_dir: GP,
) -> Self {
self.config.shared_dir = host_dir.as_ref().to_path_buf();
self.config.guest_mount = Some(guest_dir.as_ref().to_path_buf());
self
}
pub fn guest_mount<P: AsRef<Path>>(mut self, mount: P) -> Self {
self.config.guest_mount = Some(mount.as_ref().to_path_buf());
self
}
pub fn readonly(mut self, readonly: bool) -> Self {
self.config.readonly = readonly;
self
}
pub fn sandbox(mut self, sandbox: bool) -> Self {
self.config.sandbox = sandbox;
self
}
pub fn thread_pool_size(mut self, size: usize) -> Self {
self.config.thread_pool_size = Some(size);
self
}
pub fn cache(mut self, cache: bool) -> Self {
self.config.cache = cache;
self
}
pub fn extra_args(mut self, args: Vec<String>) -> Self {
self.config.extra_args = args;
self
}
pub fn startup_timeout(mut self, timeout: Duration) -> Self {
self.config.startup_timeout = timeout;
self
}
pub fn build(self) -> Result<VirtiofsdConfig, VirtiofsdError> {
self.config.validate()?;
Ok(self.config)
}
}
#[cfg(target_os = "linux")]
#[derive(Debug)]
pub struct VirtiofsdDaemon {
child: Child,
pid: u32,
config: VirtiofsdConfig,
}
#[cfg(target_os = "linux")]
impl VirtiofsdDaemon {
pub fn pid(&self) -> u32 {
self.pid
}
pub fn socket_path(&self) -> &Path {
&self.config.socket_path
}
pub fn shared_dir(&self) -> &Path {
&self.config.shared_dir
}
pub fn is_running(&mut self) -> bool {
match self.child.try_wait() {
Ok(Some(_)) => false, Ok(None) => true, Err(_) => false, }
}
pub async fn wait_for_socket(&self) -> Result<(), VirtiofsdError> {
let timeout = self.config.startup_timeout;
let start = std::time::Instant::now();
while start.elapsed() < timeout {
if self.config.socket_path.exists() {
return Ok(());
}
sleep(Duration::from_millis(100)).await;
}
Err(VirtiofsdError::SocketNotFound(
self.config.socket_path.clone(),
))
}
pub fn stop(mut self) -> Result<(), VirtiofsdError> {
match self.child.kill() {
Ok(_) => {
match self.child.wait() {
Ok(_) => Ok(()),
Err(e) => Err(VirtiofsdError::Io(e)),
}
}
Err(e) => Err(VirtiofsdError::Io(e)),
}
}
pub fn config(&self) -> &VirtiofsdConfig {
&self.config
}
}
#[cfg(target_os = "linux")]
pub fn start_daemon(config: VirtiofsdConfig) -> Result<VirtiofsdDaemon, VirtiofsdError> {
let virtiofsd_path = which::which("virtiofsd").map_err(|_| VirtiofsdError::BinaryNotFound)?;
let args = config.build_args();
let mut cmd = Command::new(virtiofsd_path);
cmd.args(&args);
let mut child = cmd
.spawn()
.map_err(|e| VirtiofsdError::DaemonStartFailed(e.to_string()))?;
let pid = child.id();
Ok(VirtiofsdDaemon { child, pid, config })
}
#[cfg(target_os = "linux")]
pub async fn start_daemon_async(
config: VirtiofsdConfig,
) -> Result<VirtiofsdDaemon, VirtiofsdError> {
let daemon = start_daemon(config)?;
daemon.wait_for_socket().await?;
Ok(daemon)
}
#[cfg(target_os = "linux")]
pub fn is_virtiofsd_available() -> bool {
which::which("virtiofsd").is_ok()
}
#[cfg(target_os = "linux")]
pub fn get_virtiofsd_version() -> Result<String, VirtiofsdError> {
let virtiofsd_path = which::which("virtiofsd").map_err(|_| VirtiofsdError::BinaryNotFound)?;
let output = Command::new(virtiofsd_path)
.arg("--version")
.output()
.map_err(|e| VirtiofsdError::Io(e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(VirtiofsdError::DaemonStartFailed(
String::from_utf8_lossy(&output.stderr).to_string(),
))
}
}
#[cfg(all(test, target_os = "linux"))]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_config_builder() {
let temp_dir = TempDir::new().unwrap();
let shared_dir = temp_dir.path().join("shared");
std::fs::create_dir(&shared_dir).unwrap();
let config = VirtiofsdConfig::builder()
.socket_path("/tmp/test.sock")
.shared_dir(&shared_dir)
.readonly(true)
.sandbox(false)
.build()
.unwrap();
assert_eq!(config.socket_path, PathBuf::from("/tmp/test.sock"));
assert_eq!(config.shared_dir, shared_dir);
assert!(config.readonly);
assert!(!config.sandbox);
}
#[test]
fn test_config_validation() {
let config = VirtiofsdConfig::builder()
.shared_dir("/nonexistent/directory")
.build();
assert!(config.is_err());
assert!(matches!(
config.unwrap_err(),
VirtiofsdError::SharedDirectoryNotFound(_)
));
}
#[test]
fn test_build_args() {
let temp_dir = std::env::temp_dir().join("virtiofs_test");
std::fs::create_dir_all(&temp_dir).unwrap();
let config = VirtiofsdConfig::builder()
.socket_path("/tmp/test.sock")
.shared_dir(&temp_dir)
.readonly(true)
.extra_args(vec!["--custom-arg".to_string(), "value".to_string()])
.build()
.unwrap();
let args = config.build_args();
assert!(args.contains(&"--socket-path".to_string()));
assert!(args.contains(&"/tmp/test.sock".to_string()));
assert!(args.contains(&"--shared-dir".to_string()));
assert!(args.contains(&temp_dir.to_string_lossy().to_string()));
assert!(args.contains(&"--readonly".to_string()));
assert!(args.contains(&"--custom-arg".to_string()));
std::fs::remove_dir(&temp_dir).ok();
}
}