embeddenator_cli/commands/
mount.rs

1//! FUSE mount command implementation
2
3#[cfg(feature = "fuse")]
4use anyhow::Result;
5#[cfg(feature = "fuse")]
6use embeddenator_fs::embrfs::{EmbrFS, DEFAULT_CHUNK_SIZE};
7#[cfg(feature = "fuse")]
8use embeddenator_fs::fuse_shim::{EngramFS, MountOptions, mount};
9#[cfg(feature = "fuse")]
10use embeddenator_vsa::ReversibleVSAConfig;
11#[cfg(feature = "fuse")]
12use std::path::PathBuf;
13
14#[cfg(feature = "fuse")]
15pub fn handle_mount(
16    engram: PathBuf,
17    manifest: PathBuf,
18    mountpoint: PathBuf,
19    allow_other: bool,
20    _foreground: bool,
21    verbose: bool,
22) -> Result<()> {
23    if verbose {
24        println!(
25            "Embeddenator v{} - FUSE Mount",
26            env!("CARGO_PKG_VERSION")
27        );
28        println!("============================");
29    }
30
31    // Load engram and manifest
32    let engram_data = EmbrFS::load_engram(&engram)?;
33    let manifest_data = EmbrFS::load_manifest(&manifest)?;
34    let config = ReversibleVSAConfig::default();
35
36    if verbose {
37        println!("Loaded engram: {}", engram.display());
38        println!("Loaded manifest: {} files", manifest_data.files.len());
39    }
40
41    // Create FUSE filesystem and populate with decoded files
42    let fuse_fs = EngramFS::new(true);
43
44    for file_entry in &manifest_data.files {
45        // Decode file data using the same approach as EmbrFS::extract
46        let mut reconstructed = Vec::new();
47
48        for &chunk_id in &file_entry.chunks {
49            if let Some(chunk_vec) = engram_data.codebook.get(&chunk_id) {
50                // Decode the sparse vector to bytes
51                // IMPORTANT: Use the same path as during encoding for correct shift calculation
52                let decoded = chunk_vec.decode_data(
53                    &config,
54                    Some(&file_entry.path),
55                    DEFAULT_CHUNK_SIZE,
56                );
57
58                // Apply correction to guarantee bit-perfect reconstruction
59                let chunk_data = if let Some(corrected) =
60                    engram_data.corrections.apply(chunk_id as u64, &decoded)
61                {
62                    corrected
63                } else {
64                    // No correction found - use decoded directly
65                    decoded
66                };
67
68                reconstructed.extend_from_slice(&chunk_data);
69            }
70        }
71
72        // Truncate to exact file size
73        reconstructed.truncate(file_entry.size);
74
75        // Add to FUSE filesystem
76        if let Err(e) = fuse_fs.add_file(&file_entry.path, reconstructed) {
77            if verbose {
78                eprintln!("Warning: Failed to add {}: {}", file_entry.path, e);
79            }
80        }
81    }
82
83    if verbose {
84        println!(
85            "Populated {} files into FUSE filesystem",
86            fuse_fs.file_count()
87        );
88        println!("Total size: {} bytes", fuse_fs.total_size());
89        println!("Mounting at: {}", mountpoint.display());
90        println!();
91    }
92
93    // Verify mountpoint exists
94    if !mountpoint.exists() {
95        anyhow::bail!("Mountpoint does not exist: {}", mountpoint.display());
96    }
97
98    // Configure mount options
99    let options = MountOptions {
100        read_only: true,
101        allow_other,
102        allow_root: !allow_other,
103        fsname: format!("engram:{}", engram.display()),
104    };
105
106    // Mount the filesystem (blocks until unmounted)
107    println!("EngramFS mounted at {}", mountpoint.display());
108    println!(
109        "Use 'fusermount -u {}' to unmount",
110        mountpoint.display()
111    );
112
113    mount(fuse_fs, &mountpoint, options)?;
114
115    if verbose {
116        println!("\nUnmounted.");
117    }
118
119    Ok(())
120}
121
122#[cfg(not(feature = "fuse"))]
123pub fn handle_mount(
124    _engram: std::path::PathBuf,
125    _manifest: std::path::PathBuf,
126    _mountpoint: std::path::PathBuf,
127    _allow_other: bool,
128    _foreground: bool,
129    _verbose: bool,
130) -> anyhow::Result<()> {
131    anyhow::bail!("FUSE support not enabled. Build with --features fuse")
132}