secure_exec_kernel/
mount_plugin.rs1use crate::mount_table::MountedFileSystem;
2use crate::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}