Skip to main content

eryx_vfs/
storage.rs

1//! VFS storage trait and implementations.
2
3use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5use std::time::SystemTime;
6
7use async_trait::async_trait;
8use tokio::sync::RwLock;
9
10use crate::error::{VfsError, VfsResult};
11
12/// Metadata for a file or directory.
13#[derive(Debug, Clone)]
14pub struct Metadata {
15    /// Whether this is a directory.
16    pub is_dir: bool,
17    /// Size in bytes (0 for directories).
18    pub size: u64,
19    /// Creation time.
20    pub created: SystemTime,
21    /// Last modification time.
22    pub modified: SystemTime,
23    /// Last access time.
24    pub accessed: SystemTime,
25}
26
27impl Default for Metadata {
28    fn default() -> Self {
29        let now = SystemTime::now();
30        Self {
31            is_dir: false,
32            size: 0,
33            created: now,
34            modified: now,
35            accessed: now,
36        }
37    }
38}
39
40/// A directory entry returned by listing.
41#[derive(Debug, Clone)]
42pub struct DirEntry {
43    /// Name of the entry (not full path).
44    pub name: String,
45    /// Metadata for the entry.
46    pub metadata: Metadata,
47}
48
49/// Trait for VFS storage backends.
50///
51/// Implementations must be thread-safe and support async operations.
52/// All paths are absolute paths starting with `/`.
53#[async_trait]
54pub trait VfsStorage: Send + Sync {
55    /// Read file contents.
56    async fn read(&self, path: &str) -> VfsResult<Vec<u8>>;
57
58    /// Read a portion of file contents.
59    async fn read_at(&self, path: &str, offset: u64, len: u64) -> VfsResult<Vec<u8>>;
60
61    /// Write file contents (creates or overwrites).
62    async fn write(&self, path: &str, data: &[u8]) -> VfsResult<()>;
63
64    /// Write at a specific offset, extending the file if necessary.
65    async fn write_at(&self, path: &str, offset: u64, data: &[u8]) -> VfsResult<()>;
66
67    /// Truncate or extend file to the given size.
68    async fn set_size(&self, path: &str, size: u64) -> VfsResult<()>;
69
70    /// Delete a file.
71    async fn delete(&self, path: &str) -> VfsResult<()>;
72
73    /// Check if a path exists.
74    async fn exists(&self, path: &str) -> VfsResult<bool>;
75
76    /// List directory contents.
77    async fn list(&self, path: &str) -> VfsResult<Vec<DirEntry>>;
78
79    /// Get file/directory metadata.
80    async fn stat(&self, path: &str) -> VfsResult<Metadata>;
81
82    /// Create a directory.
83    async fn mkdir(&self, path: &str) -> VfsResult<()>;
84
85    /// Remove a directory (must be empty).
86    async fn rmdir(&self, path: &str) -> VfsResult<()>;
87
88    /// Rename/move a file or directory.
89    async fn rename(&self, from: &str, to: &str) -> VfsResult<()>;
90
91    /// Synchronously create a directory.
92    ///
93    /// This is useful for setup code that runs before the async runtime
94    /// is available. The default implementation returns an error - implementors
95    /// should override this if they support sync directory creation.
96    fn mkdir_sync(&self, _path: &str) -> VfsResult<()> {
97        Err(VfsError::Storage(
98            "mkdir_sync not implemented for this storage backend".to_string(),
99        ))
100    }
101}
102
103/// Type-erased VFS storage that wraps any [`VfsStorage`] implementation.
104///
105/// This avoids propagating generic type parameters through the entire
106/// executor stack while still allowing different storage backends
107/// (e.g., [`InMemoryStorage`], [`ScrubbingStorage`](crate::ScrubbingStorage))
108/// to be used interchangeably.
109///
110/// # Example
111///
112/// ```rust,ignore
113/// use eryx_vfs::{ArcStorage, InMemoryStorage};
114/// use std::sync::Arc;
115///
116/// // Wrap any VfsStorage in a ArcStorage
117/// let storage = ArcStorage::new(Arc::new(InMemoryStorage::new()));
118/// ```
119#[derive(Clone)]
120pub struct ArcStorage(Arc<dyn VfsStorage>);
121
122impl ArcStorage {
123    /// Wrap any `VfsStorage` implementation in a type-erased `ArcStorage`.
124    pub fn new(storage: Arc<dyn VfsStorage>) -> Self {
125        Self(storage)
126    }
127}
128
129impl std::fmt::Debug for ArcStorage {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_tuple("ArcStorage")
132            .field(&"<dyn VfsStorage>")
133            .finish()
134    }
135}
136
137#[async_trait]
138impl VfsStorage for ArcStorage {
139    async fn read(&self, path: &str) -> VfsResult<Vec<u8>> {
140        self.0.read(path).await
141    }
142
143    async fn read_at(&self, path: &str, offset: u64, len: u64) -> VfsResult<Vec<u8>> {
144        self.0.read_at(path, offset, len).await
145    }
146
147    async fn write(&self, path: &str, data: &[u8]) -> VfsResult<()> {
148        self.0.write(path, data).await
149    }
150
151    async fn write_at(&self, path: &str, offset: u64, data: &[u8]) -> VfsResult<()> {
152        self.0.write_at(path, offset, data).await
153    }
154
155    async fn set_size(&self, path: &str, size: u64) -> VfsResult<()> {
156        self.0.set_size(path, size).await
157    }
158
159    async fn delete(&self, path: &str) -> VfsResult<()> {
160        self.0.delete(path).await
161    }
162
163    async fn exists(&self, path: &str) -> VfsResult<bool> {
164        self.0.exists(path).await
165    }
166
167    async fn list(&self, path: &str) -> VfsResult<Vec<DirEntry>> {
168        self.0.list(path).await
169    }
170
171    async fn stat(&self, path: &str) -> VfsResult<Metadata> {
172        self.0.stat(path).await
173    }
174
175    async fn mkdir(&self, path: &str) -> VfsResult<()> {
176        self.0.mkdir(path).await
177    }
178
179    async fn rmdir(&self, path: &str) -> VfsResult<()> {
180        self.0.rmdir(path).await
181    }
182
183    async fn rename(&self, from: &str, to: &str) -> VfsResult<()> {
184        self.0.rename(from, to).await
185    }
186
187    fn mkdir_sync(&self, path: &str) -> VfsResult<()> {
188        self.0.mkdir_sync(path)
189    }
190}
191
192/// Internal state for in-memory storage.
193///
194/// Combining files and directories into a single struct allows us to use
195/// a single `RwLock`, avoiding potential deadlock issues from acquiring
196/// multiple locks.
197#[derive(Debug, Default)]
198struct StorageState {
199    /// File contents: path -> data
200    files: HashMap<String, FileData>,
201    /// Directory markers: set of directory paths
202    directories: HashSet<String>,
203}
204
205/// In-memory VFS storage implementation.
206///
207/// Stores files and directories in memory using `HashMap` and `HashSet`.
208/// Thread-safe via a single `RwLock` over the combined state.
209#[derive(Debug)]
210pub struct InMemoryStorage {
211    /// Combined state under a single lock to prevent deadlocks.
212    state: RwLock<StorageState>,
213}
214
215#[derive(Debug, Clone)]
216struct FileData {
217    content: Vec<u8>,
218    created: SystemTime,
219    modified: SystemTime,
220    accessed: SystemTime,
221}
222
223impl Default for InMemoryStorage {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229impl InMemoryStorage {
230    /// Create a new empty in-memory storage with root directory.
231    #[must_use]
232    pub fn new() -> Self {
233        let mut directories = HashSet::new();
234        directories.insert("/".to_string());
235        Self {
236            state: RwLock::new(StorageState {
237                files: HashMap::new(),
238                directories,
239            }),
240        }
241    }
242
243    /// Normalize a path (remove trailing slashes, handle . and ..).
244    fn normalize_path(path: &str) -> VfsResult<String> {
245        if !path.starts_with('/') {
246            return Err(VfsError::InvalidPath(format!(
247                "path must be absolute: {path}"
248            )));
249        }
250
251        let mut components: Vec<&str> = Vec::new();
252        for component in path.split('/') {
253            match component {
254                "" | "." => continue,
255                ".." => {
256                    if components.is_empty() {
257                        return Err(VfsError::InvalidPath("path escapes root".to_string()));
258                    }
259                    components.pop();
260                }
261                c => components.push(c),
262            }
263        }
264
265        if components.is_empty() {
266            Ok("/".to_string())
267        } else {
268            Ok(format!("/{}", components.join("/")))
269        }
270    }
271
272    /// Get the parent directory of a path.
273    fn parent_path(path: &str) -> Option<String> {
274        if path == "/" {
275            return None;
276        }
277        let normalized = Self::normalize_path(path).ok()?;
278        if normalized == "/" {
279            return None;
280        }
281        match normalized.rfind('/') {
282            Some(0) => Some("/".to_string()),
283            Some(idx) => Some(normalized[..idx].to_string()),
284            None => None,
285        }
286    }
287
288    /// Check if parent directory exists (requires state to already be borrowed).
289    fn check_parent_exists_with_state(state: &StorageState, path: &str) -> VfsResult<()> {
290        if let Some(parent) = Self::parent_path(path)
291            && !state.directories.contains(&parent)
292        {
293            return Err(VfsError::NotFound(format!("parent directory: {parent}")));
294        }
295        Ok(())
296    }
297}
298
299#[async_trait]
300impl VfsStorage for InMemoryStorage {
301    async fn read(&self, path: &str) -> VfsResult<Vec<u8>> {
302        let path = Self::normalize_path(path)?;
303        let state = self.state.read().await;
304        match state.files.get(&path) {
305            Some(data) => Ok(data.content.clone()),
306            None => {
307                if state.directories.contains(&path) {
308                    Err(VfsError::NotFile(path))
309                } else {
310                    Err(VfsError::NotFound(path))
311                }
312            }
313        }
314    }
315
316    async fn read_at(&self, path: &str, offset: u64, len: u64) -> VfsResult<Vec<u8>> {
317        let path = Self::normalize_path(path)?;
318        let state = self.state.read().await;
319        match state.files.get(&path) {
320            Some(data) => {
321                let offset = offset as usize;
322                let len = len as usize;
323                if offset >= data.content.len() {
324                    Ok(Vec::new())
325                } else {
326                    let end = (offset + len).min(data.content.len());
327                    Ok(data.content[offset..end].to_vec())
328                }
329            }
330            None => {
331                if state.directories.contains(&path) {
332                    Err(VfsError::NotFile(path))
333                } else {
334                    Err(VfsError::NotFound(path))
335                }
336            }
337        }
338    }
339
340    async fn write(&self, path: &str, data: &[u8]) -> VfsResult<()> {
341        let path = Self::normalize_path(path)?;
342        let mut state = self.state.write().await;
343
344        Self::check_parent_exists_with_state(&state, &path)?;
345
346        // Check it's not a directory
347        if state.directories.contains(&path) {
348            return Err(VfsError::NotFile(path));
349        }
350
351        let now = SystemTime::now();
352        let file_data = state.files.entry(path).or_insert_with(|| FileData {
353            content: Vec::new(),
354            created: now,
355            modified: now,
356            accessed: now,
357        });
358        file_data.content = data.to_vec();
359        file_data.modified = now;
360        Ok(())
361    }
362
363    async fn write_at(&self, path: &str, offset: u64, data: &[u8]) -> VfsResult<()> {
364        let path = Self::normalize_path(path)?;
365        let mut state = self.state.write().await;
366
367        Self::check_parent_exists_with_state(&state, &path)?;
368
369        // Check it's not a directory
370        if state.directories.contains(&path) {
371            return Err(VfsError::NotFile(path));
372        }
373
374        let now = SystemTime::now();
375        let offset = offset as usize;
376        let file_data = state.files.entry(path).or_insert_with(|| FileData {
377            content: Vec::new(),
378            created: now,
379            modified: now,
380            accessed: now,
381        });
382
383        // Extend file if necessary
384        let needed_len = offset + data.len();
385        if file_data.content.len() < needed_len {
386            file_data.content.resize(needed_len, 0);
387        }
388        file_data.content[offset..offset + data.len()].copy_from_slice(data);
389        file_data.modified = now;
390        Ok(())
391    }
392
393    async fn set_size(&self, path: &str, size: u64) -> VfsResult<()> {
394        let path = Self::normalize_path(path)?;
395        let now = SystemTime::now();
396        let mut state = self.state.write().await;
397        match state.files.get_mut(&path) {
398            Some(data) => {
399                data.content.resize(size as usize, 0);
400                data.modified = now;
401                Ok(())
402            }
403            None => Err(VfsError::NotFound(path)),
404        }
405    }
406
407    async fn delete(&self, path: &str) -> VfsResult<()> {
408        let path = Self::normalize_path(path)?;
409        let mut state = self.state.write().await;
410        if state.files.remove(&path).is_some() {
411            Ok(())
412        } else if state.directories.contains(&path) {
413            Err(VfsError::NotFile(path))
414        } else {
415            Err(VfsError::NotFound(path))
416        }
417    }
418
419    async fn exists(&self, path: &str) -> VfsResult<bool> {
420        let path = Self::normalize_path(path)?;
421        let state = self.state.read().await;
422        Ok(state.files.contains_key(&path) || state.directories.contains(&path))
423    }
424
425    async fn list(&self, path: &str) -> VfsResult<Vec<DirEntry>> {
426        let path = Self::normalize_path(path)?;
427        let state = self.state.read().await;
428
429        // Check it's a directory
430        if !state.directories.contains(&path) {
431            if state.files.contains_key(&path) {
432                return Err(VfsError::NotDirectory(path));
433            } else {
434                return Err(VfsError::NotFound(path));
435            }
436        }
437
438        let prefix = if path == "/" {
439            "/".to_string()
440        } else {
441            format!("{path}/")
442        };
443
444        let mut entries = Vec::new();
445        let mut seen_names = HashSet::new();
446
447        // List files
448        for (file_path, data) in &state.files {
449            if let Some(rest) = file_path.strip_prefix(&prefix) {
450                // Only include direct children (no more slashes)
451                if !rest.contains('/') && !rest.is_empty() {
452                    seen_names.insert(rest.to_string());
453                    entries.push(DirEntry {
454                        name: rest.to_string(),
455                        metadata: Metadata {
456                            is_dir: false,
457                            size: data.content.len() as u64,
458                            created: data.created,
459                            modified: data.modified,
460                            accessed: data.accessed,
461                        },
462                    });
463                }
464            }
465        }
466
467        // List subdirectories
468        for dir_path in &state.directories {
469            if let Some(rest) = dir_path.strip_prefix(&prefix) {
470                // Only include direct children
471                if !rest.contains('/') && !rest.is_empty() && !seen_names.contains(rest) {
472                    let now = SystemTime::now();
473                    entries.push(DirEntry {
474                        name: rest.to_string(),
475                        metadata: Metadata {
476                            is_dir: true,
477                            size: 0,
478                            created: now,
479                            modified: now,
480                            accessed: now,
481                        },
482                    });
483                }
484            }
485        }
486
487        entries.sort_by(|a, b| a.name.cmp(&b.name));
488        Ok(entries)
489    }
490
491    async fn stat(&self, path: &str) -> VfsResult<Metadata> {
492        let path = Self::normalize_path(path)?;
493        let state = self.state.read().await;
494
495        // Check files first
496        if let Some(data) = state.files.get(&path) {
497            return Ok(Metadata {
498                is_dir: false,
499                size: data.content.len() as u64,
500                created: data.created,
501                modified: data.modified,
502                accessed: data.accessed,
503            });
504        }
505
506        // Check directories
507        if state.directories.contains(&path) {
508            let now = SystemTime::now();
509            return Ok(Metadata {
510                is_dir: true,
511                size: 0,
512                created: now,
513                modified: now,
514                accessed: now,
515            });
516        }
517
518        Err(VfsError::NotFound(path))
519    }
520
521    async fn mkdir(&self, path: &str) -> VfsResult<()> {
522        let path = Self::normalize_path(path)?;
523        let mut state = self.state.write().await;
524
525        Self::check_parent_exists_with_state(&state, &path)?;
526
527        // Check if already exists
528        if state.files.contains_key(&path) {
529            return Err(VfsError::AlreadyExists(path));
530        }
531        if state.directories.contains(&path) {
532            return Err(VfsError::AlreadyExists(path));
533        }
534
535        state.directories.insert(path);
536        Ok(())
537    }
538
539    async fn rmdir(&self, path: &str) -> VfsResult<()> {
540        let path = Self::normalize_path(path)?;
541
542        if path == "/" {
543            return Err(VfsError::PermissionDenied(
544                "cannot remove root directory".to_string(),
545            ));
546        }
547
548        let mut state = self.state.write().await;
549
550        // Check if it's a directory
551        if !state.directories.contains(&path) {
552            if state.files.contains_key(&path) {
553                return Err(VfsError::NotDirectory(path));
554            } else {
555                return Err(VfsError::NotFound(path));
556            }
557        }
558
559        // Check if empty
560        let prefix = format!("{path}/");
561        for file_path in state.files.keys() {
562            if file_path.starts_with(&prefix) {
563                return Err(VfsError::DirectoryNotEmpty(path));
564            }
565        }
566        for dir_path in &state.directories {
567            if dir_path.starts_with(&prefix) {
568                return Err(VfsError::DirectoryNotEmpty(path));
569            }
570        }
571
572        state.directories.remove(&path);
573        Ok(())
574    }
575
576    async fn rename(&self, from: &str, to: &str) -> VfsResult<()> {
577        let from = Self::normalize_path(from)?;
578        let to = Self::normalize_path(to)?;
579
580        if from == to {
581            return Ok(());
582        }
583
584        let mut state = self.state.write().await;
585
586        Self::check_parent_exists_with_state(&state, &to)?;
587
588        // Handle file rename
589        if state.files.contains_key(&from) {
590            // Check destination doesn't exist as directory
591            if state.directories.contains(&to) {
592                return Err(VfsError::AlreadyExists(to));
593            }
594
595            if let Some(data) = state.files.remove(&from) {
596                state.files.insert(to, data);
597                return Ok(());
598            }
599        }
600
601        // Handle directory rename
602        if state.directories.contains(&from) {
603            // Check destination doesn't exist as file
604            if state.files.contains_key(&to) {
605                return Err(VfsError::AlreadyExists(to));
606            }
607
608            // Rename directory and all contents
609            let from_prefix = format!("{from}/");
610            let to_prefix = format!("{to}/");
611
612            // Rename files under the directory
613            let files_to_rename: Vec<_> = state
614                .files
615                .keys()
616                .filter(|p| p.starts_with(&from_prefix))
617                .cloned()
618                .collect();
619            for old_path in files_to_rename {
620                if let Some(data) = state.files.remove(&old_path) {
621                    let new_path = old_path.replacen(&from_prefix, &to_prefix, 1);
622                    state.files.insert(new_path, data);
623                }
624            }
625
626            // Rename subdirectories
627            let dirs_to_rename: Vec<_> = state
628                .directories
629                .iter()
630                .filter(|p| *p == &from || p.starts_with(&from_prefix))
631                .cloned()
632                .collect();
633            for old_path in dirs_to_rename {
634                state.directories.remove(&old_path);
635                let new_path = if old_path == from {
636                    to.clone()
637                } else {
638                    old_path.replacen(&from_prefix, &to_prefix, 1)
639                };
640                state.directories.insert(new_path);
641            }
642
643            return Ok(());
644        }
645
646        Err(VfsError::NotFound(from))
647    }
648
649    fn mkdir_sync(&self, path: &str) -> VfsResult<()> {
650        let path = Self::normalize_path(path)?;
651
652        // Collect all directories to create first.
653        let mut dirs_to_create = Vec::new();
654        let mut current = String::new();
655        for component in path.split('/').filter(|s| !s.is_empty()) {
656            current = format!("{}/{}", current, component);
657            dirs_to_create.push(current.clone());
658        }
659
660        // Try to acquire the lock without blocking first.
661        // This handles the common case where no contention exists.
662        if let Ok(mut state) = self.state.try_write() {
663            for dir in dirs_to_create {
664                state.directories.insert(dir);
665            }
666            return Ok(());
667        }
668
669        // If try_write fails, we need to use a blocking approach.
670        // Check if we're in an async runtime context.
671        if let Ok(handle) = tokio::runtime::Handle::try_current() {
672            // We're in an async runtime - use block_on with async write.
673            // This is safe because mkdir_sync is only called during setup,
674            // before the store starts processing async WASM calls.
675            handle.block_on(async {
676                let mut state = self.state.write().await;
677                for dir in dirs_to_create {
678                    state.directories.insert(dir);
679                }
680            });
681        } else {
682            // Not in an async runtime - safe to use blocking_write.
683            let mut state = self.state.blocking_write();
684            for dir in dirs_to_create {
685                state.directories.insert(dir);
686            }
687        }
688
689        Ok(())
690    }
691}
692
693#[cfg(test)]
694#[allow(clippy::unwrap_used)]
695mod tests {
696    use super::*;
697
698    #[tokio::test]
699    async fn test_file_operations() {
700        let storage = InMemoryStorage::new();
701
702        // Write and read
703        storage.write("/test.txt", b"hello").await.unwrap();
704        let content = storage.read("/test.txt").await.unwrap();
705        assert_eq!(content, b"hello");
706
707        // Read at offset
708        let partial = storage.read_at("/test.txt", 2, 3).await.unwrap();
709        assert_eq!(partial, b"llo");
710
711        // Overwrite
712        storage.write("/test.txt", b"world").await.unwrap();
713        let content = storage.read("/test.txt").await.unwrap();
714        assert_eq!(content, b"world");
715
716        // Delete
717        storage.delete("/test.txt").await.unwrap();
718        assert!(storage.read("/test.txt").await.is_err());
719    }
720
721    #[tokio::test]
722    async fn test_directory_operations() {
723        let storage = InMemoryStorage::new();
724
725        // Create directory
726        storage.mkdir("/subdir").await.unwrap();
727        assert!(storage.exists("/subdir").await.unwrap());
728
729        // Create file in directory
730        storage.write("/subdir/file.txt", b"content").await.unwrap();
731
732        // List directory
733        let entries = storage.list("/subdir").await.unwrap();
734        assert_eq!(entries.len(), 1);
735        assert_eq!(entries[0].name, "file.txt");
736
737        // Can't remove non-empty directory
738        assert!(storage.rmdir("/subdir").await.is_err());
739
740        // Remove file, then directory
741        storage.delete("/subdir/file.txt").await.unwrap();
742        storage.rmdir("/subdir").await.unwrap();
743        assert!(!storage.exists("/subdir").await.unwrap());
744    }
745
746    #[tokio::test]
747    async fn test_path_normalization() {
748        let storage = InMemoryStorage::new();
749
750        storage.write("/test.txt", b"data").await.unwrap();
751
752        // Various path formats should work
753        assert!(storage.exists("/test.txt").await.unwrap());
754        assert!(storage.exists("/./test.txt").await.unwrap());
755
756        // Parent references
757        storage.mkdir("/dir").await.unwrap();
758        storage.write("/dir/file.txt", b"data").await.unwrap();
759        let content = storage.read("/dir/../dir/file.txt").await.unwrap();
760        assert_eq!(content, b"data");
761    }
762
763    #[tokio::test]
764    async fn test_rename() {
765        let storage = InMemoryStorage::new();
766
767        // File rename
768        storage.write("/old.txt", b"content").await.unwrap();
769        storage.rename("/old.txt", "/new.txt").await.unwrap();
770        assert!(!storage.exists("/old.txt").await.unwrap());
771        assert!(storage.exists("/new.txt").await.unwrap());
772
773        // Directory rename
774        storage.mkdir("/olddir").await.unwrap();
775        storage.write("/olddir/file.txt", b"data").await.unwrap();
776        storage.rename("/olddir", "/newdir").await.unwrap();
777        assert!(!storage.exists("/olddir").await.unwrap());
778        assert!(storage.exists("/newdir").await.unwrap());
779        assert!(storage.exists("/newdir/file.txt").await.unwrap());
780    }
781
782    #[tokio::test]
783    async fn test_stat() {
784        let storage = InMemoryStorage::new();
785
786        storage.write("/file.txt", b"hello").await.unwrap();
787        let meta = storage.stat("/file.txt").await.unwrap();
788        assert!(!meta.is_dir);
789        assert_eq!(meta.size, 5);
790
791        storage.mkdir("/dir").await.unwrap();
792        let meta = storage.stat("/dir").await.unwrap();
793        assert!(meta.is_dir);
794    }
795
796    #[tokio::test]
797    async fn test_write_at() {
798        let storage = InMemoryStorage::new();
799
800        // Write at offset in new file
801        storage.write_at("/file.txt", 5, b"world").await.unwrap();
802        let content = storage.read("/file.txt").await.unwrap();
803        assert_eq!(content.len(), 10);
804        assert_eq!(&content[5..], b"world");
805        assert_eq!(&content[0..5], &[0, 0, 0, 0, 0]);
806
807        // Overwrite portion
808        storage.write_at("/file.txt", 0, b"hello").await.unwrap();
809        let content = storage.read("/file.txt").await.unwrap();
810        assert_eq!(&content, b"helloworld");
811    }
812
813    #[test]
814    fn test_mkdir_sync() {
815        let storage = InMemoryStorage::new();
816
817        // Create a directory synchronously
818        storage.mkdir_sync("/data").unwrap();
819
820        // Verify using blocking read
821        let state = storage.state.blocking_read();
822        assert!(state.directories.contains("/data"));
823    }
824
825    #[test]
826    fn test_mkdir_sync_nested() {
827        let storage = InMemoryStorage::new();
828
829        // Create nested directories synchronously
830        storage.mkdir_sync("/data/subdir/nested").unwrap();
831
832        // Verify all intermediate directories were created
833        let state = storage.state.blocking_read();
834        assert!(state.directories.contains("/data"));
835        assert!(state.directories.contains("/data/subdir"));
836        assert!(state.directories.contains("/data/subdir/nested"));
837    }
838}