Skip to main content

secure_exec_vfs_core/posix/
mount_plugin.rs

1use super::mount_table::MountedFileSystem;
2use super::vfs::VfsError;
3use serde_json::Value;
4use std::collections::BTreeMap;
5use std::error::Error;
6use std::fmt;
7
8#[derive(Debug)]
9pub struct OpenFileSystemPluginRequest<'a, Context> {
10    pub vm_id: &'a str,
11    pub guest_path: &'a str,
12    pub read_only: bool,
13    pub config: &'a Value,
14    pub context: &'a Context,
15}
16
17pub trait FileSystemPluginFactory<Context>: Send + Sync {
18    fn plugin_id(&self) -> &'static str;
19    fn open(
20        &self,
21        request: OpenFileSystemPluginRequest<'_, Context>,
22    ) -> Result<Box<dyn MountedFileSystem>, PluginError>;
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct PluginError {
27    code: &'static str,
28    message: String,
29}
30
31impl PluginError {
32    pub fn code(&self) -> &'static str {
33        self.code
34    }
35
36    pub fn message(&self) -> &str {
37        &self.message
38    }
39
40    pub fn new(code: &'static str, message: impl Into<String>) -> Self {
41        Self {
42            code,
43            message: message.into(),
44        }
45    }
46
47    pub fn unsupported(message: impl Into<String>) -> Self {
48        Self::new("ENOSYS", message)
49    }
50
51    pub fn invalid_input(message: impl Into<String>) -> Self {
52        Self::new("EINVAL", message)
53    }
54
55    pub fn already_exists(message: impl Into<String>) -> Self {
56        Self::new("EEXIST", message)
57    }
58}
59
60impl fmt::Display for PluginError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "{}: {}", self.code, self.message)
63    }
64}
65
66impl Error for PluginError {}
67
68impl From<VfsError> for PluginError {
69    fn from(error: VfsError) -> Self {
70        Self::new(error.code(), error.message().to_owned())
71    }
72}
73
74pub struct FileSystemPluginRegistry<Context> {
75    factories: BTreeMap<String, Box<dyn FileSystemPluginFactory<Context>>>,
76}
77
78impl<Context> Default for FileSystemPluginRegistry<Context> {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl<Context> FileSystemPluginRegistry<Context> {
85    pub fn new() -> Self {
86        Self {
87            factories: BTreeMap::new(),
88        }
89    }
90
91    pub fn register(
92        &mut self,
93        factory: impl FileSystemPluginFactory<Context> + 'static,
94    ) -> Result<(), PluginError> {
95        let plugin_id = factory.plugin_id();
96        validate_plugin_id(plugin_id)?;
97        if self.factories.contains_key(plugin_id) {
98            return Err(PluginError::already_exists(format!(
99                "filesystem plugin already registered: {plugin_id}"
100            )));
101        }
102
103        self.factories
104            .insert(plugin_id.to_owned(), Box::new(factory));
105        Ok(())
106    }
107
108    pub fn open(
109        &self,
110        plugin_id: &str,
111        request: OpenFileSystemPluginRequest<'_, Context>,
112    ) -> Result<Box<dyn MountedFileSystem>, PluginError> {
113        let Some(factory) = self.factories.get(plugin_id) else {
114            return Err(PluginError::unsupported(format!(
115                "filesystem plugin is not registered: {plugin_id}"
116            )));
117        };
118
119        factory.open(request)
120    }
121
122    pub fn plugin_ids(&self) -> Vec<String> {
123        self.factories.keys().cloned().collect()
124    }
125}
126
127fn validate_plugin_id(plugin_id: &str) -> Result<(), PluginError> {
128    if plugin_id.is_empty()
129        || !plugin_id
130            .bytes()
131            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
132    {
133        return Err(PluginError::invalid_input(format!(
134            "invalid filesystem plugin id {plugin_id:?}"
135        )));
136    }
137
138    Ok(())
139}