lb_fs/
fs_impl.rs

1use crate::{
2    cache::FileEntry,
3    utils::{fmt, get_string},
4};
5use async_trait::async_trait;
6use lb_rs::{model::file_metadata::FileType, Lb, Uuid};
7use nfsserve::{
8    nfs::{
9        fattr3, fileid3, filename3, nfspath3, nfsstat3, nfsstring, sattr3, set_atime, set_gid3,
10        set_mode3, set_mtime, set_size3, set_uid3,
11    },
12    vfs::{DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities},
13};
14use std::{collections::HashMap, sync::Arc};
15use tokio::sync::Mutex;
16use tracing::{info, instrument, warn};
17
18#[derive(Clone)]
19pub struct Drive {
20    pub lb: Lb,
21
22    /// must be not-nil before NFSFIlesSystem is mounted
23    pub root: Uuid,
24
25    /// probably this doesn't need to have a global lock, but interactions here are generally
26    /// speedy, and for now we'll go for robustness over performance. Hopefully this accomplishes
27    /// that and not deadlock. TBD.
28    ///
29    /// this is stored in memory as it's own entity and not stored in core for two reasons:
30    /// 1. size computations are expensive in core
31    /// 2. nfs needs to update timestamps to specified values
32    /// 3. nfs models properties we don't, like file permission bits
33    pub data: Arc<Mutex<HashMap<fileid3, FileEntry>>>,
34}
35
36#[async_trait]
37impl NFSFileSystem for Drive {
38    #[instrument(skip(self))]
39    fn root_dir(&self) -> fileid3 {
40        let root = self.root;
41        let half = root.as_u64_pair().0;
42
43        info!("ret={root}");
44        half
45    }
46
47    fn capabilities(&self) -> VFSCapabilities {
48        VFSCapabilities::ReadWrite
49    }
50
51    #[instrument(skip(self), fields(id = fmt(id), buffer = buffer.len()))]
52    async fn write(&self, id: fileid3, offset: u64, buffer: &[u8]) -> Result<fattr3, nfsstat3> {
53        let offset = offset as usize;
54
55        let mut data = self.data.lock().await;
56        let entry = data.get_mut(&id).unwrap();
57        let id = entry.file.id;
58
59        let mut doc = self.lb.read_document(id, false).await.unwrap();
60        let mut expanded = false;
61        if offset + buffer.len() > doc.len() {
62            doc.resize(offset + buffer.len(), 0);
63            doc[offset..].copy_from_slice(buffer);
64            expanded = true;
65        } else {
66            for (idx, datum) in buffer.iter().enumerate() {
67                doc[offset + idx] = *datum;
68            }
69        }
70        let doc_size = doc.len();
71        self.lb.write_document(id, &doc).await.unwrap();
72
73        entry.fattr.size = doc_size as u64;
74
75        info!("expanded={expanded}, fattr.size = {}", doc_size);
76
77        Ok(entry.fattr)
78    }
79
80    // todo this should create a file regardless of whether it exists
81    #[instrument(skip(self), fields(dirid = fmt(dirid), filename = get_string(filename)))]
82    async fn create(
83        &self, dirid: fileid3, filename: &filename3, attr: sattr3,
84    ) -> Result<(fileid3, fattr3), nfsstat3> {
85        let filename = get_string(filename);
86        let parent = self.data.lock().await.get(&dirid).unwrap().file.id;
87        let file = self
88            .lb
89            .create_file(&filename, &parent, FileType::Document)
90            .await
91            .unwrap();
92
93        let entry = FileEntry::from_file(file, 0);
94        let id = entry.fattr.fileid;
95        self.data.lock().await.insert(entry.fattr.fileid, entry);
96
97        let file = self.setattr(id, attr).await.unwrap();
98
99        info!("({}, size={})", fmt(file.fileid), file.size);
100        Ok((id, file))
101    }
102
103    #[instrument(skip(self), fields(dirid = fmt(dirid), filename = get_string(filename)))]
104    async fn create_exclusive(
105        &self, dirid: fileid3, filename: &filename3,
106    ) -> Result<fileid3, nfsstat3> {
107        let filename = get_string(filename);
108        let dirid = self.data.lock().await.get(&dirid).unwrap().file.id;
109        let children = self.lb.get_children(&dirid).await.unwrap();
110        for child in children {
111            if child.name == filename {
112                warn!("exists already");
113                return Err(nfsstat3::NFS3ERR_EXIST);
114            }
115        }
116
117        let file = self
118            .lb
119            .create_file(&filename, &dirid, FileType::Document)
120            .await
121            .unwrap();
122
123        let entry = FileEntry::from_file(file, 0);
124        let id = entry.fattr.fileid;
125        info!("({}, size={})", fmt(id), entry.fattr.size);
126        self.data.lock().await.insert(entry.fattr.fileid, entry);
127
128        return Ok(id);
129    }
130
131    #[instrument(skip(self), fields(dirid = fmt(dirid), filename = get_string(filename)))]
132    async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result<fileid3, nfsstat3> {
133        let dir = self.data.lock().await.get(&dirid).unwrap().file.clone();
134
135        if dir.is_document() {
136            info!("NOTDIR");
137            return Err(nfsstat3::NFS3ERR_NOTDIR);
138        }
139
140        // if looking for dir/. its the current directory
141        if filename[..] == [b'.'] {
142            info!(". == {}", fmt(dirid));
143            return Ok(dirid);
144        }
145
146        // if looking for dir/.. its the parent directory
147        if filename[..] == [b'.', b'.'] {
148            info!(".. == {}", dir.parent);
149            return Ok(dir.parent.as_u64_pair().0);
150        }
151
152        let children = self.lb.get_children(&dir.id).await.unwrap();
153        let file_name = String::from_utf8(filename.0.clone()).unwrap();
154
155        for child in children {
156            if file_name == child.name {
157                info!("{}", child.id);
158                return Ok(child.id.as_u64_pair().0);
159            }
160        }
161
162        info!("NOENT");
163        Err(nfsstat3::NFS3ERR_NOENT)
164    }
165
166    #[instrument(skip(self), fields(id = fmt(id)))]
167    async fn getattr(&self, id: fileid3) -> Result<fattr3, nfsstat3> {
168        let file = self.data.lock().await.get(&id).unwrap().fattr;
169        info!("fattr = {:?}", file);
170        Ok(file)
171    }
172
173    #[instrument(skip(self), fields(id = fmt(id)))]
174    async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result<fattr3, nfsstat3> {
175        let mut data = self.data.lock().await;
176        let now = FileEntry::now();
177        let entry = data.get_mut(&id).unwrap();
178
179        match setattr.size {
180            set_size3::Void => {}
181            set_size3::size(new) => {
182                if entry.fattr.size != new {
183                    let mut doc = self.lb.read_document(entry.file.id, false).await.unwrap();
184                    doc.resize(new as usize, 0);
185                    self.lb.write_document(entry.file.id, &doc).await.unwrap();
186                    entry.fattr.mtime = FileEntry::ts_from_u64(now);
187                    entry.fattr.ctime = FileEntry::ts_from_u64(now);
188                }
189            }
190        }
191
192        match setattr.atime {
193            set_atime::DONT_CHANGE => {}
194            set_atime::SET_TO_SERVER_TIME => {
195                entry.fattr.atime = FileEntry::ts_from_u64(now);
196            }
197            set_atime::SET_TO_CLIENT_TIME(ts) => {
198                entry.fattr.atime = ts;
199            }
200        }
201
202        match setattr.mtime {
203            set_mtime::DONT_CHANGE => {}
204            set_mtime::SET_TO_SERVER_TIME => {
205                entry.fattr.mtime = FileEntry::ts_from_u64(now);
206                entry.fattr.ctime = FileEntry::ts_from_u64(now);
207            }
208            set_mtime::SET_TO_CLIENT_TIME(ts) => {
209                entry.fattr.mtime = ts;
210                entry.fattr.ctime = ts;
211            }
212        }
213
214        match setattr.uid {
215            set_uid3::Void => {}
216            set_uid3::uid(uid) => {
217                entry.fattr.uid = uid;
218                entry.fattr.ctime = FileEntry::ts_from_u64(now);
219            }
220        }
221
222        match setattr.gid {
223            set_gid3::Void => {}
224            set_gid3::gid(gid) => {
225                entry.fattr.gid = gid;
226                entry.fattr.ctime = FileEntry::ts_from_u64(now);
227            }
228        }
229
230        match setattr.mode {
231            set_mode3::Void => {}
232            set_mode3::mode(mode) => {
233                entry.fattr.mode = mode;
234                entry.fattr.ctime = FileEntry::ts_from_u64(now);
235            }
236        }
237
238        info!("fattr = {:?}", entry.fattr);
239
240        return Ok(entry.fattr);
241    }
242
243    #[instrument(skip(self), fields(id = fmt(id), offset, count))]
244    async fn read(
245        &self, id: fileid3, offset: u64, count: u32,
246    ) -> Result<(Vec<u8>, bool), nfsstat3> {
247        let offset = offset as usize;
248        let count = count as usize;
249        let id = self.data.lock().await.get(&id).unwrap().file.id;
250
251        let doc = self.lb.read_document(id, false).await.unwrap();
252
253        if offset >= doc.len() {
254            info!("[] EOF");
255            return Ok((vec![], true));
256        }
257
258        if offset + count >= doc.len() {
259            info!("|{}| EOF", doc[offset..].len());
260            return Ok((doc[offset..].to_vec(), true));
261        }
262
263        info!("|{}|", count);
264        return Ok((doc[offset..offset + count].to_vec(), false));
265    }
266
267    /// they will provide a start_after of 0 for no id
268    #[instrument(skip(self), fields(dirid = fmt(dirid), start_after, max_entries))]
269    async fn readdir(
270        &self, dirid: fileid3, start_after: fileid3, max_entries: usize,
271    ) -> Result<ReadDirResult, nfsstat3> {
272        let data = self.data.lock().await;
273        let dirid = data.get(&dirid).unwrap().file.id;
274        let mut children = self.lb.get_children(&dirid).await.unwrap();
275
276        children.sort_by(|a, b| a.id.cmp(&b.id));
277
278        // this is how the example does it, we'd never return a fileid3 of 0
279        let mut start_index = 0;
280        if start_after > 0 {
281            for (idx, child) in children.iter().enumerate() {
282                if child.id.as_u64_pair().0 == start_after {
283                    start_index = idx + 1;
284                }
285            }
286        }
287
288        let mut ret = ReadDirResult::default();
289
290        if start_index >= children.len() {
291            ret.end = true;
292            info!("[] done");
293            return Ok(ret);
294        }
295
296        let end_index = if start_index + max_entries >= children.len() {
297            ret.end = true;
298            children.len()
299        } else {
300            start_index + max_entries
301        };
302
303        for child in &children[start_index..end_index] {
304            let fileid = child.id.as_u64_pair().0;
305            let name = nfsstring(child.name.clone().into_bytes());
306            let attr = data.get(&fileid).unwrap().fattr;
307
308            ret.entries.push(DirEntry { fileid, name, attr });
309        }
310
311        info!("|{}| done={}", ret.entries.len(), ret.end);
312
313        Ok(ret)
314    }
315
316    /// Removes a file.
317    /// If not supported dur to readonly file system
318    /// this should return Err(nfsstat3::NFS3ERR_ROFS)
319    #[instrument(skip(self), fields(dirid = fmt(dirid), filename = get_string(filename)))]
320    #[allow(unused)]
321    async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3> {
322        let mut data = self.data.lock().await;
323        let dirid = data.get(&dirid).unwrap().file.id;
324
325        let children = self.lb.get_children(&dirid).await.unwrap();
326        let file_name = String::from_utf8(filename.0.clone()).unwrap();
327
328        for child in children {
329            if file_name == child.name {
330                info!("deleted");
331                self.lb.delete(&child.id).await;
332                data.remove(&child.id.as_u64_pair().0);
333                return Ok(());
334            }
335        }
336
337        info!("NOENT");
338        return Err(nfsstat3::NFS3ERR_NOENT);
339    }
340
341    /// either an overwrite rename or move
342    #[instrument(skip(self), fields(from_dirid = fmt(from_dirid), from_filename = get_string(from_filename), to_dirid = fmt(to_dirid), to_filename = get_string(to_filename)))]
343    #[allow(unused)]
344    async fn rename(
345        &self, from_dirid: fileid3, from_filename: &filename3, to_dirid: fileid3,
346        to_filename: &filename3,
347    ) -> Result<(), nfsstat3> {
348        let mut data = self.data.lock().await;
349
350        let from_filename = String::from_utf8(from_filename.0.clone()).unwrap();
351        let to_filename = String::from_utf8(to_filename.0.clone()).unwrap();
352
353        let from_dirid = data.get(&from_dirid).unwrap().file.id;
354        let to_dirid = data.get(&to_dirid).unwrap().file.id;
355
356        let src_children = self.lb.get_children(&from_dirid).await.unwrap();
357
358        let mut from_id = None;
359        let mut to_id = None;
360        for child in src_children {
361            if child.name == from_filename {
362                from_id = Some(child.id);
363            }
364
365            if to_dirid == from_dirid && child.name == to_filename {
366                to_id = Some(child.id);
367            }
368        }
369
370        if to_dirid != from_dirid {
371            let dst_children = self.lb.get_children(&to_dirid).await.unwrap();
372            for child in dst_children {
373                if child.name == to_filename {
374                    to_id = Some(child.id);
375                }
376            }
377        }
378
379        let from_id = from_id.unwrap();
380
381        match to_id {
382            // we are overwriting a file
383            Some(id) => {
384                info!("overwrite {from_id} -> {id}");
385                let from_doc = self.lb.read_document(from_id, false).await.unwrap();
386                info!("|{}|", from_doc.len());
387                let doc_len = from_doc.len() as u64;
388                self.lb.write_document(id, &from_doc).await.unwrap();
389                self.lb.delete(&from_id).await.unwrap();
390
391                let mut entry = data.get_mut(&id.as_u64_pair().0).unwrap();
392                entry.fattr.size = doc_len;
393
394                data.remove(&from_id.as_u64_pair().0);
395            }
396
397            // we are doing a move and/or rename
398            None => {
399                if from_dirid != to_dirid {
400                    info!("move {} -> {}\t", from_id, to_dirid);
401                    self.lb.move_file(&from_id, &to_dirid).await.unwrap();
402                }
403
404                if from_filename != to_filename {
405                    info!("rename {} -> {}\t", from_id, to_filename);
406                    self.lb.rename_file(&from_id, &to_filename).await.unwrap();
407                }
408
409                let mut entry = data.get_mut(&from_id.as_u64_pair().0).unwrap();
410
411                let file = self.lb.get_file_by_id(from_id).await.unwrap();
412                entry.file = file;
413
414                info!("ok");
415            }
416        }
417
418        return Ok(());
419    }
420
421    #[instrument(skip(self), fields(dirid = fmt(dirid), dirname = get_string(dirname)))]
422    #[allow(unused)]
423    async fn mkdir(
424        &self, dirid: fileid3, dirname: &filename3,
425    ) -> Result<(fileid3, fattr3), nfsstat3> {
426        let filename = get_string(dirname);
427        let parent = self.data.lock().await.get(&dirid).unwrap().file.id;
428        let file = self
429            .lb
430            .create_file(&filename, &parent, FileType::Folder)
431            .await
432            .unwrap();
433
434        let entry = FileEntry::from_file(file, 0);
435        let id = entry.fattr.fileid;
436        let fattr = entry.fattr;
437        self.data.lock().await.insert(entry.fattr.fileid, entry);
438
439        info!("({}, fattr={:?})", fmt(id), fattr);
440        Ok((id, fattr))
441    }
442
443    async fn symlink(
444        &self, _dirid: fileid3, _linkname: &filename3, _symlink: &nfspath3, _attr: &sattr3,
445    ) -> Result<(fileid3, fattr3), nfsstat3> {
446        info!("symlink NOTSUPP");
447        return Err(nfsstat3::NFS3ERR_NOTSUPP);
448    }
449    async fn readlink(&self, _id: fileid3) -> Result<nfspath3, nfsstat3> {
450        info!("readklink NOTSUPP");
451        return Err(nfsstat3::NFS3ERR_NOTSUPP);
452    }
453}