Skip to main content

sal_virt/rfs/
builder.rs

1use super::{
2    cmd::execute_rfs_command,
3    error::RfsError,
4    types::{Mount, MountType, StoreSpec},
5};
6use std::collections::HashMap;
7
8/// Builder for RFS mount operations
9#[derive(Clone)]
10pub struct RfsBuilder {
11    /// Source path or URL
12    source: String,
13    /// Target mount point
14    target: String,
15    /// Mount type
16    mount_type: MountType,
17    /// Mount options
18    options: HashMap<String, String>,
19    /// Mount ID
20    #[allow(dead_code)]
21    mount_id: Option<String>,
22    /// Debug mode
23    debug: bool,
24}
25
26impl RfsBuilder {
27    /// Create a new RFS builder
28    ///
29    /// # Arguments
30    ///
31    /// * `source` - Source path or URL
32    /// * `target` - Target mount point
33    /// * `mount_type` - Mount type
34    ///
35    /// # Returns
36    ///
37    /// * `Self` - New RFS builder
38    pub fn new(source: &str, target: &str, mount_type: MountType) -> Self {
39        Self {
40            source: source.to_string(),
41            target: target.to_string(),
42            mount_type,
43            options: HashMap::new(),
44            mount_id: None,
45            debug: false,
46        }
47    }
48
49    /// Add a mount option
50    ///
51    /// # Arguments
52    ///
53    /// * `key` - Option key
54    /// * `value` - Option value
55    ///
56    /// # Returns
57    ///
58    /// * `Self` - Updated RFS builder for method chaining
59    pub fn with_option(mut self, key: &str, value: &str) -> Self {
60        self.options.insert(key.to_string(), value.to_string());
61        self
62    }
63
64    /// Add multiple mount options
65    ///
66    /// # Arguments
67    ///
68    /// * `options` - Map of option keys to values
69    ///
70    /// # Returns
71    ///
72    /// * `Self` - Updated RFS builder for method chaining
73    pub fn with_options(mut self, options: HashMap<&str, &str>) -> Self {
74        for (key, value) in options {
75            self.options.insert(key.to_string(), value.to_string());
76        }
77        self
78    }
79
80    /// Set debug mode
81    ///
82    /// # Arguments
83    ///
84    /// * `debug` - Whether to enable debug output
85    ///
86    /// # Returns
87    ///
88    /// * `Self` - Updated RFS builder for method chaining
89    pub fn with_debug(mut self, debug: bool) -> Self {
90        self.debug = debug;
91        self
92    }
93
94    /// Get the source path
95    ///
96    /// # Returns
97    ///
98    /// * `&str` - Source path
99    pub fn source(&self) -> &str {
100        &self.source
101    }
102
103    /// Get the target path
104    ///
105    /// # Returns
106    ///
107    /// * `&str` - Target path
108    pub fn target(&self) -> &str {
109        &self.target
110    }
111
112    /// Get the mount type
113    ///
114    /// # Returns
115    ///
116    /// * `&MountType` - Mount type
117    pub fn mount_type(&self) -> &MountType {
118        &self.mount_type
119    }
120
121    /// Get the options
122    ///
123    /// # Returns
124    ///
125    /// * `&HashMap<String, String>` - Mount options
126    pub fn options(&self) -> &HashMap<String, String> {
127        &self.options
128    }
129
130    /// Get debug mode
131    ///
132    /// # Returns
133    ///
134    /// * `bool` - Whether debug mode is enabled
135    pub fn debug(&self) -> bool {
136        self.debug
137    }
138
139    /// Mount the filesystem
140    ///
141    /// # Returns
142    ///
143    /// * `Result<Mount, RfsError>` - Mount information or error
144    pub fn mount(self) -> Result<Mount, RfsError> {
145        // Build the command string
146        let mut cmd = String::from("mount -t ");
147        cmd.push_str(&self.mount_type.to_string());
148
149        // Add options if any
150        if !self.options.is_empty() {
151            cmd.push_str(" -o ");
152            let mut first = true;
153            for (key, value) in &self.options {
154                if !first {
155                    cmd.push_str(",");
156                }
157                cmd.push_str(key);
158                cmd.push_str("=");
159                cmd.push_str(value);
160                first = false;
161            }
162        }
163
164        // Add source and target
165        cmd.push_str(" ");
166        cmd.push_str(&self.source);
167        cmd.push_str(" ");
168        cmd.push_str(&self.target);
169
170        // Split the command into arguments
171        let args: Vec<&str> = cmd.split_whitespace().collect();
172
173        // Execute the command
174        let result = execute_rfs_command(&args)?;
175
176        // Parse the output to get the mount ID
177        let mount_id = result.stdout.trim().to_string();
178        if mount_id.is_empty() {
179            return Err(RfsError::MountFailed("Failed to get mount ID".to_string()));
180        }
181
182        // Create and return the Mount struct
183        Ok(Mount {
184            id: mount_id,
185            source: self.source,
186            target: self.target,
187            fs_type: self.mount_type.to_string(),
188            options: self
189                .options
190                .iter()
191                .map(|(k, v)| format!("{}={}", k, v))
192                .collect(),
193        })
194    }
195
196    /// Unmount the filesystem
197    ///
198    /// # Returns
199    ///
200    /// * `Result<(), RfsError>` - Success or error
201    pub fn unmount(&self) -> Result<(), RfsError> {
202        // Execute the unmount command
203        let result = execute_rfs_command(&["unmount", &self.target])?;
204
205        // Check for errors
206        if !result.success {
207            return Err(RfsError::UnmountFailed(format!(
208                "Failed to unmount {}: {}",
209                self.target, result.stderr
210            )));
211        }
212
213        Ok(())
214    }
215}
216
217/// Builder for RFS pack operations
218#[derive(Clone)]
219pub struct PackBuilder {
220    /// Directory to pack
221    directory: String,
222    /// Output file
223    output: String,
224    /// Store specifications
225    store_specs: Vec<StoreSpec>,
226    /// Debug mode
227    debug: bool,
228}
229
230impl PackBuilder {
231    /// Create a new pack builder
232    ///
233    /// # Arguments
234    ///
235    /// * `directory` - Directory to pack
236    /// * `output` - Output file
237    ///
238    /// # Returns
239    ///
240    /// * `Self` - New pack builder
241    pub fn new(directory: &str, output: &str) -> Self {
242        Self {
243            directory: directory.to_string(),
244            output: output.to_string(),
245            store_specs: Vec::new(),
246            debug: false,
247        }
248    }
249
250    /// Add a store specification
251    ///
252    /// # Arguments
253    ///
254    /// * `store_spec` - Store specification
255    ///
256    /// # Returns
257    ///
258    /// * `Self` - Updated pack builder for method chaining
259    pub fn with_store_spec(mut self, store_spec: StoreSpec) -> Self {
260        self.store_specs.push(store_spec);
261        self
262    }
263
264    /// Add multiple store specifications
265    ///
266    /// # Arguments
267    ///
268    /// * `store_specs` - Store specifications
269    ///
270    /// # Returns
271    ///
272    /// * `Self` - Updated pack builder for method chaining
273    pub fn with_store_specs(mut self, store_specs: Vec<StoreSpec>) -> Self {
274        self.store_specs.extend(store_specs);
275        self
276    }
277
278    /// Set debug mode
279    ///
280    /// # Arguments
281    ///
282    /// * `debug` - Whether to enable debug output
283    ///
284    /// # Returns
285    ///
286    /// * `Self` - Updated pack builder for method chaining
287    pub fn with_debug(mut self, debug: bool) -> Self {
288        self.debug = debug;
289        self
290    }
291
292    /// Get the directory path
293    ///
294    /// # Returns
295    ///
296    /// * `&str` - Directory path
297    pub fn directory(&self) -> &str {
298        &self.directory
299    }
300
301    /// Get the output path
302    ///
303    /// # Returns
304    ///
305    /// * `&str` - Output path
306    pub fn output(&self) -> &str {
307        &self.output
308    }
309
310    /// Get the store specifications
311    ///
312    /// # Returns
313    ///
314    /// * `&Vec<StoreSpec>` - Store specifications
315    pub fn store_specs(&self) -> &Vec<StoreSpec> {
316        &self.store_specs
317    }
318
319    /// Get debug mode
320    ///
321    /// # Returns
322    ///
323    /// * `bool` - Whether debug mode is enabled
324    pub fn debug(&self) -> bool {
325        self.debug
326    }
327
328    /// Pack the directory
329    ///
330    /// # Returns
331    ///
332    /// * `Result<(), RfsError>` - Success or error
333    pub fn pack(self) -> Result<(), RfsError> {
334        // Build the command string
335        let mut cmd = String::from("pack -m ");
336        cmd.push_str(&self.output);
337
338        // Add store specs if any
339        if !self.store_specs.is_empty() {
340            cmd.push_str(" -s ");
341            let mut first = true;
342            for spec in &self.store_specs {
343                if !first {
344                    cmd.push_str(",");
345                }
346                let spec_str = spec.to_string();
347                cmd.push_str(&spec_str);
348                first = false;
349            }
350        }
351
352        // Add directory
353        cmd.push_str(" ");
354        cmd.push_str(&self.directory);
355
356        // Split the command into arguments
357        let args: Vec<&str> = cmd.split_whitespace().collect();
358
359        // Execute the command
360        let result = execute_rfs_command(&args)?;
361
362        // Check for errors
363        if !result.success {
364            return Err(RfsError::PackFailed(format!(
365                "Failed to pack {}: {}",
366                self.directory, result.stderr
367            )));
368        }
369
370        Ok(())
371    }
372}