extern crate parking_lot;
use self::parking_lot::{Mutex, RwLock};
use std::cmp::{self, Ordering};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::ffi::OsString;
use std::fmt;
use std::io::{self, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;
use std::vec::IntoIter;
use fs::{self, DirBuilder as _DirBuilder, FileType as _FileType, Metadata as _Metadata};
use unix_ext;
use errors::*;
use path_parts::{normalize, IteratorExt, Part, Parts};
use ptr::Raw;
const DIRLEN: usize = 4096;
#[derive(Clone, Debug)]
pub struct DirBuilder {
fs: FS,
recursive: bool,
mode: u32,
}
impl fs::DirBuilder for DirBuilder {
fn recursive(&mut self, recursive: bool) -> &mut Self {
self.recursive = recursive; self
}
fn create<P: AsRef<Path>>(&self, path: P) -> Result<()> {
if self.recursive {
self.fs.0.lock().create_dir_all(path, self.mode)
} else {
self.fs.0.lock().create_dir(path, self.mode, false)
}
}
}
impl unix_ext::DirBuilderExt for DirBuilder {
fn mode(&mut self, mode: u32) -> &mut Self {
self.mode = mode; self
}
}
#[derive(Debug)]
pub struct DirEntry {
dir: PathBuf,
base: OsString,
inode: Inode,
}
impl fs::DirEntry for DirEntry {
type Metadata = Metadata;
type FileType = FileType;
fn path(&self) -> PathBuf {
self.dir.join(self.base.clone())
}
fn metadata(&self) -> Result<Self::Metadata> {
Ok(Metadata(*self.inode.read()))
}
fn file_type(&self) -> Result<Self::FileType> {
Ok(self.inode.read().ftyp)
}
fn file_name(&self) -> OsString {
self.base.clone()
}
}
#[derive(Debug)]
struct RawFile {
data: Vec<u8>,
inode: Inode,
}
impl RawFile {
fn read_at(&self, at: usize, dst: &mut [u8]) -> Result<usize> {
self.inode.write().times.update(ACCESSED);
let data = &self.data;
if at > data.len() {
return Ok(0);
}
let unread = &data[at..];
let copy_size = cmp::min(dst.len(), unread.len());
dst[..copy_size].copy_from_slice(&unread[..copy_size]);
Ok(copy_size)
}
fn write_at(&mut self, at: usize, src: &[u8]) -> Result<usize> {
if src.is_empty() {
return Ok(0)
}
let mut dst = &mut self.data;
if at > dst.len() {
let mut new = vec![0; at + src.len()];
new[..dst.len()].copy_from_slice(dst);
*dst = new;
}
let new_end = src.len() + at;
if dst.len() >= new_end {
dst[at..new_end].copy_from_slice(src);
} else {
dst.truncate(at);
dst.extend_from_slice(src);
}
let mut inode = self.inode.write();
inode.times.update(MODIFIED);
inode.length = dst.len();
Ok(src.len())
}
}
#[derive(Debug)]
pub struct File {
read: bool,
write: bool,
append: bool,
cursor: Arc<Mutex<FileCursor>>,
}
impl fs::File for File {
type Metadata = Metadata;
type Permissions = Permissions;
fn sync_all(&self) -> Result<()> {
Ok(())
}
fn sync_data(&self) -> Result<()> {
Ok(())
}
fn set_len(&self, size: u64) -> Result<()> {
if !self.write {
return Err(EINVAL());
}
Ok(self.cursor.lock().set_len(size)?)
}
fn metadata(&self) -> Result<Self::Metadata> {
let cursor = self.cursor.lock();
let file = cursor.file.read();
let meta = Metadata(*file.inode.read());
Ok(meta)
}
fn try_clone(&self) -> Result<Self> {
Ok(File {
read: self.read,
write: self.write,
append: self.append,
cursor: self.cursor.clone(),
})
}
fn set_permissions(&self, perms: Self::Permissions) -> Result<()> {
let cursor = self.cursor.lock();
let file = cursor.file.write();
let mut inode = file.inode.write();
inode.perms = perms;
Ok(())
}
}
#[derive(Debug)]
struct FileCursor {
file: Arc<RwLock<RawFile>>,
at: usize,
}
impl FileCursor {
fn set_len(&mut self, size: u64) -> Result<()> {
let mut file = self.file.write();
file.inode.write().times.update(MODIFIED);
let size = size as usize;
match file.data.len().cmp(&size) {
Ordering::Less => {
let mut new = vec![0; size];
new[..file.data.len()].copy_from_slice(&file.data);
file.data = new;
}
Ordering::Greater => file.data.truncate(size),
_ => (),
}
Ok(())
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let n = self.file.read().read_at(self.at, buf)?;
self.at += n;
Ok(n)
}
fn write(&mut self, buf: &[u8], append: bool) -> Result<usize> {
let mut file = self.file.write();
if append {
self.at = file.data.len();
}
let n = file.write_at(self.at, buf)?;
self.at += n;
Ok(n)
}
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let file = self.file.write();
file.inode.write().times.update(ACCESSED);
let len = file.data.len();
match pos {
SeekFrom::Start(offset) => {
self.at = cmp::min(len, offset as usize);
Ok(offset)
}
SeekFrom::Current(offset) => {
let at_end = (self.at as i64).saturating_add(offset);
if at_end < 0 {
return Err(EINVAL());
}
self.at = cmp::min(len, at_end as usize);
Ok(at_end as u64)
}
SeekFrom::End(offset) => {
let at_end = (len as i64).saturating_add(offset);
if at_end < 0 {
return Err(EINVAL());
}
self.at = cmp::min(len, at_end as usize);
Ok(at_end as u64)
}
}
}
}
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
(&mut &*self).read(buf)
}
}
impl Write for File {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
(&mut &*self).write(buf)
}
fn flush(&mut self) -> Result<()> {
(&mut &*self).flush()
}
}
impl Seek for File {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
Ok(self.cursor.lock().seek(pos)?)
}
}
impl<'a> Read for &'a File {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if !self.read {
return Err(EBADF());
}
Ok(self.cursor.lock().read(buf)?)
}
}
impl<'a> Write for &'a File {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if !self.write {
return Err(EBADF());
}
Ok(self.cursor.lock().write(buf, self.append)?)
}
fn flush(&mut self) -> Result<()> {
if !self.write {
return Err(EBADF());
}
Ok(())
}
}
impl<'a> Seek for &'a File {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
Ok(self.cursor.lock().seek(pos)?)
}
}
impl unix_ext::FileExt for File {
fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
if !self.read {
return Err(EBADF());
}
let cursor = self.cursor.lock();
let file = cursor.file.read();
Ok(file.read_at(offset as usize, buf)?)
}
fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize> {
if !self.write {
return Err(EBADF());
}
let cursor = self.cursor.lock();
let mut file = cursor.file.write();
Ok(file.write_at(offset as usize, buf)?)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum Ftyp {
File,
Dir,
Symlink,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FileType(Ftyp);
impl fs::FileType for FileType {
fn is_dir(&self) -> bool {
self.0 == Ftyp::Dir
}
fn is_file(&self) -> bool {
self.0 == Ftyp::File
}
fn is_symlink(&self) -> bool {
self.0 == Ftyp::Symlink
}
}
#[derive(Clone, Debug)]
pub struct Metadata(InodeData);
impl fs::Metadata for Metadata {
type Permissions = Permissions;
type FileType = FileType;
fn file_type(&self) -> Self::FileType {
self.0.ftyp
}
fn is_dir(&self) -> bool {
self.0.ftyp.is_dir()
}
fn is_file(&self) -> bool {
self.0.ftyp.is_file()
}
fn len(&self) -> u64 {
self.0.length as u64
}
fn permissions(&self) -> Self::Permissions {
self.0.perms
}
fn modified(&self) -> Result<SystemTime> {
Ok(self.0.times.modified)
}
fn accessed(&self) -> Result<SystemTime> {
Ok(self.0.times.accessed)
}
fn created(&self) -> Result<SystemTime> {
Ok(self.0.times.created)
}
}
#[derive(Clone, Debug)]
pub struct OpenOptions {
fs: FS,
read: bool,
write: bool,
append: bool,
trunc: bool,
create: bool,
excl: bool,
mode: u32,
}
impl fs::OpenOptions for OpenOptions {
type File = File;
fn read(&mut self, read: bool) -> &mut Self {
self.read = read; self
}
fn write(&mut self, write: bool) -> &mut Self {
self.write = write; self
}
fn append(&mut self, append: bool) -> &mut Self {
self.append = append; self
}
fn truncate(&mut self, truncate: bool) -> &mut Self {
self.trunc = truncate; self
}
fn create(&mut self, create: bool) -> &mut Self {
self.create = create; self
}
fn create_new(&mut self, create_new: bool) -> &mut Self {
self.excl = create_new; self
}
fn open<P: AsRef<Path>>(&self, path: P) -> Result<Self::File> {
self.fs.0.lock().open(path, self, &mut 0)
}
}
impl unix_ext::OpenOptionsExt for OpenOptions {
fn mode(&mut self, mode: u32) -> &mut Self {
self.mode = mode; self
}
fn custom_flags(&mut self, _: i32) -> &mut Self {
self }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Permissions(u32);
impl fs::Permissions for Permissions {
fn readonly(&self) -> bool {
self.0 & 0o222 == 0
}
fn set_readonly(&mut self, readonly: bool) {
if readonly {
self.0 &= !0o222
} else {
self.0 |= 0o222
}
}
}
impl unix_ext::PermissionsExt for Permissions {
fn mode(&self) -> u32 {
self.0
}
fn set_mode(&mut self, mode: u32) {
self.0 = mode
}
fn from_mode(mode: u32) -> Self {
Permissions(mode)
}
}
#[derive(Debug)]
pub struct ReadDir {
ents: IntoIter<DirEntry>,
}
impl ReadDir {
fn new<P: AsRef<Path>>(path: P, dir: &Dirent) -> Result<ReadDir> {
if !dir.readable() {
return Err(EACCES());
}
let children = match dir.kind {
DeKind::Dir(ref children) => children,
_ => return Err(ENOTDIR()),
};
dir.inode.write().times.update(ACCESSED);
let mut dirents = Vec::new();
for (base, dirent) in children.iter() {
dirents.push(DirEntry {
dir: PathBuf::from(path.as_ref()),
base: base.clone(),
inode: dirent.inode.clone(),
});
}
dirents.sort_by(|l, r| match l.dir.cmp(&r.dir) {
Ordering::Equal => l.base.cmp(&r.base),
x => x,
});
Ok(ReadDir { ents: dirents.into_iter() })
}
}
impl Iterator for ReadDir {
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Self::Item> {
self.ents.next().map(Ok)
}
}
#[derive(Clone, Debug)]
pub struct FS(Arc<Mutex<FileSystem>>);
impl FS {
pub fn new() -> FS {
Self::with_mode(0o777)
}
pub fn with_mode(mode: u32) -> FS {
let pwd = Raw::from(Dirent {
parent: None,
kind: DeKind::Dir(HashMap::new()),
name: OsString::from(""),
inode: Inode::new(mode, Ftyp::Dir, DIRLEN),
});
FS(Arc::new(Mutex::new(FileSystem {
root: pwd,
pwd: Pwd::from(pwd),
})))
}
}
impl Default for FS {
fn default() -> Self {
FS::new()
}
}
impl fs::GenFS for FS {
type DirBuilder = DirBuilder;
type DirEntry = DirEntry;
type File = File;
type Metadata = Metadata;
type OpenOptions = OpenOptions;
type Permissions = Permissions;
type ReadDir = ReadDir;
fn canonicalize<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
self.0.lock().canonicalize(path, &mut 0)
}
fn copy<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> Result<u64> {
use fs::OpenOptions;
use fs::File;
fn not_file() -> Error {
Error::new(ErrorKind::InvalidInput, "the source path is not an existing regular file")
}
let (from, to) = (from.as_ref(), to.as_ref());
{
let fs = &*self.0.lock();
let (fs, may_base) = fs.traverse(normalize(&from), &mut 0)?;
let base = may_base.ok_or_else(not_file)?;
if !fs.executable() {
return Err(EACCES());
}
match fs.kind.dir_ref().get(&base) {
Some(child) => match child.kind {
DeKind::File(_) => (),
_ => return Err(not_file()),
},
None => return Err(not_file()),
}
}
let mut reader = self.new_openopts().read(true).open(from)?;
let mut writer = self.new_openopts().write(true).truncate(true).create(true).open(to)?;
let perm = reader.metadata()?.permissions();
let ret = io::copy(&mut reader, &mut writer)?;
self.set_permissions(to, perm)?;
Ok(ret)
}
fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.new_dirbuilder().create(path)
}
fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.new_dirbuilder().recursive(true).create(path)
}
fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()> {
self.0.lock().hard_link(src, dst)
}
fn metadata<P: AsRef<Path>>(&self, path: P) -> Result<Self::Metadata> {
self.0.lock().metadata(path, &mut 0)
}
fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<Self::ReadDir> {
self.0.lock().read_dir(&path, &path, &mut 0)
}
fn read_link<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
self.0.lock().read_link(path)
}
fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.0.lock().remove_dir(path)
}
fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.0.lock().remove_dir_all(path)
}
fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.0.lock().remove_file(path)
}
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> Result<()> {
self.0.lock().rename(from, to)
}
fn set_permissions<P: AsRef<Path>>(&self, path: P, perms: Self::Permissions) -> Result<()> {
self.0.lock().set_permissions(path, perms, &mut 0)
}
fn symlink_metadata<P: AsRef<Path>>(&self, path: P) -> Result<Self::Metadata> {
self.0.lock().symlink_metadata(path)
}
fn new_openopts(&self) -> Self::OpenOptions {
OpenOptions {
fs: self.clone(),
read: false,
write: false,
append: false,
trunc: false,
create: false,
excl: false,
mode: 0o666, }
}
fn new_dirbuilder(&self) -> Self::DirBuilder {
DirBuilder {
fs: self.clone(),
recursive: false,
mode: 0o777, }
}
fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<Self::File> {
use fs::OpenOptions;
self.new_openopts().read(true).open(path.as_ref())
}
fn create_file<P: AsRef<Path>>(&self, path: P) -> Result<Self::File> {
use fs::OpenOptions;
self.new_openopts().write(true).create(true).truncate(true).open(path.as_ref())
}
}
impl unix_ext::GenFSExt for FS {
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()> {
self.0.lock().symlink(src, dst)
}
}
#[derive(Copy, Clone, Debug)]
struct Times {
modified: SystemTime,
accessed: SystemTime,
created: SystemTime,
}
const MODIFIED: u8 = 1; const ACCESSED: u8 = 2; const CREATED: u8 = 4;
impl Times {
fn new() -> Times {
let now = SystemTime::now();
Times {
modified: now,
accessed: now,
created: now,
}
}
fn update(&mut self, fields: u8) {
const MASK: u8 = !(MODIFIED | ACCESSED | CREATED);
if fields & MASK != 0 {
panic!("incorrect times update usage!")
}
let now = SystemTime::now();
if fields & MODIFIED != 0 {
self.modified = now;
}
if fields & ACCESSED != 0 {
self.accessed = now;
}
if fields & CREATED != 0 {
self.created = now;
}
}
}
#[derive(Copy, Clone, Debug)]
struct InodeData {
times: Times,
perms: Permissions,
ftyp: FileType,
length: usize,
}
impl PartialEq for InodeData {
fn eq(&self, other: &Self) -> bool {
self.perms == other.perms &&
self.ftyp == other.ftyp &&
self.length == other.length
}
}
#[derive(Clone, Debug)]
struct Inode(Arc<RwLock<InodeData>>);
impl PartialEq for Inode {
fn eq(&self, other: &Self) -> bool {
*self.read() == *other.read()
}
}
impl Inode {
fn new(mode: u32, ftyp: Ftyp, len: usize) -> Inode {
Inode(Arc::new(RwLock::new(InodeData {
times: Times::new(),
perms: Permissions(mode),
ftyp: FileType(ftyp),
length: len,
})))
}
fn view(&self) -> InodeData {
*self.read()
}
fn perms(&self) -> Permissions {
self.read().perms
}
}
impl Deref for Inode {
type Target = Arc<RwLock<InodeData>>;
#[inline]
fn deref(&self) -> &Arc<RwLock<InodeData>> {
&self.0
}
}
#[derive(Debug)]
enum DeKind {
File(Arc<RwLock<RawFile>>),
Dir(HashMap<OsString, Raw<Dirent>>),
Symlink(PathBuf),
}
impl DeKind {
fn file_ref(&self) -> &Arc<RwLock<RawFile>> {
match *self {
DeKind::File(ref f) => f,
_ => panic!("file_ref used on DeKind when not a file"),
}
}
fn dir_ref(&self) -> &HashMap<OsString, Raw<Dirent>> {
match *self {
DeKind::Dir(ref d) => d,
_ => panic!("dir_ref used on DeKind when not a dir"),
}
}
fn dir_mut(&mut self) -> &mut HashMap<OsString, Raw<Dirent>> {
match *self {
DeKind::Dir(ref mut d) => d,
_ => panic!("dir_mut used on DeKind when not a dir"),
}
}
fn symlink_ref(&self) -> &PathBuf {
match *self {
DeKind::Symlink(ref sl) => sl,
_ => panic!("symlink_ref used on DeKind when not a symlink"),
}
}
}
struct Dirent {
parent: Option<Raw<Dirent>>,
kind: DeKind,
name: OsString,
inode: Inode,
}
impl Dirent {
fn is_dir(&self) -> bool {
match self.kind {
DeKind::Dir(_) => true,
_ => false,
}
}
fn readable(&self) -> bool {
self.inode.perms().0 & 0o400 == 0o400
}
fn writable(&self) -> bool {
self.inode.perms().0 & 0o200 == 0o200
}
fn executable(&self) -> bool {
self.inode.perms().0 & 0o100 == 0o100
}
fn changeable(&self) -> bool {
self.inode.perms().0 & 0o300 == 0o300
}
fn rremovable(&self) -> bool {
self.inode.perms().0 & 0o700 == 0o700
}
}
impl fmt::Debug for Dirent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Dirent {{ parent: ")?;
match self.parent {
Some(_) => write!(f, "Some(parent)")?,
None => write!(f, "None")?,
};
write!(f, ", kind: {:?}, name: {:?}, inode: {:?} }}",
self.kind, self.name, self.inode)
}
}
#[derive(Debug)]
struct Pwd {
inner: Raw<Dirent>,
alive: bool,
}
impl From<Raw<Dirent>> for Pwd {
fn from(d: Raw<Dirent>) -> Pwd {
Pwd {
inner: d,
alive: true,
}
}
}
#[derive(Debug)]
struct FileSystem {
root: Raw<Dirent>,
pwd: Pwd,
}
impl Deref for FileSystem {
type Target = Pwd;
fn deref(&self) -> &Pwd {
&self.pwd
}
}
impl DerefMut for FileSystem {
fn deref_mut(&mut self) -> &mut Pwd {
&mut self.pwd
}
}
impl Drop for FileSystem {
fn drop(&mut self) {
if !self.pwd.alive && Raw::ptr_eq(&self.root, &self.pwd.inner) {
return;
}
let mut todo = Vec::new();
todo.push(self.root);
while let Some(elem) = todo.pop() {
let rs = unsafe { Box::from_raw(elem.ptr()) };
if let DeKind::Dir(ref d) = rs.kind {
for child in d.values() {
todo.push(*child);
}
}
}
}
}
fn path_empty<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().as_os_str().is_empty()
}
impl PartialEq for FileSystem {
fn eq(&self, other: &Self) -> bool {
fn eq_at(l: Raw<Dirent>, r: Raw<Dirent>) -> bool {
if l.inode.view() != r.inode.view() ||
l.name != r.name {
return false;
}
match (&l.kind, &r.kind) {
(&DeKind::File(ref fl), &DeKind::File(ref fr)) => fl.read().data == fr.read().data,
(&DeKind::Dir(ref dl), &DeKind::Dir(ref dr)) => {
if dl.len() != dr.len() {
return false;
}
for (child_name, cl) in dl.iter() {
match dr.get(child_name) {
Some(cr) => if !eq_at(*cl, *cr) {
return false;
},
None => return false,
}
}
true
},
(&DeKind::Symlink(ref sl), &DeKind::Symlink(ref sr)) => sl == sr,
_ => false,
}
}
if !self.pwd.alive || !other.pwd.alive {
panic!("invalid FileSystem PartialEq - both FileSystem's pwd must be alive!")
}
eq_at(self.root, other.root)
}
}
impl Pwd {
fn up_path(&self,
parts: Parts)
-> Result<(Raw<Dirent>, Vec<Part>)> {
if !self.alive {
return Err(EINVAL());
}
let mut up = self.inner;
if parts.at_root {
while let Some(parent) = up.parent {
up = parent;
}
return Ok((up, parts.inner));
}
let mut parts_iter = parts.inner.into_iter().peekable();
while parts_iter.peek()
.and_then(|part| if *part == Part::ParentDir { Some(()) } else { None })
.is_some() {
parts_iter.next();
if let Some(parent) = up.parent {
if !parent.executable() {
return Err(EACCES());
}
up = parent;
}
}
Ok((up, parts_iter.collect()))
}
fn traverse(&self,
parts: Parts,
level: &mut u8)
-> Result<(Raw<Dirent>, Option<OsString>)> {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
let (mut fs, parts) = self.up_path(parts)?;
let mut parts_iter = parts.into_iter().peek2able();
loop {
let on = fs; match on.kind {
DeKind::Dir(ref children) => {
if !parts_iter.peek2().is_some() {
match parts_iter.next() {
Some(Part::Normal(base)) => return Ok((fs, Some(base))),
_ => return Ok((fs, None)),
}
}
if !fs.executable() {
return Err(EACCES());
}
fs = children.get(parts_iter
.next()
.expect("peek2 is Some, next is None")
.as_normal()
.expect("parts_iter should be Normal after up_path"))
.cloned()
.ok_or_else(ENOENT)?;
}
DeKind::Symlink(ref sl) => {
fs = fs.parent.expect("symlinks should always have a parent");
let (new_fs, may_base) = Pwd::from(fs).traverse(normalize(sl), level)?;
fs = new_fs;
match may_base {
Some(base) => {
let parts: Parts = Part::Normal(base).into();
parts_iter = parts.inner
.into_iter()
.chain(parts_iter)
.collect::<Vec<Part>>()
.into_iter()
.peek2able();
},
None => {
if path_empty(sl) {
panic!("empty path in a symlink (should not be possible)");
}
}
}
}
_ => return Err(ENOTDIR()),
}
}
}
fn canonicalize<P: AsRef<Path>>(&self, path: P, level: &mut u8) -> Result<PathBuf> {
fn build_from_fs(mut fs: Raw<Dirent>) -> Result<PathBuf> {
let mut rev = Vec::new();
while !fs.name.is_empty() {
rev.push(fs.name.clone());
fs = fs.parent.expect("a non-root directory should have a parent, only root has no name");
}
rev.reverse();
let mut pb = PathBuf::from("/");
for p in rev {
pb.push(p)
}
Ok(pb)
}
let (fs, may_base) = self.traverse(normalize(&path), level)?;
let base = match may_base {
Some(base) => base,
None => {
if path_empty(&path) {
return Err(ENOENT());
}
return build_from_fs(fs);
}
};
if !fs.executable() {
return Err(EACCES());
}
let parent = fs;
match fs.kind.dir_ref().get(&base) {
Some(child) => {
if let DeKind::Symlink(ref sl) = child.kind {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
return Pwd::from(parent).canonicalize(sl, level);
}
build_from_fs(*child)
}
None => Err(ENOENT()),
}
}
fn create_dir<P: AsRef<Path>>(&self, path: P, mode: u32, can_exist: bool) -> Result<()> {
let (mut fs, may_base) = self.traverse(normalize(&path), &mut 0)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(&path) {
return Err(ENOENT());
} else { if can_exist {
return Ok(());
}
return Err(EEXIST());
}
};
if !fs.changeable() {
return Err(EACCES());
}
let parent = fs;
match fs.kind.dir_mut().entry(base.clone()) {
Entry::Occupied(o) => {
if o.get().inode.view().ftyp.is_dir() && can_exist {
return Ok(())
}
Err(EEXIST())
}
Entry::Vacant(v) => {
v.insert(Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: base,
inode: Inode::new(mode, Ftyp::Dir, DIRLEN),
}));
Ok(())
}
}
}
fn create_dir_all<P: AsRef<Path>>(&self, path: P, mode: u32) -> Result<()> {
let (_, parts) = self.up_path(normalize(&path))?;
let mut path_buf = PathBuf::new();
for part in parts {
path_buf.push(part.into_normal().expect("parts_iter after up_path should only be Normal"));
self.create_dir(&path_buf, mode, true)?;
}
Ok(())
}
fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()> {
let (src_fs, src_may_base) = self.traverse(normalize(&src), &mut 0)?;
let src_base = src_may_base.ok_or_else(EPERM)?;
if !src_fs.executable() {
return Err(EACCES());
}
let (mut dst_fs, dst_may_base) = self.traverse(normalize(&dst), &mut 0)?;
let dst_base = dst_may_base.ok_or_else(EEXIST)?;
if !dst_fs.executable() {
return Err(EACCES());
}
let src_child = match src_fs.kind.dir_ref().get(&src_base) {
Some(child) => child,
None => return Err(ENOENT()),
};
if dst_fs.kind.dir_ref().get(&dst_base).is_some() {
return Err(EEXIST());
}
if !dst_fs.writable() {
return Err(EACCES());
}
let parent = dst_fs;
match src_child.kind {
DeKind::Dir(_) => Err(EPERM()),
DeKind::Symlink(ref sl) => {
dst_fs.kind
.dir_mut()
.insert(dst_base.clone(),
Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Symlink(sl.clone()),
name: dst_base,
inode: src_child.inode.clone(),
}));
Ok(())
}
DeKind::File(ref f) => {
dst_fs.kind
.dir_mut()
.insert(dst_base.clone(),
Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::File(f.clone()),
name: dst_base,
inode: src_child.inode.clone(),
}));
Ok(())
}
}
}
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()> {
let (mut dst_fs, dst_may_base) = self.traverse(normalize(&dst), &mut 0)?;
let dst_base = dst_may_base.ok_or_else(EEXIST)?;
if !dst_fs.changeable() {
return Err(EACCES());
}
if dst_fs.kind.dir_ref().get(&dst_base).is_some() {
return Err(EEXIST());
}
let sl = src.as_ref().to_owned();
let len = sl.as_os_str().len();
let parent = dst_fs;
dst_fs.kind
.dir_mut()
.insert(dst_base.clone(),
Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Symlink(sl),
name: dst_base,
inode: Inode::new(0o777, Ftyp::Symlink, len),
}));
Ok(())
}
fn metadata<P: AsRef<Path>>(&self, path: P, level: &mut u8) -> Result<Metadata> {
let (fs, may_base) = self.traverse(normalize(&path), level)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(path) {
return Err(ENOENT());
} else { return Ok(Metadata(fs.inode.view()));
}
};
if !fs.executable() {
return Err(EACCES());
}
let parent = fs;
match fs.kind.dir_ref().get(&base) {
Some(child) => {
let meta = child.inode.view();
if meta.ftyp.is_symlink() {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
return Pwd::from(parent).metadata(child.kind.symlink_ref(), level);
}
Ok(Metadata(meta))
}
None => Err(ENOENT()),
}
}
fn read_dir<P: AsRef<Path>, Q: AsRef<Path>>(&self, og_path: Q, path: P, level: &mut u8) -> Result<ReadDir> {
let (fs, may_base) = self.traverse(normalize(&path), level)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(&path) {
return Err(ENOENT());
} else { return ReadDir::new(&og_path, &*fs);
}
};
if !fs.executable() {
return Err(EACCES());
}
let parent = fs;
match fs.kind.dir_ref().get(&base) {
Some(child) => {
if let DeKind::Symlink(ref sl) = child.kind {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
return Pwd::from(parent).read_dir(og_path, sl, level);
}
ReadDir::new(&og_path, &*child)
},
None => Err(ENOENT()),
}
}
fn read_link<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
let (fs, may_base) = self.traverse(normalize(&path), &mut 0)?;
let base = may_base.ok_or_else(||
if path_empty(&path) {
ENOENT()
} else {
EINVAL()
})?;
if !fs.executable() {
return Err(EACCES());
}
match fs.kind.dir_ref().get(&base) {
Some(child) => match child.kind {
DeKind::Symlink(ref sl) => Ok(sl.clone()),
_ => Err(EINVAL()),
},
None => Err(ENOENT()),
}
}
fn remove<P: AsRef<Path>>(&self, path: P, kind: FileType) -> Result<()> {
let (mut fs, may_base) = self.traverse(normalize(&path), &mut 0)?;
let base = may_base.ok_or_else(||
if path_empty(&path) {
ENOENT()
} else {
ENOTEMPTY()
})?;
{
if !fs.changeable() {
return Err(EACCES());
}
let child = fs.kind
.dir_ref()
.get(&base)
.ok_or_else(ENOENT)?;
match kind.0 {
Ftyp::File | Ftyp::Symlink => if child.is_dir() { return Err(EISDIR()); },
Ftyp::Dir => {
if !child.is_dir() {
return Err(ENOTDIR());
}
if !child.kind.dir_ref().is_empty() {
return Err(ENOTEMPTY());
}
},
}
}
let removed = fs.kind
.dir_mut()
.remove(&base)
.expect("remove logic checking existence is wrong");
unsafe { Box::from_raw(removed.ptr()); }
Ok(())
}
fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.remove(path, FileType(Ftyp::File))
}
fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.remove(path, FileType(Ftyp::Dir))
}
fn remove_dir_all<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
fn maybe_kill_self(pwd: &mut Pwd, removed: &Raw<Dirent>) {
if pwd.alive && Raw::ptr_eq(removed, &pwd.inner) {
pwd.alive = false;
}
}
fn recursive_remove(pwd: &mut Pwd, mut fs: Raw<Dirent>) -> Result<()> {
let accessible = fs.rremovable();
if let DeKind::Dir(ref mut children) = fs.kind { if !accessible {
return Err(EACCES());
}
let mut deleted = Vec::new();
let res: Result<()> = {
let mut child_names = Vec::new();
for child in children.iter() {
child_names.push(child);
}
child_names.sort_by_key(|&(k, _)| k);
let mut err = Ok(());
for &(child_name, child) in &child_names {
match recursive_remove(pwd, *child) {
Ok(_) => deleted.push(child_name.clone()),
Err(e) => { err = Err(e); break; },
}
}
err
};
for child_name in deleted {
let removed = children.remove(&child_name)
.expect("deleted has child_name not in child map");
maybe_kill_self(pwd, &removed);
unsafe { Box::from_raw(removed.ptr()); } }
res?
}
Ok(())
}
let (mut fs, may_base) = self.traverse(normalize(&path), &mut 0)?;
match may_base {
Some(base) => { if !fs.changeable() {
return Err(EACCES());
}
if let Entry::Occupied(child) = fs.kind.dir_mut().entry(base) {
recursive_remove(self, *child.get())?;
maybe_kill_self(self, child.get());
unsafe { Box::from_raw(child.remove().ptr()); }
}
}
None => { if path_empty(&path) {
return Err(ENOENT());
}
recursive_remove(self, fs)?;
maybe_kill_self(self, &fs);
unsafe { Box::from_raw(fs.ptr()); }
}
}
Ok(())
}
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> Result<()> {
let (mut old_fs, old_may_base) = self.traverse(normalize(&from), &mut 0)?;
let old_base = old_may_base.ok_or_else(||
if path_empty(&from) {
ENOENT()
} else if old_fs.parent.is_some() {
EBUSY() } else {
Error::new(ErrorKind::Other, "rename of root unimplemented")
})?;
let (mut new_fs, new_may_base) = self.traverse(normalize(&to), &mut 0)?;
let new_base = new_may_base.ok_or_else(||
if path_empty(&to) {
ENOENT()
} else if new_fs.parent.is_some() {
EBUSY()
} else {
EEXIST()
})?;
if Raw::ptr_eq(&old_fs, &new_fs) && old_base == new_base {
return Ok(());
}
if !old_fs.executable() || !new_fs.executable() {
return Err(EACCES());
}
let (old_exist, old_is_dir) =
match old_fs.kind.dir_ref().get(&old_base) {
Some(child) => (true, child.is_dir()),
None => (false, false),
};
if !old_exist {
return Err(ENOENT());
}
if !old_fs.writable() || !new_fs.writable() {
return Err(EACCES());
}
let (new_exist, new_is_dir, new_is_empty) =
match new_fs.kind.dir_ref().get(&new_base) {
Some(child) => match child.kind {
DeKind::Dir(ref children) => (true, true, children.is_empty()),
_ => (true, false, false),
},
None => (false, false, false),
};
if !old_is_dir {
if new_is_dir {
return Err(EISDIR());
}
} else if new_exist {
if new_is_dir {
if !new_is_empty {
return Err(ENOTEMPTY());
}
} else {
return Err(EEXIST());
}
}
let mut renamed = old_fs.kind.dir_mut().remove(&old_base)
.expect("logic verifying dirent existence is wrong");
renamed.name = new_base.clone();
let mut inode = renamed.inode.write();
inode.times.update(MODIFIED|ACCESSED|CREATED);
new_fs.kind.dir_mut().insert(new_base, renamed);
Ok(())
}
fn set_permissions<P: AsRef<Path>>(&self, path: P, perms: Permissions, level: &mut u8) -> Result<()> {
let (mut fs, may_base) = self.traverse(normalize(&path), level)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(&path) {
return Err(ENOENT());
} else {
fs.inode.write().perms = perms;
return Ok(());
}
};
if !fs.executable() {
return Err(EACCES());
}
let parent = fs;
match fs.kind.dir_ref().get(&base) {
Some(child) => {
if let DeKind::Symlink(ref sl) = child.kind {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
return Pwd::from(parent).set_permissions(sl, perms, level);
}
let mut child = *child; child.inode.write().perms = perms;
Ok(())
}
None => Err(ENOENT()),
}
}
fn symlink_metadata<P: AsRef<Path>>(&self, path: P) -> Result<Metadata> {
let (fs, may_base) = self.traverse(normalize(&path), &mut 0)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(path) {
return Err(ENOENT());
} else {
return Ok(Metadata(fs.inode.view())); }
};
if !fs.executable() {
return Err(EACCES());
}
match fs.kind.dir_ref().get(&base) {
Some(child) => Ok(Metadata(child.inode.view())),
None => Err(ENOENT()),
}
}
fn open<P: AsRef<Path>>(&self,
path: P,
options: &OpenOptions,
level: &mut u8)
-> Result<File> {
let mut options = options.clone();
if options.append {
options.write = true;
}
if options.excl {
options.create = true;
}
if (options.create || options.trunc) && !options.write {
return Err(EINVAL());
}
if options.trunc && options.append {
return Err(EINVAL());
}
let (mut fs, may_base) = self.traverse(normalize(&path), level)?;
let base = match may_base {
Some(base) => base,
None => if path_empty(&path) {
return Err(ENOENT());
} else { return Self::open_existing(&fs, &options);
}
};
if !fs.executable() {
return Err(EACCES());
}
let parent = fs;
if let Some(child) = fs.kind.dir_ref().get(&base) {
if let DeKind::Symlink(ref sl) = child.kind {
if {*level += 1; *level} == 40 {
return Err(ELOOP());
}
return Pwd::from(parent).open(sl, &options, level);
}
return Self::open_existing(child, &options);
}
if !options.create {
return Err(ENOENT());
}
if !fs.writable() {
return Err(EACCES());
}
let file = Arc::new(RwLock::new(RawFile { data: Vec::new(),
inode: Inode::new(options.mode, Ftyp::File, 0),
}));
let child = Raw::from(Dirent {
parent: Some(fs),
kind: DeKind::File(file.clone()),
name: base.clone(),
inode: file.write().inode.clone(),
});
fs.kind.dir_mut().insert(base, child);
Ok(File { read: options.read,
write: options.write,
append: options.append,
cursor: Arc::new(Mutex::new(FileCursor {
file: file,
at: 0,
})),
})
}
fn open_existing(fs: &Raw<Dirent>, options: &OpenOptions) -> Result<File> {
if options.excl {
return Err(EEXIST());
}
if fs.is_dir() {
if options.write {
return Err(EISDIR());
}
return Err(Error::new(ErrorKind::Other, "open on directory unimplemented"));
}
let (mut read, mut write) = (false, false);
if options.read {
if !fs.readable() {
return Err(EACCES());
}
read = true;
}
if options.write {
if !fs.writable() {
return Err(EACCES());
}
write = true;
}
let file = fs.kind.file_ref().clone();
{
let mut raw_file = file.write();
if options.trunc {
raw_file.data = Vec::new();
}
raw_file.inode.write().times.update(ACCESSED);
}
Ok(File {
read: read,
write: write,
append: options.append,
cursor: Arc::new(Mutex::new(FileCursor {
file: file,
at: 0,
})),
})
}
}
#[cfg(test)]
mod test {
use std::ffi::OsString;
use std::io::Error;
use std::sync::mpsc;
use std::thread;
use fs::{DirEntry as DirEntryTrait, File, GenFS, OpenOptions};
use unix_ext::*;
use super::*;
impl PartialEq for FS {
fn eq(&self, other: &Self) -> bool {
*self.0.lock() == *other.0.lock()
}
}
fn errs_eq(l: Error, r: Error) -> bool {
l.raw_os_error() == r.raw_os_error()
}
fn ref_errs_eq(l: &Error, r: &Error) -> bool {
l.raw_os_error() == r.raw_os_error()
}
#[test]
fn equal() {
let mut exp_root = Raw::from(Dirent {
parent: None,
kind: DeKind::Dir(HashMap::new()),
name: OsString::from(""),
inode: Inode::new(0, Ftyp::Dir, DIRLEN),
});
let exp = FS(Arc::new(Mutex::new(FileSystem {
root: exp_root,
pwd: Pwd::from(exp_root),
})));
{
let parent = exp_root;
let ref mut dir = exp_root.kind.dir_mut();
dir.insert(OsString::from("lolz"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: OsString::from("lolz"),
inode: Inode::new(0o666, Ftyp::Dir, DIRLEN),
}));
let file_inode = Inode::new(0o334, Ftyp::File, 3);
dir.insert(OsString::from("f"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::File(Arc::new(RwLock::new(RawFile{
data: vec![1, 2, 3],
inode: file_inode.clone(),
}))),
name: OsString::from("f"),
inode: file_inode,
}));
dir.insert(OsString::from("sl"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Symlink(String::from(".././zzz").into()),
name: OsString::from("sl"),
inode: Inode::new(0o777, Ftyp::Symlink, 8),
}));
}
let fs = FS::with_mode(0o777);
assert!(fs.new_dirbuilder().mode(0o666).create("lolz").is_ok());
let mut f = fs.new_openopts().mode(0o334).write(true).create_new(true).open("f").unwrap();
assert!(f.write(vec![1, 2, 3].as_slice()).is_ok());
assert!(fs.symlink(".././zzz", "sl").is_ok());
assert!(fs.set_permissions("/", Permissions::from_mode(0)).is_ok());
assert!(fs == exp);
}
#[test]
fn create_dir() {
let mut exp_root = Raw::from(Dirent {
parent: None,
kind: DeKind::Dir(HashMap::new()),
name: OsString::from(""),
inode: Inode::new(0o300, Ftyp::Dir, DIRLEN),
});
let exp = FS(Arc::new(Mutex::new(FileSystem {
root: exp_root,
pwd: Pwd::from(exp_root),
})));
{
let parent = exp_root;
let children = exp_root.kind.dir_mut();
children.insert(OsString::from("a"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: OsString::from("a"),
inode: Inode::new(0o500, Ftyp::Dir, DIRLEN), }));
children.insert(OsString::from("b"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: OsString::from("b"),
inode: Inode::new(0o600, Ftyp::Dir, DIRLEN), }));
let mut child = Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: OsString::from("c"),
inode: Inode::new(0o300, Ftyp::Dir, DIRLEN), });
{
let parent = child;
let child_children = child.kind.dir_mut();
child_children.insert(OsString::from("d"), Raw::from(Dirent {
parent: Some(parent),
kind: DeKind::Dir(HashMap::new()),
name: OsString::from("d"),
inode: Inode::new(0o777, Ftyp::Dir, DIRLEN),
}));
}
children.insert(OsString::from("c"), child);
}
let fs = FS::with_mode(0o300);
assert!(fs.new_dirbuilder().mode(0o500).create("/../a").is_ok());
assert!(fs.new_dirbuilder().mode(0o600).create("../b").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).create("c").is_ok());
assert!(fs.new_dirbuilder().mode(0o777).create("c/d").is_ok());
assert!(fs == exp);
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("a/z").unwrap_err(), EACCES()));
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("b/z").unwrap_err(), EACCES()));
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("/").unwrap_err(), EEXIST()));
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("a").unwrap_err(), EEXIST()));
assert!(errs_eq(fs.new_dirbuilder().mode(0o777).create("z/z").unwrap_err(), ENOENT()));
}
#[test]
fn create_dir_all() {
let fs = FS::with_mode(0o300);
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("////").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("a/b/c").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("/a/b/c/").is_ok());
assert!(fs.new_dirbuilder().recursive(true).create("..").is_ok());
assert!(errs_eq(fs.new_dirbuilder()
.mode(0o100)
.recursive(true)
.create("d/e/f")
.unwrap_err(),
EACCES()));
let exp = FS::with_mode(0o300);
assert!(exp.new_dirbuilder().mode(0o300).create("a").is_ok());
assert!(exp.new_dirbuilder().mode(0o300).create("a/b").is_ok());
assert!(exp.new_dirbuilder().mode(0o300).create("a/b/c").is_ok());
assert!(exp.new_dirbuilder().mode(0o100).create("d").is_ok());
assert!(fs == exp);
assert!(fs.set_permissions("a/b", Permissions::from_mode(0o600)).is_ok());
assert!(errs_eq(fs.new_dirbuilder().mode(0o100).create("a/b/z").unwrap_err(),
EACCES()));
assert!(exp.set_permissions("a/b", Permissions::from_mode(0o600)).is_ok());
assert!(fs == exp);
}
#[test]
fn open() {
let fs = FS::with_mode(0o700);
assert!(fs.new_dirbuilder().mode(0o100).create("unwrite").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("unexec/subdir").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("okdir").is_ok());
assert!(fs.set_permissions("unexec", Permissions::from_mode(0o200)).is_ok());
assert!(errs_eq(fs.new_openopts().write(true).open("").unwrap_err(), ENOENT()));
fn test_open<P: AsRef<Path>>(on: i32,
fs: &FS,
read: bool,
write: bool,
append: bool,
trunc: bool,
excl: bool,
create: bool,
mode: u32,
path: P,
err: Option<Error>) {
let res = fs.new_openopts()
.read(read)
.write(write)
.append(append)
.truncate(trunc)
.create_new(excl)
.create(create)
.mode(mode)
.open(&path);
if err.is_some() {
if res.is_ok() {
panic!("#{}: expected an error", on);
}
let (res_err, exp_err) = (res.unwrap_err(), err.unwrap());
if res_err.kind() == ErrorKind::Other && exp_err.kind() == ErrorKind::Other {
return;
}
assert!(ref_errs_eq(&res_err, &exp_err),
"#{}: got err {:?} != exp err {:?}",
on, &res_err, &exp_err);
return;
}
if res.is_err() {
panic!("#{}: not ok: {:?}", on, res.unwrap_err());
}
let file = res.unwrap();
assert!(read == file.read);
assert!((write || append) == file.write);
assert!(append == file.append);
assert!(file.metadata().unwrap().is_file());
}
let (t, f) = (true, false);
let mut i = -1;
let mut on = || -> i32 { i += 1; i };
test_open(on(), &fs, t, t, t, f, t, t, 0o700, "", Some(ENOENT()));
test_open(on(), &fs, t, f, f, t, f, f, 0o700, "/", Some(EINVAL())); test_open(on(), &fs, t, f, f, f, t, f, 0o700, "/", Some(EINVAL())); test_open(on(), &fs, t, f, f, f, f, t, 0o700, "/", Some(EINVAL())); test_open(on(), &fs, f, t, t, t, f, f, 0o700, "/", Some(EINVAL()));
test_open(on(), &fs, f, t, f, f, f, f, 0o700, "/", Some(EISDIR()));
test_open(on(), &fs, t, f, f, f, f, f, 0o700, "/", Some(Error::new(ErrorKind::Other, "")));
test_open(on(), &fs, t, f, f, f, f, f, 0o700, "okdir", Some(Error::new(ErrorKind::Other, "")));
test_open(on(), &fs, f, t, f, f, t, f, 0o200, "unexec/a", Some(EACCES()));
test_open(on(), &fs, f, t, f, f, t, f, 0o200, "unwrite/a", Some(EACCES()));
test_open(on(), &fs, t, f, f, f, f, f, 0o700, "f", Some(ENOENT())); test_open(on(), &fs, f, t, f, f, t, f, 0o700, "f", None); test_open(on(), &fs, f, t, f, f, t, f, 0o300, "f", Some(EEXIST())); test_open(on(), &fs, f, t, f, f, t, f, 0o200, "/", Some(EEXIST()));
test_open(on(), &fs, t, f, f, f, f, f, 0o300, "f", None); test_open(on(), &fs, t, f, t, f, f, f, 0o300, "f", None); test_open(on(), &fs, t, f, t, f, f, t, 0o300, "f", None); test_open(on(), &fs, t, f, t, f, t, f, 0o700, "g", None); test_open(on(), &fs, t, f, t, f, f, t, 0o300, "h", None); test_open(on(), &fs, t, t, t, f, f, t, 0o300, "g", None); test_open(on(), &fs, t, t, f, t, t, t, 0o700, "i", None); test_open(on(), &fs, t, t, t, f, f, t, 0o300, "i", None); test_open(on(), &fs, f, t, f, f, f, t, 0o300, "i", None);
test_open(on(), &fs, t, t, f, f, f, t, 0o300, "f_unread", None); test_open(on(), &fs, t, t, f, f, f, t, 0o300, "f_unread", Some(EACCES()));
test_open(on(), &fs, f, t, f, f, f, t, 0o500, "f_unwrite", None); test_open(on(), &fs, f, t, f, f, f, t, 0o500, "f_unwrite", Some(EACCES()));
}
#[test]
fn remove() {
let fs = FS::with_mode(0o700);
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("unexec/subdir/d").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).recursive(true).create("a/d/e").is_ok());
assert!(fs.new_dirbuilder().mode(0o000).create("a/b").is_ok());
assert!(fs.new_openopts().write(true).create(true).mode(0o400).open("a/c").is_ok());
assert!(fs.set_permissions("unexec", Permissions::from_mode(0o200)).is_ok());
assert!(errs_eq(fs.new_dirbuilder().mode(0o300).create("a/c").unwrap_err(),
EEXIST()));
assert!(errs_eq(fs.remove_dir("").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.remove_dir("/").unwrap_err(), ENOTEMPTY()));
assert!(errs_eq(fs.remove_dir("unexec/subdir").unwrap_err(), EACCES()));
assert!(errs_eq(fs.remove_dir("unexec/subdir/d").unwrap_err(), EACCES()));
assert!(errs_eq(fs.remove_dir("a").unwrap_err(), ENOTEMPTY()));
assert!(errs_eq(fs.remove_dir("a/c/z").unwrap_err(), ENOTDIR()));
assert!(errs_eq(fs.remove_dir("a/z").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.remove_dir("a/d").unwrap_err(), ENOTEMPTY()));
assert!(fs.remove_file("a/c").is_ok());
assert!(errs_eq(fs.remove_dir("../../unexec/subdir").unwrap_err(), EACCES()));
assert!(errs_eq(fs.remove_dir("../a/d").unwrap_err(), ENOTEMPTY()));
assert!(fs.remove_dir("../a/d/e/./").is_ok());
assert!(fs.remove_dir("a/d").is_ok());
assert!(errs_eq(fs.remove_dir("a/d").unwrap_err(), ENOENT()));
}
#[test]
fn remove_dir_all() {
let fs = FS::with_mode(0o700);
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("a/b/c").is_ok());
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("j/k/l").is_ok());
assert!(fs.new_openopts().mode(0o000).write(true).create(true).open("j/f").is_ok());
assert!(fs.new_dirbuilder().mode(0o500).create("x").is_ok());
assert!(errs_eq(fs.remove_dir_all("").unwrap_err(), ENOENT()));
assert!(fs.remove_dir_all("a").is_ok());
assert!(errs_eq(fs.remove_dir_all("..").unwrap_err(), EACCES()));
assert!(errs_eq(fs.remove_dir_all("x").unwrap_err(), EACCES()));
let exp = FS::with_mode(0o700);
assert!(exp.new_dirbuilder().mode(0o500).create("x").is_ok());
assert!(fs == exp);
let fs = FS::new();
assert!(fs.remove_dir_all(".").is_ok());
assert!(errs_eq(fs.canonicalize("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.copy("a", "b").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.create_dir("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.create_dir_all("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.hard_link("a", "b").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.metadata("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.read_dir("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.read_link("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.remove_dir("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.remove_dir_all("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.remove_file("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.rename("a", "b").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.set_permissions("a", Permissions::from_mode(0)).unwrap_err(), EINVAL()));
assert!(errs_eq(fs.symlink_metadata("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.symlink("a", "b").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.open_file("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.create_file("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.new_openopts().write(true).create(true).open("a").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.new_dirbuilder().create("a").unwrap_err(), EINVAL()));
}
#[test]
fn rename() {
let fs = FS::with_mode(0o700);
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("a/b/c").is_ok());
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("d/e").is_ok());
assert!(fs.set_permissions("a/b", Permissions::from_mode(0o600)).is_ok()); assert!(fs.new_openopts().mode(0).write(true).create(true).open("f").is_ok());
assert!(fs.new_openopts().mode(0).write(true).create(true).open("g").is_ok());
assert!(errs_eq(fs.rename("a/b/c/d", "").unwrap_err(), EACCES()));
assert!(errs_eq(fs.rename("a", "a/b/c/d").unwrap_err(), EACCES()));
assert!(errs_eq(fs.rename("", "d/e/f").unwrap_err(), ENOENT()));
assert!(fs.rename("/", "d/e/f").unwrap_err().kind() == ErrorKind::Other);
assert!(errs_eq(fs.rename("a/b/c", "").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.rename("d", "/").unwrap_err(), EEXIST()));
assert!(fs.rename("a", "a").is_ok());
assert!(errs_eq(fs.rename("a/b/c", "d").unwrap_err(), EACCES()));
assert!(errs_eq(fs.rename("d", "a/b/d").unwrap_err(), EACCES()));
assert!(errs_eq(fs.rename("a/z", "d/z").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.rename("a", "d").unwrap_err(), ENOTEMPTY()));
assert!(errs_eq(fs.rename("a", "f").unwrap_err(), EEXIST()));
assert!(errs_eq(fs.rename("f", "a").unwrap_err(), EISDIR()));
assert!(fs.rename("a", "d/a").is_ok());
assert!(fs.rename("d/a", "d/e").is_ok());
assert!(fs.rename("f", "z").is_ok());
assert!(fs.rename("g", "z").is_ok());
let exp = FS::with_mode(0o700);
assert!(exp.new_dirbuilder().mode(0o700).recursive(true).create("d/e/b/c").is_ok());
assert!(exp.set_permissions("d/e/b", Permissions::from_mode(0o600)).is_ok());
assert!(exp.new_openopts().mode(0).write(true).create(true).open("z").is_ok());
assert!(fs == exp);
}
#[test]
fn read_dir() {
let fs = FS::with_mode(0o700);
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("a/b/c").is_ok());
assert!(fs.new_dirbuilder().mode(0o300).create("a/b/d").is_ok());
assert!(fs.new_dirbuilder().mode(0o100).create("a/b/z").is_ok());
assert!(fs.new_openopts().mode(0o000).write(true).create(true).open("a/b/f").is_ok());
fn de_eq(this: DirEntry, other: DirEntry) -> bool {
this.dir == other.dir &&
this.base == other.base &&
this.inode == other.inode
}
let mut reader = fs.read_dir("a/b").unwrap();
assert!(de_eq(reader.next().unwrap().unwrap(), DirEntry {
dir: PathBuf::from("a/b"),
base: OsString::from("c"),
inode: Inode::new(0o700, Ftyp::Dir, DIRLEN),
}));
assert!(de_eq(reader.next().unwrap().unwrap(), DirEntry {
dir: PathBuf::from("a/b"),
base: OsString::from("d"),
inode: Inode::new(0o300, Ftyp::Dir, DIRLEN),
}));
let next = reader.next().unwrap().unwrap();
assert_eq!(next.path(), PathBuf::from("a/b/f"));
assert_eq!(next.file_name(), OsString::from("f"));
let meta = next.metadata().unwrap();
assert!(meta.0 == Inode::new(0, Ftyp::File, 0).view());
let next = reader.next().unwrap().unwrap();
assert_eq!(next.path(), PathBuf::from("a/b/z"));
assert_eq!(next.file_name(), OsString::from("z"));
assert!(next.metadata().unwrap().0 == Inode::new(0o100, Ftyp::Dir, DIRLEN).view());
assert!(reader.next().is_none());
}
#[test]
fn raw_file() {
let mut raw_file = RawFile {
data: Vec::new(),
inode: Inode::new(0, Ftyp::File, 0),
};
let slice = &[1, 2, 3, 4, 5];
assert_eq!(raw_file.write_at(0, &slice[..3]).unwrap(), 3);
assert_eq!(raw_file.data.as_slice(), &[1, 2, 3]);
let mut output = [0u8; 5];
assert_eq!(raw_file.read_at(0, &mut output).unwrap(), 3);
assert_eq!(&output, &[1, 2, 3, 0, 0]);
assert_eq!(raw_file.read_at(1, &mut output).unwrap(), 2);
assert_eq!(&output, &[2, 3, 3, 0, 0]);
assert_eq!(raw_file.read_at(2, &mut output).unwrap(), 1);
assert_eq!(&output, &[3, 3, 3, 0, 0]);
assert_eq!(raw_file.read_at(3, &mut output).unwrap(), 0);
assert_eq!(&output, &[3, 3, 3, 0, 0]);
assert_eq!(raw_file.write_at(1, &slice[..4]).unwrap(), 4);
assert_eq!(raw_file.data.as_slice(), &[1, 1, 2, 3, 4]);
assert_eq!(raw_file.write_at(1, slice).unwrap(), 5);
assert_eq!(raw_file.data.as_slice(), &[1, 1, 2, 3, 4, 5]);
assert_eq!(raw_file.read_at(1, &mut output).unwrap(), 5);
assert_eq!(&output, &[1, 2, 3, 4, 5]);
assert_eq!(raw_file.read_at(0, &mut output).unwrap(), 5);
assert_eq!(&output, &[1, 1, 2, 3, 4]);
assert_eq!(raw_file.inode.read().length, 6);
assert_eq!(raw_file.write_at(10, &slice[..1]).unwrap(), 1);
assert_eq!(raw_file.data.as_slice(), &[1, 1, 2, 3, 4, 5, 0, 0, 0, 0, 1]);
let mut output = [0u8; 2];
assert_eq!(raw_file.read_at(9, &mut output).unwrap(), 2);
assert_eq!(&output, &[0, 1]);
assert_eq!(raw_file.read_at(8, &mut output).unwrap(), 2);
assert_eq!(&output, &[0, 0]);
assert_eq!(raw_file.inode.read().length, 11);
}
#[test]
fn usage() {
let fs = FS::with_mode(0o300);
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("a/b/c").is_ok());
let mut wf =
fs.new_openopts().mode(0o600).write(true).create_new(true).open("a/f").unwrap();
assert_eq!(wf.write(vec![0, 1, 2, 3, 4, 5].as_slice()).unwrap(), 6);
assert_eq!(wf.seek(SeekFrom::Start(1)).unwrap(), 1);
assert_eq!(wf.write(vec![1, 2, 3].as_slice()).unwrap(), 3);
let mut rf = fs.new_openopts().read(true).open("a/f").unwrap();
assert_eq!(rf.seek(SeekFrom::Current(1)).unwrap(), 1);
let mut output = [0u8; 4];
assert_eq!(rf.read(&mut output).unwrap(), 4);
assert_eq!(&output, &[1, 2, 3, 4]);
assert_eq!(rf.seek(SeekFrom::End(-4)).unwrap(), 2);
assert_eq!(rf.read(&mut output).unwrap(), 4);
assert_eq!(&output, &[2, 3, 4, 5]);
let mut reader = fs.read_dir("a").unwrap();
let next = reader.next().unwrap().unwrap();
assert_eq!(next.file_name(), OsString::from("b"));
assert_eq!(next.path(), PathBuf::from("a/b"));
let next = reader.next().unwrap().unwrap();
assert_eq!(next.file_name(), OsString::from("f"));
assert_eq!(next.path(), PathBuf::from("a/f"));
assert!(reader.next().is_none());
}
#[test]
fn thread() {
let fs = FS::with_mode(0o300);
let (tx, rx) = mpsc::channel();
{
let fs = fs.clone();
thread::spawn(move || {
assert!(fs.new_dirbuilder().mode(0o700).recursive(true).create("a/b/c").is_ok());
tx.send(()).unwrap();
});
}
rx.recv().unwrap();
assert!(fs.metadata("a/b/c").is_ok());
}
#[test]
fn hard_link() {
let fs = FS::with_mode(0o300);
assert!(fs.new_openopts().mode(0o600).create(true).write(true).open("f").is_ok());
assert!(fs.symlink("f", "sl").is_ok());
assert!(fs.new_dirbuilder().mode(0).create("a").is_ok()); assert!(fs.create_file("b").is_ok()); assert!(fs.new_dirbuilder().mode(0).create("c").is_ok());
assert!(fs.create_dir("d").is_ok());
assert!(fs.create_file("d/f").is_ok());
assert!(fs.new_dirbuilder().mode(0o100).create("e").is_ok());
assert!(errs_eq(fs.hard_link("a/f", "z").unwrap_err(), EACCES()));
assert!(errs_eq(fs.hard_link("f", "b/f").unwrap_err(), ENOTDIR()));
assert!(errs_eq(fs.hard_link("f", "c/f").unwrap_err(), EACCES()));
assert!(errs_eq(fs.hard_link("z", "q").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.hard_link("f", "d/f").unwrap_err(), EEXIST()));
assert!(errs_eq(fs.hard_link("f", "e/f").unwrap_err(), EACCES()));
assert!(errs_eq(fs.hard_link("a", "z").unwrap_err(), EPERM()));
assert!(fs.hard_link("f", "z").is_ok());
assert!(fs.hard_link("sl", "ls").is_ok());
let mut f = fs.new_openopts().write(true).open("f").unwrap();
assert_eq!(f.write(vec![1, 2, 3].as_slice()).unwrap(), 3);
assert_eq!(fs.copy("f", "cpy").unwrap(), 3);
assert_eq!(fs.metadata("cpy").unwrap().permissions().mode(), 0o600);
assert_eq!(fs.copy("q", "d").unwrap_err().kind(), ErrorKind::InvalidInput);
assert_eq!(fs.copy("d", "d").unwrap_err().kind(), ErrorKind::InvalidInput);
let mut z = fs.new_openopts().write(true).append(true).open("z").unwrap(); {
let cursor = z.cursor.lock();
let file = cursor.file.read();
assert_eq!(file.data.as_slice(), &[1, 2, 3]);
}
assert_eq!(z.write(vec![1, 2, 3].as_slice()).unwrap(), 3);
{
let f = fs.open_file("ls").unwrap(); let cursor = f.cursor.lock();
let file = cursor.file.read();
assert_eq!(file.data.as_slice(), &[1, 2, 3, 1, 2, 3]);
}
{
let f = fs.open_file("cpy").unwrap(); let cursor = f.cursor.lock();
let file = cursor.file.read();
assert_eq!(file.data.as_slice(), &[1, 2, 3]);
}
{
let cursor = f.cursor.lock(); let file = cursor.file.read();
assert_eq!(file.data.as_slice(), &[1, 2, 3, 1, 2, 3]);
}
}
#[test]
fn symlink() {
let fs = FS::with_mode(0o300);
let exp = FS::with_mode(0o300);
assert!(fs.symlink("hello", "sl").is_ok());
assert!(exp.symlink("hello", "sl").is_ok());
assert!(fs.symlink("goodbye", "hello").is_ok());
assert!(exp.symlink("goodbye", "hello").is_ok());
assert!(fs.symlink("unexec", "u").is_ok());
assert!(exp.symlink("unexec", "u").is_ok());
assert!(fs.create_dir("goodbye").is_ok());
assert!(fs.symlink(".././a", "goodbye/sl").is_ok());
assert!(fs.create_dir("a").is_ok());
assert!(fs.create_dir("sl/sl/b").is_ok());
assert!(fs.create_file("sl/sl/f").is_ok());
assert!(exp.create_dir_all("a/b").is_ok());
assert!(exp.create_file("a/f").is_ok());
assert!(exp.create_dir("goodbye").is_ok());
assert!(exp.symlink(".././a", "goodbye/sl").is_ok());
assert!(fs == exp);
assert!(fs.new_dirbuilder().mode(0).create("unexec").is_ok());
assert!(exp.new_dirbuilder().mode(0).create("unexec").is_ok());
assert_eq!(fs.canonicalize("../././/sl/.//sl/b/../").unwrap(), PathBuf::from("/a"));
assert_eq!(fs.canonicalize("../").unwrap(), PathBuf::from("/"));
assert!(errs_eq(fs.canonicalize("").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.canonicalize("a/d/z").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.canonicalize("sl/sl/z").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.canonicalize("unexec/z").unwrap_err(), EACCES()));
assert!(errs_eq(fs.canonicalize("u/z").unwrap_err(), EACCES()));
assert!(fs.remove_dir_all("sl/sl/b/c").is_ok());
assert!(exp.remove_dir_all("a/b/c").is_ok());
assert!(fs == exp);
assert!(fs.remove_dir("sl/sl/b").is_ok());
assert!(exp.remove_dir("a/b").is_ok());
assert!(fs == exp);
assert_eq!(fs.read_link("sl").unwrap(), PathBuf::from("hello"));
assert_eq!(fs.read_link("sl/sl").unwrap(), PathBuf::from(".././a"));
assert!(errs_eq(fs.read_link("").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.read_link("..").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.read_link("unexec/a").unwrap_err(), EACCES()));
assert!(errs_eq(fs.read_link("u/a").unwrap_err(), EACCES()));
assert!(errs_eq(fs.read_link("goodbye").unwrap_err(), EINVAL()));
assert!(errs_eq(fs.read_link("goodbye/d").unwrap_err(), ENOENT()));
assert_eq!(fs.metadata("sl").unwrap().0, Inode::new(0o777, Ftyp::Dir, DIRLEN).view());
assert_eq!(fs.metadata("sl/sl").unwrap().0, Inode::new(0o777, Ftyp::Dir, DIRLEN).view());
assert_eq!(fs.metadata("sl/sl/f").unwrap().0, Inode::new(0o666, Ftyp::File, 0).view());
assert!(errs_eq(fs.metadata("u/f").unwrap_err(), EACCES()));
assert_eq!(fs.symlink_metadata("sl").unwrap().0, Inode::new(0o777, Ftyp::Symlink, 5).view());
assert_eq!(fs.symlink_metadata("sl/sl").unwrap().0, Inode::new(0o777, Ftyp::Symlink, 6).view());
assert_eq!(fs.symlink_metadata("..").unwrap().0, Inode::new(0o300, Ftyp::Dir, DIRLEN).view());
assert_eq!(fs.symlink_metadata("unexec").unwrap().0, Inode::new(0, Ftyp::Dir, DIRLEN).view());
assert_eq!(fs.symlink_metadata("a/f").unwrap().0, Inode::new(0o666, Ftyp::File, 0).view());
assert_eq!(fs.symlink_metadata("sl/sl/f").unwrap().0, Inode::new(0o666, Ftyp::File, 0).view());
assert!(errs_eq(fs.symlink_metadata("unexec/a").unwrap_err(), EACCES()));
assert!(errs_eq(fs.symlink_metadata("q").unwrap_err(), ENOENT()));
assert!(errs_eq(fs.symlink_metadata("").unwrap_err(), ENOENT()));
assert!(fs.set_permissions("sl/sl", Permissions::from_mode(0o700)).is_ok());
assert!(exp.set_permissions("a", Permissions::from_mode(0o700)).is_ok());
assert!(fs == exp);
let mut read = fs.read_dir("sl/sl").unwrap();
assert_eq!(read.next().unwrap().unwrap().path(), PathBuf::from("sl/sl/f"));
assert!(read.next().is_none());
assert!(fs.rename("sl/sl", "sl/longsl").is_ok());
assert!(exp.remove_file("sl/sl").is_ok());
assert!(exp.symlink(".././a", "sl/longsl").is_ok());
assert!(fs == exp);
assert!(fs.symlink("z", "z").is_ok());
assert!(errs_eq(fs.metadata("z/z").unwrap_err(), ELOOP())); assert!(errs_eq(fs.canonicalize("z").unwrap_err(), ELOOP()));
assert!(errs_eq(fs.metadata("z").unwrap_err(), ELOOP()));
assert!(errs_eq(fs.metadata("z").unwrap_err(), ELOOP()));
assert!(errs_eq(fs.read_dir("z").unwrap_err(), ELOOP()));
assert!(errs_eq(fs.set_permissions("z", Permissions::from_mode(0)).unwrap_err(), ELOOP()));
assert!(errs_eq(fs.open_file("z").unwrap_err(), ELOOP()));
assert!(exp.symlink("z", "z").is_ok());
assert!(fs == exp);
}
}