Skip to content

Add random file access support #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! The filesystem trait definitions needed to implement new virtual filesystems

use crate::error::VfsErrorKind;
use crate::{SeekAndRead, VfsMetadata, VfsPath, VfsResult};
use crate::{SeekAndRead, SeekAndReadAndWrite, VfsMetadata, VfsPath, VfsResult};
use std::fmt::Debug;
use std::io::Write;

Expand Down Expand Up @@ -46,6 +46,28 @@ pub trait FileSystem: Debug + Sync + Send + 'static {
fn move_dir(&self, _src: &str, _dest: &str) -> VfsResult<()> {
Err(VfsErrorKind::NotSupported.into())
}

/// This obtains the potential size of the current path in the filesystem.
///
/// This, by default, queries the current size.
fn size_hint(&self, path: &str) -> VfsResult<u64> {
self.metadata(path).map(|f| f.len)
}

/// Informs the filesystem to 'flush' its potentially cached information.
fn sync(&self, path: &str) -> VfsResult<()>;

/// Set a size hint for the associated path.
///
/// This is, by default, a no-op.
fn set_size_hint(&self, _hint: usize, _path: &str) -> VfsResult<()> {
Ok(())
}

/// Opens the file at this path for reading and writing
fn update_file(&self, _path: &str) -> VfsResult<Box<dyn SeekAndReadAndWrite>> {
Err(VfsErrorKind::NotSupported.into())
}
}

impl<T: FileSystem> From<T> for VfsPath {
Expand Down
8 changes: 8 additions & 0 deletions src/impls/altroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ impl FileSystem for AltrootFS {
}
self.path(src)?.copy_file(&self.path(dest)?)
}

fn update_file(&self, path: &str) -> VfsResult<Box<dyn crate::SeekAndReadAndWrite>> {
self.path(path)?.update_file()
}

fn sync(&self, path: &str) -> VfsResult<()> {
self.path(path)?.sync()
}
}

#[cfg(test)]
Expand Down
7 changes: 6 additions & 1 deletion src/impls/embedded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::marker::PhantomData;
use rust_embed::RustEmbed;

use crate::error::VfsErrorKind;
use crate::{FileSystem, SeekAndRead, VfsFileType, VfsMetadata, VfsResult};
use crate::{FileSystem, SeekAndRead, VfsFileType, VfsMetadata, VfsResult, VfsAccess};

type EmbeddedPath = Cow<'static, str>;

Expand Down Expand Up @@ -120,12 +120,14 @@ where
return Ok(VfsMetadata {
file_type: VfsFileType::File,
len: *len,
access: HashSet::from([VfsAccess::Read])
});
}
if self.directory_map.contains_key(normalized_path) {
return Ok(VfsMetadata {
file_type: VfsFileType::Directory,
len: 0,
access: HashSet::from([VfsAccess::Read])
});
}
Err(VfsErrorKind::FileNotFound.into())
Expand Down Expand Up @@ -153,6 +155,9 @@ where
fn remove_dir(&self, _path: &str) -> VfsResult<()> {
Err(VfsErrorKind::NotSupported.into())
}
fn sync(&self, _path: &str) -> VfsResult<()> {
Ok(())
}
}

fn normalize_path(path: &str) -> VfsResult<&str> {
Expand Down
80 changes: 75 additions & 5 deletions src/impls/memory.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
//! An ephemeral in-memory file system, intended mainly for unit tests

use crate::error::VfsErrorKind;
use crate::VfsResult;
use crate::{FileSystem, VfsFileType};
use crate::{SeekAndRead, VfsMetadata};
use crate::{VfsResult, FileSystem, SeekAndRead, VfsFileType, VfsMetadata, VfsAccess};
use core::cmp;
use std::collections::HashMap;
use std::collections::{HashSet, HashMap};
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::mem::swap;
use std::sync::{Arc, RwLock};


type MemoryFsHandle = Arc<RwLock<MemoryFsImpl>>;

/// An ephemeral in-memory file system, intended mainly for unit tests
Expand Down Expand Up @@ -119,6 +118,56 @@ impl Seek for ReadableFile {
}
}

struct RandomAccessFile {
content: Cursor<Vec<u8>>,
destination: String,
fs: MemoryFsHandle,
}

impl RandomAccessFile {
fn from_file(value: Arc<Vec<u8>>, destination: String, fs: MemoryFsHandle) -> Self {
Self {
content: Cursor::new(value.to_vec()),
destination,
fs,
}
}
}

impl Write for RandomAccessFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.content.write(buf)
}

fn flush(&mut self) -> std::io::Result<()> {
self.content.flush()
}
}

impl Read for RandomAccessFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.content.read(buf)
}
}
impl Seek for RandomAccessFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.content.seek(pos)
}
}
impl Drop for RandomAccessFile {
fn drop(&mut self) {
let mut content = vec![];
swap(&mut content, self.content.get_mut());
self.fs.write().unwrap().files.insert(
self.destination.clone(),
MemoryFile {
file_type: VfsFileType::File,
content: Arc::new(content),
},
);
}
}

impl FileSystem for MemoryFS {
fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String>>> {
let prefix = format!("{}/", path);
Expand Down Expand Up @@ -195,18 +244,35 @@ impl FileSystem for MemoryFS {
let writer = WritableFile {
content,
destination: path.to_string(),
fs: self.handle.clone(),
fs: Arc::clone(&self.handle),
};
Ok(Box::new(writer))
}

fn update_file(&self, path: &str) -> VfsResult<Box<dyn crate::SeekAndReadAndWrite>> {
let handle = self.handle.read().unwrap();
let file = handle
.files
.get(path)
.ok_or_else(|| VfsErrorKind::FileNotFound)?;
ensure_file(file)?;

Ok(Box::new(RandomAccessFile::from_file(
Arc::clone(&file.content),
path.to_string(),
Arc::clone(&self.handle),
)))
}

fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
let guard = self.handle.read().unwrap();
let files = &guard.files;
let file = files.get(path).ok_or(VfsErrorKind::FileNotFound)?;

Ok(VfsMetadata {
file_type: file.file_type,
len: file.content.len() as u64,
access: HashSet::from([VfsAccess::Read, VfsAccess::Write]),
})
}

Expand Down Expand Up @@ -234,6 +300,10 @@ impl FileSystem for MemoryFS {
.ok_or(VfsErrorKind::FileNotFound)?;
Ok(())
}

fn sync(&self, _path: &str) -> VfsResult<()> {
Ok(())
}
}

struct MemoryFsImpl {
Expand Down
12 changes: 12 additions & 0 deletions src/impls/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ impl OverlayFS {
&self.layers[0]
}

fn sync_path(&self,path:&str) -> VfsResult<()>{
self.read_path(path).and_then(|p| p.sync())
}

fn read_path(&self, path: &str) -> VfsResult<VfsPath> {
if path.is_empty() {
return Ok(self.layers[0].clone());
Expand Down Expand Up @@ -121,6 +125,10 @@ impl FileSystem for OverlayFS {
self.read_path(path)?.open_file()
}

fn update_file(&self, path: &str) -> VfsResult<Box<dyn crate::SeekAndReadAndWrite>> {
self.read_path(path)?.update_file()
}

fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
self.ensure_has_parent(path)?;
let result = self.write_path(path)?.create_file()?;
Expand Down Expand Up @@ -182,6 +190,10 @@ impl FileSystem for OverlayFS {
whiteout_path.create_file()?;
Ok(())
}

fn sync(&self, path: &str) -> VfsResult<()> {
self.sync_path(path)
}
}

#[cfg(test)]
Expand Down
31 changes: 27 additions & 4 deletions src/impls/physical.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! A "physical" file system implementation using the underlying OS file system

use crate::error::VfsErrorKind;
use crate::VfsResult;
use crate::{FileSystem, VfsMetadata};
use crate::{SeekAndRead, VfsFileType};
use crate::{SeekAndRead, VfsFileType, VfsResult, VfsAccess, FileSystem, VfsMetadata};
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};


/// A physical filesystem implementation using the underlying OS file system
#[derive(Debug)]
pub struct PhysicalFS {
Expand Down Expand Up @@ -63,16 +63,26 @@ impl FileSystem for PhysicalFS {
}

fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
let metadata = self.get_path(path).metadata()?;
let pb = self.get_path(path);
let metadata = pb.metadata()?;
let mut access = HashSet::new();
access.insert(VfsAccess::Read);

if !metadata.permissions().readonly() {
access.insert(VfsAccess::Write);
}

Ok(if metadata.is_dir() {
VfsMetadata {
file_type: VfsFileType::Directory,
len: 0,
access,
}
} else {
VfsMetadata {
file_type: VfsFileType::File,
len: metadata.len(),
access,
}
})
}
Expand Down Expand Up @@ -110,6 +120,19 @@ impl FileSystem for PhysicalFS {
}
Ok(())
}

fn update_file(&self, path: &str) -> VfsResult<Box<dyn crate::SeekAndReadAndWrite>> {
Ok(Box::new(
OpenOptions::new()
.write(true)
.read(true)
.open(self.get_path(path))?,
))
}

fn sync(&self, _path: &str) -> VfsResult<()> {
Ok(())
}
}

#[cfg(test)]
Expand Down
Loading