1use 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}