remotefs_memory/
lib.rs

1#![crate_name = "remotefs_memory"]
2#![crate_type = "lib"]
3
4//! # remotefs-memory
5//!
6//! A memory-based implementation of the `remotefs` crate.
7//! This crate provides a simple in-memory filesystem that can be used for testing purposes.
8//!
9//! ## Getting Started
10//!
11//! Add `remotefs-memory` to your `Cargo.toml`:
12//!
13//! ```toml
14//! remotefs = "0.3"
15//! remotefs-memory = "0.1"
16//! ```
17//!
18//! ## Example
19//!
20//! ```rust
21//! use std::path::PathBuf;
22//!
23//! use remotefs_memory::{Inode, MemoryFs, node, Node, Tree};
24//! use remotefs::RemoteFs;
25//! use remotefs::fs::{UnixPex, Metadata};
26//!
27//! let tempdir = PathBuf::from("/tmp");
28//! let tree = Tree::new(node!(
29//!     PathBuf::from("/"),
30//!     Inode::dir(0, 0, UnixPex::from(0o755)),
31//!     node!(tempdir.clone(), Inode::dir(0, 0, UnixPex::from(0o755)))
32//! ));
33//!
34//! let mut client = MemoryFs::new(tree);
35//!
36//! assert!(client.connect().is_ok());
37//! // Change directory
38//! assert!(client.change_dir(tempdir.as_path()).is_ok());
39//! ```
40//!
41
42#![doc(html_playground_url = "https://play.rust-lang.org")]
43#![doc(
44    html_favicon_url = "https://raw.githubusercontent.com/remotefs-rs/remotefs-rs/main/assets/logo-128.png"
45)]
46#![doc(
47    html_logo_url = "https://raw.githubusercontent.com/remotefs-rs/remotefs-rs/main/assets/logo.png"
48)]
49
50#[macro_use]
51extern crate log;
52
53mod inode;
54#[cfg(test)]
55mod test;
56
57use std::io::{Cursor, Read, Seek, Write};
58use std::path::{Path, PathBuf};
59use std::time::SystemTime;
60
61pub use orange_trees::{node, Node, Tree};
62use remotefs::fs::stream::{StreamWriter, WriteAndSeek};
63use remotefs::fs::{FileType, Metadata, ReadStream, UnixPex, Welcome, WriteStream};
64use remotefs::{File, RemoteError, RemoteErrorType, RemoteFs, RemoteResult};
65
66pub use self::inode::Inode;
67
68/// Alias for the filesystem tree. It is a [`Tree`] of [`PathBuf`] and [`Inode`].
69pub type FsTree = Tree<PathBuf, Inode>;
70
71/// MemoryFs is a simple in-memory filesystem that can be used for testing purposes.
72///
73/// It implements the [`RemoteFs`] trait.
74///
75/// The [`MemoryFs`] is instantiated providing a [`orange_trees::Tree`] which contains the filesystem data.
76///
77/// When reading or writing files, the [`MemoryFs`] will use the [`orange_trees::Tree`] to store the data.
78///
79/// You can easily create the [`MemoryFs`] using the [`MemoryFs::new`] method, providing the tree.
80/// Use the [`node!`] macro to create the tree or use the [`orange_trees`] crate to create it programmatically.
81///
82/// The tree contains nodes identified by a [`PathBuf`] and a value of type [`Inode`].
83pub struct MemoryFs {
84    tree: FsTree,
85    wrkdir: PathBuf,
86    connected: bool,
87    // Fn to get uid
88    get_uid: Box<dyn Fn() -> u32 + Send + Sync>,
89    // Fn to get gid
90    get_gid: Box<dyn Fn() -> u32 + Send + Sync>,
91}
92
93#[derive(Debug, Clone)]
94struct WriteHandle {
95    path: PathBuf,
96    data: Cursor<Vec<u8>>,
97    mode: WriteMode,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101enum WriteMode {
102    Append,
103    Create,
104}
105
106impl Write for WriteHandle {
107    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
108        self.data.write(buf)
109    }
110
111    fn flush(&mut self) -> std::io::Result<()> {
112        Ok(())
113    }
114}
115
116impl Seek for WriteHandle {
117    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
118        self.data.seek(pos)
119    }
120}
121
122impl WriteAndSeek for WriteHandle {}
123
124impl MemoryFs {
125    /// Create a new instance of the [`MemoryFs`] with the provided [`FsTree`].
126    pub fn new(tree: FsTree) -> Self {
127        Self {
128            tree,
129            wrkdir: PathBuf::from("/"),
130            connected: false,
131            get_uid: Box::new(|| 0),
132            get_gid: Box::new(|| 0),
133        }
134    }
135
136    /// Set the function to get the user id (uid).
137    pub fn with_get_uid<F>(mut self, get_uid: F) -> Self
138    where
139        F: Fn() -> u32 + Send + Sync + 'static,
140    {
141        self.get_uid = Box::new(get_uid);
142        self
143    }
144
145    /// Set the function to get the group id (gid).
146    pub fn with_get_gid<F>(mut self, get_gid: F) -> Self
147    where
148        F: Fn() -> u32 + Send + Sync + 'static,
149    {
150        self.get_gid = Box::new(get_gid);
151        self
152    }
153
154    fn absolutize(&self, path: &Path) -> PathBuf {
155        if path.is_absolute() {
156            path.to_path_buf()
157        } else {
158            self.wrkdir.join(path)
159        }
160    }
161
162    /// Downcast the write handle to a write handle.
163    fn downcast_write_handle(handle: WriteStream) -> Box<WriteHandle> {
164        match handle.stream {
165            StreamWriter::Write(w) => {
166                let raw: *mut dyn Write = Box::into_raw(w);
167                unsafe { Box::from_raw(raw as *mut WriteHandle) }
168            }
169            StreamWriter::WriteAndSeek(w) => {
170                let raw: *mut dyn WriteAndSeek = Box::into_raw(w);
171                unsafe { Box::from_raw(raw as *mut WriteHandle) }
172            }
173        }
174    }
175}
176
177impl RemoteFs for MemoryFs {
178    fn connect(&mut self) -> RemoteResult<Welcome> {
179        debug!("connect()");
180        self.connected = true;
181        Ok(Welcome::default())
182    }
183
184    fn disconnect(&mut self) -> RemoteResult<()> {
185        debug!("disconnect()");
186        self.connected = false;
187        Ok(())
188    }
189
190    fn is_connected(&mut self) -> bool {
191        debug!("is_connected() -> {}", self.connected);
192        self.connected
193    }
194
195    fn pwd(&mut self) -> RemoteResult<PathBuf> {
196        if !self.connected {
197            return Err(RemoteError::new(RemoteErrorType::NotConnected));
198        }
199        debug!("pwd() -> {:?}", self.wrkdir);
200
201        Ok(self.wrkdir.clone())
202    }
203
204    fn change_dir(&mut self, dir: &Path) -> RemoteResult<PathBuf> {
205        if !self.connected {
206            return Err(RemoteError::new(RemoteErrorType::NotConnected));
207        }
208
209        let dir = self.absolutize(dir);
210
211        debug!("change_dir({:?})", dir);
212
213        // check if the directory exists
214        let inode = self
215            .tree
216            .root()
217            .query(&dir)
218            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?
219            .value()
220            .clone();
221
222        match inode.metadata().file_type {
223            FileType::Directory => {
224                self.wrkdir = dir.clone();
225                Ok(self.wrkdir.clone())
226            }
227            FileType::Symlink if inode.metadata().symlink.is_some() => {
228                self.change_dir(inode.metadata().symlink.as_ref().unwrap())
229            }
230            FileType::Symlink | FileType::File => Err(RemoteError::new(RemoteErrorType::BadFile)),
231        }
232    }
233
234    fn list_dir(&mut self, path: &Path) -> RemoteResult<Vec<File>> {
235        if !self.connected {
236            return Err(RemoteError::new(RemoteErrorType::NotConnected));
237        }
238
239        let path = self.absolutize(path);
240        debug!("list_dir({:?})", path);
241
242        // query node
243        let node = self
244            .tree
245            .root()
246            .query(&path)
247            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
248
249        let mut files = vec![];
250
251        for child in node.children() {
252            let path = child.id().clone();
253            let metadata = child.value().metadata().clone();
254            debug!("list_dir() -> {path:?}, {metadata:?}");
255
256            files.push(File { path, metadata })
257        }
258
259        Ok(files)
260    }
261
262    fn stat(&mut self, path: &Path) -> RemoteResult<File> {
263        if !self.connected {
264            return Err(RemoteError::new(RemoteErrorType::NotConnected));
265        }
266
267        let path = self.absolutize(path);
268        debug!("stat({:?})", path);
269
270        let node = self
271            .tree
272            .root()
273            .query(&path)
274            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
275
276        let path = node.id().clone();
277        let metadata = node.value().metadata().clone();
278
279        debug!("stat({path:?}) -> {metadata:?}");
280
281        Ok(File { path, metadata })
282    }
283
284    fn setstat(&mut self, path: &Path, metadata: Metadata) -> RemoteResult<()> {
285        if !self.connected {
286            return Err(RemoteError::new(RemoteErrorType::NotConnected));
287        }
288
289        let path = self.absolutize(path);
290        debug!("setstat({:?}, {:?})", path, metadata);
291
292        let node = self
293            .tree
294            .root_mut()
295            .query_mut(&path)
296            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
297
298        node.set_value(Inode {
299            metadata,
300            content: node.value().content.clone(),
301        });
302
303        Ok(())
304    }
305
306    fn exists(&mut self, path: &Path) -> RemoteResult<bool> {
307        if !self.connected {
308            return Err(RemoteError::new(RemoteErrorType::NotConnected));
309        }
310
311        let path = self.absolutize(path);
312        debug!("exists({:?})", path);
313
314        Ok(self.tree.root().query(&path).is_some())
315    }
316
317    fn remove_file(&mut self, path: &Path) -> RemoteResult<()> {
318        if !self.connected {
319            return Err(RemoteError::new(RemoteErrorType::NotConnected));
320        }
321
322        let path = self.absolutize(path);
323        debug!("remove_file({:?})", path);
324
325        // get node
326        let node = self
327            .tree
328            .root_mut()
329            .query_mut(&path)
330            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
331
332        // check if is a leaf and a file
333        if !node.is_leaf() || node.value().metadata().file_type == FileType::Directory {
334            return Err(RemoteError::new(RemoteErrorType::CouldNotRemoveFile));
335        }
336
337        let parent = self.tree.root_mut().parent_mut(&path).unwrap();
338        parent.remove_child(&path);
339
340        Ok(())
341    }
342
343    fn remove_dir(&mut self, path: &Path) -> RemoteResult<()> {
344        if !self.connected {
345            return Err(RemoteError::new(RemoteErrorType::NotConnected));
346        }
347
348        let path = self.absolutize(path);
349        debug!("remove_dir({:?})", path);
350
351        // get node
352        let node = self
353            .tree
354            .root_mut()
355            .query_mut(&path)
356            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
357        // check if is a leaf and is a directory
358        if !node.is_leaf() {
359            debug!("Directory {path:?} is not empty");
360            return Err(RemoteError::new(RemoteErrorType::DirectoryNotEmpty));
361        }
362        if node.value().metadata().file_type != FileType::Directory {
363            debug!("{path:?} is not a directory");
364            return Err(RemoteError::new(RemoteErrorType::CouldNotRemoveFile));
365        }
366
367        let parent = self.tree.root_mut().parent_mut(&path).unwrap();
368        parent.remove_child(&path);
369        debug!("removed {:?}", path);
370
371        Ok(())
372    }
373
374    fn remove_dir_all(&mut self, path: &Path) -> RemoteResult<()> {
375        if !self.connected {
376            return Err(RemoteError::new(RemoteErrorType::NotConnected));
377        }
378
379        let path = self.absolutize(path);
380        debug!("remove_dir_all({:?})", path);
381
382        let parent = self
383            .tree
384            .root_mut()
385            .parent_mut(&path)
386            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
387
388        if !parent.children().iter().any(|child| *child.id() == path) {
389            return Err(RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory));
390        }
391        parent.remove_child(&path);
392        debug!("removed {:?}", path);
393
394        Ok(())
395    }
396
397    fn create_dir(&mut self, path: &Path, mode: UnixPex) -> RemoteResult<()> {
398        if !self.connected {
399            return Err(RemoteError::new(RemoteErrorType::NotConnected));
400        }
401
402        let path = self.absolutize(path);
403        debug!("create_dir({:?})", path);
404        let parent = path
405            .parent()
406            .unwrap_or_else(|| Path::new("/"))
407            .to_path_buf();
408
409        let dir = Inode::dir((self.get_uid)(), (self.get_gid)(), mode);
410
411        let parent = self
412            .tree
413            .root_mut()
414            .query_mut(&parent)
415            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
416
417        // check if the directory already exists
418        if parent.children().iter().any(|child| *child.id() == path) {
419            debug!("Directory {path:?} already exists");
420            return Err(RemoteError::new(RemoteErrorType::DirectoryAlreadyExists));
421        }
422
423        // add the directory
424        parent.add_child(Node::new(path.clone(), dir));
425        debug!("created directory {path:?}");
426
427        Ok(())
428    }
429
430    fn symlink(&mut self, path: &Path, target: &Path) -> RemoteResult<()> {
431        if !self.connected {
432            return Err(RemoteError::new(RemoteErrorType::NotConnected));
433        }
434        let path = self.absolutize(path);
435        let target = self.absolutize(target);
436        debug!("symlink({:?}, {:?})", path, target);
437        // check if `target` exists
438        if self.tree.root().query(&target).is_none() {
439            debug!("target {target:?} does not exist");
440            return Err(RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory));
441        }
442
443        let parent = path
444            .parent()
445            .unwrap_or_else(|| Path::new("/"))
446            .to_path_buf();
447
448        let symlink = Inode::symlink((self.get_uid)(), (self.get_gid)(), target.to_path_buf());
449
450        let parent = self
451            .tree
452            .root_mut()
453            .query_mut(&parent)
454            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
455
456        // check if the file already exists
457        if parent.children().iter().any(|child| *child.id() == path) {
458            debug!("symbolic link {path:?} already exists");
459            return Err(RemoteError::new(RemoteErrorType::FileCreateDenied));
460        }
461
462        // add the directory
463        parent.add_child(Node::new(path.clone(), symlink));
464        debug!("symlink {path:?} -> {target:?}");
465
466        Ok(())
467    }
468
469    fn copy(&mut self, src: &Path, dest: &Path) -> RemoteResult<()> {
470        if !self.connected {
471            return Err(RemoteError::new(RemoteErrorType::NotConnected));
472        }
473        let src = self.absolutize(src);
474        let dest = self.absolutize(dest);
475        debug!("copy({:?}, {:?})", src, dest);
476
477        let dest_parent = dest
478            .parent()
479            .unwrap_or_else(|| Path::new("/"))
480            .to_path_buf();
481
482        let dest_inode = self
483            .tree
484            .root()
485            .query(&src)
486            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?
487            .value()
488            .clone();
489
490        let dest_parent = self
491            .tree
492            .root_mut()
493            .query_mut(&dest_parent)
494            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
495
496        debug!("copied {src:?} to {dest:?}");
497        dest_parent.add_child(Node::new(dest, dest_inode));
498
499        Ok(())
500    }
501
502    fn mov(&mut self, src: &Path, dest: &Path) -> RemoteResult<()> {
503        if !self.connected {
504            return Err(RemoteError::new(RemoteErrorType::NotConnected));
505        }
506        let src = self.absolutize(src);
507        let dest = self.absolutize(dest);
508        debug!("mov({:?}, {:?})", src, dest);
509
510        let dest_parent = dest
511            .parent()
512            .unwrap_or_else(|| Path::new("/"))
513            .to_path_buf();
514
515        let dest_inode = self
516            .tree
517            .root()
518            .query(&src)
519            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?
520            .value()
521            .clone();
522
523        let dest_parent = self
524            .tree
525            .root_mut()
526            .query_mut(&dest_parent)
527            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
528
529        dest_parent.add_child(Node::new(dest.clone(), dest_inode));
530
531        // remove src
532        let src_parent = self
533            .tree
534            .root_mut()
535            .parent_mut(&src)
536            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
537
538        src_parent.remove_child(&src);
539        debug!("moved {src:?} to {dest:?}");
540
541        Ok(())
542    }
543
544    fn exec(&mut self, _cmd: &str) -> RemoteResult<(u32, String)> {
545        Err(RemoteError::new(RemoteErrorType::UnsupportedFeature))
546    }
547
548    fn append(&mut self, path: &Path, metadata: &Metadata) -> RemoteResult<WriteStream> {
549        if !self.connected {
550            return Err(RemoteError::new(RemoteErrorType::NotConnected));
551        }
552        let path = self.absolutize(path);
553        debug!("append({:?},{:?})", path, metadata);
554        let parent = path
555            .parent()
556            .unwrap_or_else(|| Path::new("/"))
557            .to_path_buf();
558
559        let parent = self
560            .tree
561            .root_mut()
562            .query_mut(&parent)
563            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
564
565        // get current content if any
566        let content = match parent.query(&path) {
567            Some(node) => node.value().content.clone(),
568            None => None,
569        };
570
571        let file = Inode::file(
572            metadata.uid.unwrap_or((self.get_uid)()),
573            metadata.gid.unwrap_or((self.get_gid)()),
574            metadata.mode.unwrap_or_else(|| UnixPex::from(0o755)),
575            content.clone().unwrap_or_default(),
576        );
577
578        // add new file
579        parent.add_child(Node::new(path.clone(), file));
580
581        // make stream
582        debug!("file {path:?} opened for append");
583        let handle = WriteHandle {
584            path,
585            data: Cursor::new(content.unwrap_or_default()),
586            mode: WriteMode::Append,
587        };
588
589        let stream = Box::new(handle) as Box<dyn WriteAndSeek + Send>;
590
591        Ok(WriteStream {
592            stream: StreamWriter::WriteAndSeek(stream),
593        })
594    }
595
596    fn create(&mut self, path: &Path, metadata: &Metadata) -> RemoteResult<WriteStream> {
597        if !self.connected {
598            return Err(RemoteError::new(RemoteErrorType::NotConnected));
599        }
600        let path = self.absolutize(path);
601        debug!("create({:?},{:?})", path, metadata);
602        let parent = path
603            .parent()
604            .unwrap_or_else(|| Path::new("/"))
605            .to_path_buf();
606
607        let parent = self
608            .tree
609            .root_mut()
610            .query_mut(&parent)
611            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
612
613        let file = Inode::file(
614            metadata.uid.unwrap_or((self.get_uid)()),
615            metadata.gid.unwrap_or((self.get_gid)()),
616            metadata.mode.unwrap_or_else(|| UnixPex::from(0o755)),
617            vec![],
618        );
619
620        // add new file
621        parent.add_child(Node::new(path.clone(), file));
622        debug!("{:?} created", path);
623
624        // make stream
625        let handle = WriteHandle {
626            path,
627            data: Cursor::new(vec![]),
628            mode: WriteMode::Create,
629        };
630
631        let stream = Box::new(handle) as Box<dyn WriteAndSeek + Send>;
632
633        Ok(WriteStream {
634            stream: StreamWriter::WriteAndSeek(stream),
635        })
636    }
637
638    fn open(&mut self, path: &Path) -> RemoteResult<ReadStream> {
639        if !self.connected {
640            return Err(RemoteError::new(RemoteErrorType::NotConnected));
641        }
642        let path = self.absolutize(path);
643        debug!("open({:?})", path);
644
645        let node = self
646            .tree
647            .root()
648            .query(&path)
649            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
650
651        debug!("{:?} opened", path);
652
653        let stream = Cursor::new(node.value().content.as_ref().cloned().unwrap_or_default());
654        let stream = Box::new(stream) as Box<dyn Read + Send>;
655
656        Ok(ReadStream::from(stream))
657    }
658
659    fn on_written(&mut self, writable: WriteStream) -> RemoteResult<()> {
660        let handle = Self::downcast_write_handle(writable);
661        debug!("on_written({:?}, {:?})", handle.path, handle.mode);
662
663        // get node
664        let node = self
665            .tree
666            .root_mut()
667            .query_mut(&handle.path)
668            .ok_or_else(|| RemoteError::new(RemoteErrorType::NoSuchFileOrDirectory))?;
669
670        let mut value = node.value().clone();
671
672        value.content = match handle.mode {
673            WriteMode::Append => {
674                let mut content = value.content.as_ref().cloned().unwrap_or_default();
675                content.extend_from_slice(handle.data.get_ref());
676                Some(content)
677            }
678            WriteMode::Create => Some(handle.data.get_ref().to_vec()),
679        };
680        value.metadata.size = match handle.mode {
681            WriteMode::Append => {
682                let mut size = value.metadata.size;
683                size += handle.data.get_ref().len() as u64;
684                size
685            }
686            WriteMode::Create => handle.data.get_ref().len() as u64,
687        };
688        value.metadata.modified = Some(SystemTime::now());
689
690        debug!("{:?} written {:?}", handle.path, value);
691        node.set_value(value);
692
693        Ok(())
694    }
695}