use std::{
collections::HashMap,
ffi::OsString,
io::{Error, ErrorKind, SeekFrom},
mem,
path::{Component, Path, PathBuf},
};
use crate::{
dir_entry::DirEntry,
file::{File, FileInner},
metadata::{FileType, Metadata},
open_options::OpenOptions,
virtual_diskit::VirtualDiskit,
walkdir::{WalkDir, WalkdirIterator, WalkdirIteratorInner},
};
macro_rules! try_nested {
($val: expr, $self: expr, $inner: expr) => {
loop
{
let error;
match $val
{
Ok(x) => break x,
Err(err) =>
{
error = err;
}
}
$self.walkdirs[$inner.val].fused = true;
return Some(Err(error));
}
};
}
#[cfg_attr(not(feature = "trash"), allow(dead_code))]
#[derive(Debug, PartialEq, Eq)]
pub enum InodeInner
{
File(Vec<u8>),
Dir(HashMap<OsString, usize>),
Empty,
}
#[derive(Debug)]
pub struct Inode
{
pub inner: InodeInner,
pub id: usize,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct OpenedFile
{
pub inode_id: usize,
pub pos: usize,
pub read: bool,
pub write: bool,
pub append: bool,
pub closed: bool,
}
#[derive(Debug)]
pub struct WalkingDir
{
pub pos: Vec<usize>,
pub fused: bool,
}
#[derive(Debug)]
pub struct VirtualDiskitInner
{
pub content: Vec<Inode>,
pub files: Vec<OpenedFile>,
pub walkdirs: Vec<WalkingDir>,
pub pwd: PathBuf,
}
impl VirtualDiskitInner
{
pub fn new() -> Self
{
Self {
content: vec![Inode {
inner: InodeInner::Dir(
[(OsString::from("."), 0), (OsString::from(".."), 0)].into(),
),
id: 0,
}],
files: vec![],
walkdirs: vec![],
pwd: PathBuf::from("/"),
}
}
pub fn open_with_options(
&mut self,
path: &Path,
options: OpenOptions,
diskit: &VirtualDiskit,
) -> Result<File<VirtualDiskit>, Error>
{
if (options.truncate && !options.write)
|| ((options.create || options.create_new) && !(options.write || options.append))
{
return Err(From::from(ErrorKind::InvalidInput));
}
let inode = self.get_inode_by_full_path(path)?;
if let Err(inode) = inode
{
if !(options.create || options.create_new)
{
return Err(From::from(ErrorKind::NotFound));
}
let len = self.content.len();
match &mut self.get_mut_inode_by_id(inode.id)?.inner
{
InodeInner::Dir(dir) =>
{
dir.insert(
path.file_name().ok_or(ErrorKind::InvalidInput)?.to_owned(),
len,
)
.ok_or(())
.expect_err("The directory shouldn't have this file already");
self.files.push(OpenedFile {
inode_id: len,
pos: 0,
read: options.read,
write: options.write,
append: options.append,
closed: false,
});
self.content.push(Inode {
inner: InodeInner::File(vec![]),
id: self.content.len(),
});
Ok(File {
inner: FileInner {
file: None,
val: self.files.len() - 1,
},
diskit: diskit.clone(),
})
}
InodeInner::File(_) => Err(From::from(ErrorKind::NotADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
else if options.create_new
{
Err(From::from(ErrorKind::AlreadyExists))
}
else
{
let inode_id = inode.unwrap().id;
let inode = self.get_mut_inode_by_id(inode_id)?;
match &mut inode.inner
{
InodeInner::File(file) =>
{
if options.truncate
{
file.truncate(0);
}
let pos = if options.append { file.len() } else { 0 };
self.files.push(OpenedFile {
inode_id,
pos,
read: options.read,
write: options.write,
append: options.append,
closed: false,
});
Ok(File {
inner: FileInner {
file: None,
val: self.files.len() - 1,
},
diskit: diskit.clone(),
})
}
InodeInner::Dir(_) => Err(From::from(ErrorKind::IsADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
}
pub fn read(&mut self, file: &FileInner, buf: &mut [u8]) -> Result<usize, Error>
{
let opened_file = &self.files[file.val];
debug_assert!(!opened_file.closed, "Attempted to use closed file");
if !opened_file.read
{
return Err(From::from(ErrorKind::PermissionDenied));
}
let inode = &self.get_inode_by_id(opened_file.inode_id)?.inner;
let pos = opened_file.pos;
match inode
{
InodeInner::File(content) =>
{
if pos >= content.len()
{
return Ok(0);
}
let mut amount = content.len() - pos;
if amount > buf.len()
{
amount = buf.len();
}
buf[0..amount].copy_from_slice(&content[pos..(pos + amount)]);
self.files[file.val].pos += amount;
Ok(amount)
}
InodeInner::Dir(_) => Err(From::from(ErrorKind::IsADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
pub fn read_to_end(&mut self, file: &mut FileInner, buf: &mut Vec<u8>) -> Result<usize, Error>
{
let opened_file = &self.files[file.val];
debug_assert!(!opened_file.closed, "Attempted to use closed file");
if !opened_file.read
{
return Err(From::from(ErrorKind::PermissionDenied));
}
let inode = &self.get_inode_by_id(opened_file.inode_id)?.inner;
let pos = opened_file.pos;
match inode
{
InodeInner::File(content) =>
{
if pos >= content.len()
{
return Ok(0);
}
buf.extend_from_slice(&content[pos..]);
let amount = content.len() - pos;
self.files[file.val].pos += content.len();
Ok(amount)
}
InodeInner::Dir(_) => Err(From::from(ErrorKind::IsADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
pub fn read_to_string(&mut self, file: &mut FileInner, buf: &mut String)
-> Result<usize, Error>
{
let mut vec = vec![];
let amount = self.read_to_end(file, &mut vec)?;
buf.push_str(std::str::from_utf8(&vec).map_err(|_| ErrorKind::InvalidData)?);
Ok(amount)
}
pub fn write(&mut self, file: &mut FileInner, buf: &[u8]) -> Result<usize, Error>
{
self.write_all(file, buf)?;
Ok(buf.len())
}
pub fn write_all(&mut self, file: &mut FileInner, buf: &[u8]) -> Result<(), Error>
{
let opened_file = &self.files[file.val];
let mut pos = opened_file.pos;
let append = opened_file.append;
debug_assert!(!opened_file.closed, "Attempted to use closed file");
if !(opened_file.write || opened_file.append)
{
return Err(From::from(ErrorKind::PermissionDenied));
}
let inode = &mut self.get_mut_inode_by_id(opened_file.inode_id)?.inner;
match inode
{
InodeInner::File(content) =>
{
if append
{
pos = content.len();
}
if pos + buf.len() > content.len()
{
content.append(&mut vec![0; pos + buf.len() - content.len()]);
}
content[pos..(pos + buf.len())].copy_from_slice(buf);
self.files[file.val].pos = pos + buf.len();
Ok(())
}
InodeInner::Dir(_) => Err(From::from(ErrorKind::IsADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
pub fn metadata_inner(&self, file: &FileInner) -> Result<Metadata, Error>
{
match &self.get_inode_by_id(self.files[file.val].inode_id)?.inner
{
InodeInner::File(content) => Ok(Metadata {
file_type: FileType::File,
len: content.len() as _,
}),
InodeInner::Dir(_) => Ok(Metadata {
file_type: FileType::Dir,
len: 0,
}),
InodeInner::Empty => panic!("Empty inode found"),
}
}
pub fn seek(&mut self, file: &mut FileInner, pos: SeekFrom) -> Result<u64, Error>
{
let len = self.metadata_inner(file)?.len;
let file = &mut self.files[file.val];
debug_assert!(!file.closed, "Attempted to use closed file");
match pos
{
SeekFrom::Start(x) => file.pos = x as _,
SeekFrom::End(x) =>
{
file.pos = ((len as i64) + x) as _;
}
SeekFrom::Current(x) => file.pos = ((file.pos as i64) + x) as _,
}
Ok(file.pos as _)
}
pub fn create_dir(&mut self, path: &Path) -> Result<(), Error>
{
let filename = if let Component::Normal(filename) =
path.components().last().ok_or(ErrorKind::InvalidInput)?
{
filename
}
else
{
return Err(From::from(ErrorKind::InvalidInput));
};
let old_content_len = self.content.len();
let inode = self.get_mut_inode_by_id(
self.get_inode_by_full_path(path)?
.err()
.ok_or(ErrorKind::AlreadyExists)?
.id,
)?;
let old_inode_id = inode.id;
match &mut inode.inner
{
InodeInner::Dir(dir) =>
{
dir.insert(filename.to_owned(), old_content_len)
.ok_or(())
.expect_err("The directory shouldn't have this file already");
self.content.push(Inode {
inner: InodeInner::Dir(
[
(OsString::from("."), old_content_len),
(OsString::from(".."), old_inode_id),
]
.into(),
),
id: old_content_len,
});
Ok(())
}
InodeInner::File(_) => Err(From::from(ErrorKind::NotADirectory)),
InodeInner::Empty => panic!("Empty inode found"),
}
}
pub fn create_dir_all(&mut self, path: &Path) -> Result<(), Error>
{
let mut full_path = PathBuf::from(".");
for component in path.components()
{
full_path.push(component);
self.create_dir(&full_path).or_else(|err| {
if err.kind() == ErrorKind::AlreadyExists
{
Ok(())
}
else
{
Err(err)
}
})?;
}
Ok(())
}
pub fn close(&mut self, file: &mut FileInner)
{
let files = &mut self.files;
files[file.val].closed = true;
while !files.is_empty() && files[files.len() - 1].closed
{
files.pop();
}
}
#[allow(clippy::wrong_self_convention)]
pub fn into_walkdir_iterator(
&mut self,
walkdir: WalkDir<VirtualDiskit>,
) -> WalkdirIterator<VirtualDiskit>
{
let pos = match self
.get_first_walkdir_pos(&walkdir.options.path, walkdir.options.contents_first)
{
Ok(pos) => pos,
Err(err) => return WalkdirIterator { inner: Err(err) },
};
self.walkdirs.push(WalkingDir { pos, fused: false });
WalkdirIterator {
inner: Ok((
WalkdirIteratorInner {
walkdir: None,
val: self.walkdirs.len() - 1,
original: walkdir.options,
},
walkdir.diskit,
)),
}
}
#[allow(clippy::too_many_lines)]
fn walkdir_next_helper(
&mut self,
inner: &mut WalkdirIteratorInner,
) -> Option<Result<DirEntry, Error>>
{
let walkdir = &self.walkdirs[inner.val];
if walkdir.fused
{
return None;
}
let root_inode = try_nested!(
try_nested!(
self.get_inode_by_full_path(&inner.original.path),
self,
inner
)
.map_err(|_| ErrorKind::NotFound.into()),
self,
inner
);
let root_dir = if let InodeInner::Dir(dir) = &root_inode.inner
{
dir
}
else
{
self.walkdirs[inner.val].fused = true;
return Some(Err(ErrorKind::NotADirectory.into()));
};
if walkdir.pos.is_empty()
{
let ino = root_inode.id as _;
if inner.original.contents_first
{
let len = root_dir.len();
self.walkdirs[inner.val].pos.push(len);
}
else
{
self.walkdirs[inner.val].pos.push(0);
}
return Some(Ok(DirEntry {
path: inner.original.path.clone(),
metadata: Metadata {
file_type: FileType::Dir,
len: 0,
},
follow_link: false,
depth: 0,
ino,
}));
}
let mut inode_path = vec![0];
let mut path = inner.original.path.clone();
let mut inode = root_inode;
for index in &walkdir.pos
{
let (content_path, &content_inode) =
try_nested!(Self::get_dir_as_iterator(inode), self, inner).nth(*index)?;
path.push(content_path);
inode_path.push(content_inode);
inode = try_nested!(self.get_inode_by_id(content_inode), self, inner);
}
let metadata = match &inode.inner
{
InodeInner::File(file) => Metadata {
file_type: FileType::File,
len: file.len() as _,
},
InodeInner::Dir(_) => Metadata {
file_type: FileType::Dir,
len: 0,
},
InodeInner::Empty => panic!("Empty inode found"),
};
let rv = DirEntry {
path,
metadata,
follow_link: false,
depth: walkdir.pos.len(),
ino: inode.id as _,
};
let mut pos = self.walkdirs[inner.val].pos.clone();
if inner.original.contents_first
{
let len = pos.len();
pos[len - 1] += 1;
while self.check_pos_path(root_inode, &pos).is_some()
{
pos.push(0);
}
pos.pop();
}
else
{
pos.push(0);
if self.check_pos_path(root_inode, &pos).is_none()
{
pos.pop();
loop
{
let len = pos.len();
if len == 0
{
self.walkdirs[inner.val].fused = true;
break;
}
pos[len - 1] += 1;
if self.check_pos_path(root_inode, &pos).is_none()
{
pos.pop();
continue;
}
break;
}
}
}
drop(mem::replace(&mut self.walkdirs[inner.val].pos, pos));
Some(Ok(rv))
}
pub fn walkdir_next_inner(
&mut self,
inner: &mut WalkdirIteratorInner,
) -> Option<Result<DirEntry, Error>>
{
let options = inner.original.clone();
loop
{
match self.walkdir_next_helper(inner)
{
Some(Ok(dir_entry)) =>
{
if dir_entry.depth() >= options.min_depth
&& dir_entry.depth() <= options.max_depth
{
return Some(Ok(dir_entry));
}
}
other => return other,
}
}
}
#[cfg(feature = "trash")]
pub fn trash_delete(&mut self, path: &Path) -> Result<(), Error>
{
fn get_parent_dir<'a>(
self_: &'a mut VirtualDiskitInner,
parent_path: &Path,
) -> Result<&'a mut HashMap<OsString, usize>, Error>
{
VirtualDiskitInner::get_mut_dir_from_inode(
self_.get_mut_inode_by_full_path(parent_path)?,
)
}
let path = self.pwd.join(path);
let parent_path = path.parent().ok_or(ErrorKind::PermissionDenied)?.to_owned();
if path.ends_with("..") || path.ends_with(".")
{
return self.trash_delete(&parent_path);
}
let file_inode_id = *get_parent_dir(self, &parent_path)?
.get(path.file_name().expect("Path should have a file name"))
.ok_or(ErrorKind::NotFound)?;
let mut all_inodes = vec![file_inode_id];
if self.get_dir_by_id(file_inode_id).is_ok()
{
self.collect_inodes(self.get_inode_by_id(file_inode_id)?, &mut all_inodes)?;
}
if all_inodes
.iter()
.any(|&id| self.files.iter().any(|file| id == file.inode_id))
{
return Err(ErrorKind::ResourceBusy.into());
}
get_parent_dir(self, &parent_path)?
.remove(path.file_name().expect("Path should have a file name"))
.expect("Value was proven to exist, but doesn't");
for inode_id in all_inodes
{
drop(mem::replace(
self.get_mut_inode_by_id(inode_id)?,
Inode {
inner: InodeInner::Empty,
id: inode_id,
},
));
}
while !self.content.is_empty()
&& self.content[self.content.len() - 1].inner == InodeInner::Empty
{
self.content.pop();
}
Ok(())
}
}