Skip to main content

ragfs_fuse/filesystem/
query.rs

1//! `.query` / `.search` / `.similar` and the other `.ragfs/` control files
2//! (`.index`, `.config`, `.reindex`, `.help`).
3
4use fuser::{FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyWrite};
5use libc::{EINVAL, ENOENT};
6use std::path::PathBuf;
7use tracing::{debug, info, warn};
8
9use super::{RagFs, TTL, reply_file_bytes};
10use crate::inode::{
11    CONFIG_FILE_INO, HELP_FILE_INO, INDEX_FILE_INO, OPS_DIR_INO, QUERY_DIR_INO, REINDEX_FILE_INO,
12    SAFETY_DIR_INO, SEARCH_DIR_INO, SEMANTIC_DIR_INO, SIMILAR_DIR_INO,
13};
14
15impl RagFs {
16    pub(crate) fn lookup_ragfs_dir(&mut self, name_str: &str, reply: ReplyEntry) {
17        match name_str {
18            ".query" => {
19                let attr = self.make_attr(QUERY_DIR_INO, FileType::Directory, 0);
20                reply.entry(&TTL, &attr, 0);
21            }
22            ".search" => {
23                let attr = self.make_attr(SEARCH_DIR_INO, FileType::Directory, 0);
24                reply.entry(&TTL, &attr, 0);
25            }
26            ".index" => {
27                let content = self.get_index_status();
28                let attr =
29                    self.make_attr(INDEX_FILE_INO, FileType::RegularFile, content.len() as u64);
30                let mut cache = self.runtime.block_on(self.content_cache.write());
31                cache.insert(INDEX_FILE_INO, content);
32                reply.entry(&TTL, &attr, 0);
33            }
34            ".config" => {
35                let content = self.get_config();
36                let attr =
37                    self.make_attr(CONFIG_FILE_INO, FileType::RegularFile, content.len() as u64);
38                let mut cache = self.runtime.block_on(self.content_cache.write());
39                cache.insert(CONFIG_FILE_INO, content);
40                reply.entry(&TTL, &attr, 0);
41            }
42            ".reindex" => {
43                let attr = self.make_attr(REINDEX_FILE_INO, FileType::RegularFile, 0);
44                reply.entry(&TTL, &attr, 0);
45            }
46            ".similar" => {
47                let attr = self.make_attr(SIMILAR_DIR_INO, FileType::Directory, 0);
48                reply.entry(&TTL, &attr, 0);
49            }
50            ".help" => {
51                let content = self.get_help_content();
52                let attr =
53                    self.make_attr(HELP_FILE_INO, FileType::RegularFile, content.len() as u64);
54                let mut cache = self.runtime.block_on(self.content_cache.write());
55                cache.insert(HELP_FILE_INO, content);
56                reply.entry(&TTL, &attr, 0);
57            }
58            ".ops" => {
59                let attr = self.make_attr(OPS_DIR_INO, FileType::Directory, 0);
60                reply.entry(&TTL, &attr, 0);
61            }
62            ".safety" => {
63                let attr = self.make_attr(SAFETY_DIR_INO, FileType::Directory, 0);
64                reply.entry(&TTL, &attr, 0);
65            }
66            ".semantic" => {
67                let attr = self.make_attr(SEMANTIC_DIR_INO, FileType::Directory, 0);
68                reply.entry(&TTL, &attr, 0);
69            }
70            _ => reply.error(ENOENT),
71        }
72    }
73
74    pub(crate) fn lookup_query_dir(&mut self, name_str: &str, reply: ReplyEntry) {
75        let query = name_str.to_string();
76        let content = self.execute_query(&query);
77
78        let mut inodes = self.runtime.block_on(self.inodes.write());
79        let ino = inodes.get_or_create_query_result(QUERY_DIR_INO, query);
80        drop(inodes);
81
82        let attr = self.make_attr(ino, FileType::RegularFile, content.len() as u64);
83        let mut cache = self.runtime.block_on(self.content_cache.write());
84        cache.insert(ino, content);
85
86        reply.entry(&TTL, &attr, 0);
87    }
88
89    pub(crate) fn getattr_query(&mut self, ino: u64, reply: ReplyAttr) {
90        match ino {
91            QUERY_DIR_INO | SEARCH_DIR_INO | SIMILAR_DIR_INO => {
92                let attr = self.make_attr(ino, FileType::Directory, 0);
93                reply.attr(&TTL, &attr);
94            }
95            INDEX_FILE_INO => {
96                let content = self.get_index_status();
97                let size = content.len() as u64;
98                let mut cache = self.runtime.block_on(self.content_cache.write());
99                cache.insert(INDEX_FILE_INO, content);
100                let attr = self.make_attr(ino, FileType::RegularFile, size);
101                reply.attr(&TTL, &attr);
102            }
103            CONFIG_FILE_INO => {
104                let content = self.get_config();
105                let size = content.len() as u64;
106                let mut cache = self.runtime.block_on(self.content_cache.write());
107                cache.insert(CONFIG_FILE_INO, content);
108                let attr = self.make_attr(ino, FileType::RegularFile, size);
109                reply.attr(&TTL, &attr);
110            }
111            REINDEX_FILE_INO => {
112                let attr = self.make_attr(ino, FileType::RegularFile, 0);
113                reply.attr(&TTL, &attr);
114            }
115            HELP_FILE_INO => {
116                let content = self.get_help_content();
117                let size = content.len() as u64;
118                let mut cache = self.runtime.block_on(self.content_cache.write());
119                cache.insert(HELP_FILE_INO, content);
120                let attr = self.make_attr(ino, FileType::RegularFile, size);
121                reply.attr(&TTL, &attr);
122            }
123            _ => reply.error(ENOENT),
124        }
125    }
126
127    pub(crate) fn read_query(&mut self, ino: u64, offset: i64, size: u32, reply: ReplyData) {
128        match ino {
129            INDEX_FILE_INO => {
130                let content = self.get_index_status();
131                reply_file_bytes(reply, &content, offset, size);
132            }
133            CONFIG_FILE_INO => {
134                let content = self.get_config();
135                reply_file_bytes(reply, &content, offset, size);
136            }
137            REINDEX_FILE_INO => {
138                reply.data(&[]);
139            }
140            HELP_FILE_INO => {
141                let content = self.get_help_content();
142                reply_file_bytes(reply, &content, offset, size);
143            }
144            _ => reply.error(ENOENT),
145        }
146    }
147
148    pub(crate) fn readdir_ragfs(&mut self, offset: i64, mut reply: ReplyDirectory) {
149        let entries = [
150            (crate::inode::RAGFS_DIR_INO, FileType::Directory, "."),
151            (crate::inode::ROOT_INO, FileType::Directory, ".."),
152            (QUERY_DIR_INO, FileType::Directory, ".query"),
153            (SEARCH_DIR_INO, FileType::Directory, ".search"),
154            (INDEX_FILE_INO, FileType::RegularFile, ".index"),
155            (CONFIG_FILE_INO, FileType::RegularFile, ".config"),
156            (REINDEX_FILE_INO, FileType::RegularFile, ".reindex"),
157            (HELP_FILE_INO, FileType::RegularFile, ".help"),
158            (SIMILAR_DIR_INO, FileType::Directory, ".similar"),
159            (OPS_DIR_INO, FileType::Directory, ".ops"),
160            (SAFETY_DIR_INO, FileType::Directory, ".safety"),
161            (SEMANTIC_DIR_INO, FileType::Directory, ".semantic"),
162        ];
163
164        for (i, (ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
165            if reply.add(*ino, (i + 1) as i64, *kind, name) {
166                break;
167            }
168        }
169        reply.ok();
170    }
171
172    pub(crate) fn readdir_query(&mut self, ino: u64, offset: i64, mut reply: ReplyDirectory) {
173        let entries = [
174            (ino, FileType::Directory, "."),
175            (crate::inode::RAGFS_DIR_INO, FileType::Directory, ".."),
176        ];
177
178        for (i, (entry_ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
179            if reply.add(*entry_ino, (i + 1) as i64, *kind, name) {
180                break;
181            }
182        }
183        reply.ok();
184    }
185
186    pub(crate) fn write_reindex(&mut self, data: &[u8], reply: ReplyWrite) {
187        let path_str = String::from_utf8_lossy(data).trim().to_string();
188
189        if path_str.is_empty() {
190            debug!("Empty reindex request, ignoring");
191            reply.written(data.len() as u32);
192            return;
193        }
194
195        let path = PathBuf::from(&path_str);
196        let absolute_path = match self.jail_reindex_path(&path) {
197            Ok(p) => p,
198            Err(e) => {
199                warn!("Rejected reindex path: {e}");
200                reply.error(EINVAL);
201                return;
202            }
203        };
204
205        info!("Reindex requested for: {:?}", absolute_path);
206
207        if let Some(ref sender) = self.reindex_sender {
208            let sender = sender.clone();
209            let path_to_send = absolute_path.clone();
210
211            self.runtime.spawn(async move {
212                if let Err(e) = sender.send(path_to_send).await {
213                    warn!("Failed to send reindex request: {}", e);
214                }
215            });
216
217            debug!("Reindex request sent for: {:?}", absolute_path);
218        } else {
219            warn!("Reindex requested but no sender configured");
220        }
221
222        reply.written(data.len() as u32);
223    }
224}