blob: 609e370be50d4fed4869dd9659898db7c6785024 [file] [log] [blame]
//! An in-memory implementation of Env.
#[cfg(feature = "mesalock_sgx")]
use std::prelude::v1::*;
use crate::env::{path_to_str, path_to_string, Env, FileLock, Logger, RandomAccess};
use crate::env_common::micros;
use crate::error::{err, Result, StatusCode};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::{self, Read, Write};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{Arc, SgxMutex as Mutex};
/// BufferBackedFile is a simple type implementing RandomAccess on a Vec<u8>.
pub type BufferBackedFile = Vec<u8>;
impl RandomAccess for BufferBackedFile {
fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<usize> {
if off > self.len() {
return Ok(0);
}
let remaining = self.len() - off;
let to_read = if dst.len() > remaining {
remaining
} else {
dst.len()
};
(&mut dst[0..to_read]).copy_from_slice(&self[off..off + to_read]);
Ok(to_read)
}
}
/// A MemFile holds a shared, concurrency-safe buffer. It can be shared among several
/// MemFileReaders and MemFileWriters, each with an independent offset.
#[derive(Clone)]
pub struct MemFile(Arc<Mutex<BufferBackedFile>>);
impl MemFile {
fn new() -> MemFile {
MemFile(Arc::new(Mutex::new(Vec::new())))
}
}
/// A MemFileReader holds a reference to a MemFile and a read offset.
struct MemFileReader(MemFile, usize);
impl MemFileReader {
fn new(f: MemFile, from: usize) -> MemFileReader {
MemFileReader(f, from)
}
}
// We need Read/Write/Seek implementations for our MemFile in order to work well with the
// concurrency requirements. It's very hard or even impossible to implement those traits just by
// wrapping MemFile in other types.
impl Read for MemFileReader {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
let buf = (self.0).0.lock().unwrap();
if self.1 >= buf.len() {
// EOF
return Ok(0);
}
let remaining = buf.len() - self.1;
let to_read = if dst.len() > remaining {
remaining
} else {
dst.len()
};
(&mut dst[0..to_read]).copy_from_slice(&buf[self.1..self.1 + to_read]);
self.1 += to_read;
Ok(to_read)
}
}
/// A MemFileWriter holds a reference to a MemFile and a write offset.
struct MemFileWriter(MemFile, usize);
impl MemFileWriter {
fn new(f: MemFile, append: bool) -> MemFileWriter {
let len = f.0.lock().unwrap().len();
MemFileWriter(f, if append { len } else { 0 })
}
}
impl Write for MemFileWriter {
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
let mut buf = (self.0).0.lock().unwrap();
// Write is append.
if self.1 == buf.len() {
buf.extend_from_slice(src);
} else {
// Write in the middle, possibly appending.
let remaining = buf.len() - self.1;
if src.len() <= remaining {
// src fits into buffer.
(&mut buf[self.1..self.1 + src.len()]).copy_from_slice(src);
} else {
// src doesn't fit; first copy what fits, then append the rest/
(&mut buf[self.1..self.1 + remaining]).copy_from_slice(&src[0..remaining]);
buf.extend_from_slice(&src[remaining..src.len()]);
}
}
self.1 += src.len();
Ok(src.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl RandomAccess for MemFile {
fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<usize> {
let guard = self.0.lock().unwrap();
let buf: &BufferBackedFile = guard.deref();
buf.read_at(off, dst)
}
}
struct MemFSEntry {
f: MemFile,
locked: bool,
}
/// MemFS implements a completely in-memory file system, both for testing and temporary in-memory
/// databases. It supports full concurrency.
pub struct MemFS {
store: Arc<Mutex<HashMap<String, MemFSEntry>>>,
}
impl MemFS {
fn new() -> MemFS {
MemFS {
store: Arc::new(Mutex::new(HashMap::new())),
}
}
/// Open a file. The caller can use the MemFile either inside a MemFileReader or as
/// RandomAccess.
fn open(&self, p: &Path, create: bool) -> Result<MemFile> {
let mut fs = self.store.lock().unwrap();
match fs.entry(path_to_string(p)) {
Entry::Occupied(o) => Ok(o.get().f.clone()),
Entry::Vacant(v) => {
if !create {
return err(
StatusCode::NotFound,
&format!("open: file not found: {}", path_to_str(p)),
);
}
let f = MemFile::new();
v.insert(MemFSEntry {
f: f.clone(),
locked: false,
});
Ok(f)
}
}
}
/// Open a file for writing.
fn open_w(&self, p: &Path, append: bool, truncate: bool) -> Result<Box<dyn Write>> {
let f = self.open(p, true)?;
if truncate {
f.0.lock().unwrap().clear();
}
Ok(Box::new(MemFileWriter::new(f, append)))
}
fn exists_(&self, p: &Path) -> Result<bool> {
let fs = self.store.lock()?;
Ok(fs.contains_key(path_to_str(p)))
}
fn children_of(&self, p: &Path) -> Result<Vec<PathBuf>> {
let fs = self.store.lock()?;
let mut prefix = path_to_string(p);
if !prefix.ends_with("/") {
prefix.push('/');
}
let mut children = Vec::new();
for k in fs.keys() {
if k.starts_with(&prefix) {
children.push(Path::new(k.trim_start_matches(&prefix)).to_owned());
}
}
Ok(children)
}
fn size_of_(&self, p: &Path) -> Result<usize> {
let mut fs = self.store.lock()?;
match fs.entry(path_to_string(p)) {
Entry::Occupied(o) => Ok(o.get().f.0.lock()?.len()),
_ => err(
StatusCode::NotFound,
&format!("size_of: file not found: {}", path_to_str(p)),
),
}
}
fn delete_(&self, p: &Path) -> Result<()> {
let mut fs = self.store.lock()?;
match fs.entry(path_to_string(p)) {
Entry::Occupied(o) => {
o.remove_entry();
Ok(())
}
_ => err(
StatusCode::NotFound,
&format!("delete: file not found: {}", path_to_str(p)),
),
}
}
fn rename_(&self, from: &Path, to: &Path) -> Result<()> {
let mut fs = self.store.lock()?;
match fs.remove(path_to_str(from)) {
Some(v) => {
fs.insert(path_to_string(to), v);
Ok(())
}
_ => err(
StatusCode::NotFound,
&format!("rename: file not found: {}", path_to_str(from)),
),
}
}
fn lock_(&self, p: &Path) -> Result<FileLock> {
let mut fs = self.store.lock()?;
match fs.entry(path_to_string(p)) {
Entry::Occupied(mut o) => {
if o.get().locked {
err(
StatusCode::LockError,
&format!("already locked: {}", path_to_str(p)),
)
} else {
o.get_mut().locked = true;
Ok(FileLock {
id: path_to_string(p),
})
}
}
Entry::Vacant(v) => {
let f = MemFile::new();
v.insert(MemFSEntry {
f: f.clone(),
locked: true,
});
Ok(FileLock {
id: path_to_string(p),
})
}
}
}
fn unlock_(&self, l: FileLock) -> Result<()> {
let mut fs = self.store.lock()?;
let id = l.id.clone();
match fs.entry(l.id) {
Entry::Occupied(mut o) => {
if !o.get().locked {
err(
StatusCode::LockError,
&format!("unlocking unlocked file: {}", id),
)
} else {
o.get_mut().locked = false;
Ok(())
}
}
_ => err(
StatusCode::NotFound,
&format!("unlock: file not found: {}", id),
),
}
}
}
/// MemEnv is an in-memory environment that can be used for testing or ephemeral databases. The
/// performance will be better than what a disk environment delivers.
pub struct MemEnv(MemFS);
impl MemEnv {
pub fn new() -> MemEnv {
MemEnv(MemFS::new())
}
}
impl Env for MemEnv {
fn open_sequential_file(&self, p: &Path) -> Result<Box<dyn Read>> {
let f = self.0.open(p, false)?;
Ok(Box::new(MemFileReader::new(f, 0)))
}
fn open_random_access_file(&self, p: &Path) -> Result<Box<dyn RandomAccess>> {
self.0
.open(p, false)
.map(|m| Box::new(m) as Box<dyn RandomAccess>)
}
fn open_writable_file(&self, p: &Path) -> Result<Box<dyn Write>> {
self.0.open_w(p, true, true)
}
fn open_appendable_file(&self, p: &Path) -> Result<Box<dyn Write>> {
self.0.open_w(p, true, false)
}
fn exists(&self, p: &Path) -> Result<bool> {
self.0.exists_(p)
}
fn children(&self, p: &Path) -> Result<Vec<PathBuf>> {
self.0.children_of(p)
}
fn size_of(&self, p: &Path) -> Result<usize> {
self.0.size_of_(p)
}
fn delete(&self, p: &Path) -> Result<()> {
self.0.delete_(p)
}
fn mkdir(&self, p: &Path) -> Result<()> {
if self.exists(p)? {
err(StatusCode::AlreadyExists, "")
} else {
Ok(())
}
}
fn rmdir(&self, p: &Path) -> Result<()> {
if !self.exists(p)? {
err(StatusCode::NotFound, "")
} else {
Ok(())
}
}
fn rename(&self, old: &Path, new: &Path) -> Result<()> {
self.0.rename_(old, new)
}
fn lock(&self, p: &Path) -> Result<FileLock> {
self.0.lock_(p)
}
fn unlock(&self, p: FileLock) -> Result<()> {
self.0.unlock_(p)
}
fn micros(&self) -> u64 {
micros()
}
fn new_logger(&self, p: &Path) -> Result<Logger> {
self.open_appendable_file(p)
.map(|dst| Logger::new(Box::new(dst)))
}
}
#[cfg(feature = "enclave_unit_test")]
pub mod tests {
use super::*;
use crate::env;
use teaclave_test_utils::*;
pub fn run_tests() -> bool {
run_tests!(
test_mem_fs_memfile_read,
test_mem_fs_memfile_write,
test_mem_fs_memfile_readat,
test_mem_fs_open_read_write,
test_mem_fs_open_read_write_append_truncate,
test_mem_fs_metadata_operations,
test_mem_fs_children,
test_mem_fs_lock,
test_memenv_all,
)
}
fn new_memfile(v: Vec<u8>) -> MemFile {
MemFile(Arc::new(Mutex::new(v)))
}
fn test_mem_fs_memfile_read() {
let f = new_memfile(vec![1, 2, 3, 4, 5, 6, 7, 8]);
let mut buf: [u8; 1] = [0];
let mut reader = MemFileReader(f, 0);
for i in [1, 2, 3, 4, 5, 6, 7, 8].iter() {
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(buf, [*i]);
}
}
fn test_mem_fs_memfile_write() {
let f = new_memfile(vec![]);
let mut w1 = MemFileWriter::new(f.clone(), false);
assert_eq!(w1.write(&[1, 2, 3]).unwrap(), 3);
let mut w2 = MemFileWriter::new(f, true);
assert_eq!(w1.write(&[1, 7, 8, 9]).unwrap(), 4);
assert_eq!(w2.write(&[4, 5, 6]).unwrap(), 3);
assert_eq!(
(w1.0).0.lock().unwrap().as_ref() as &Vec<u8>,
&[1, 2, 3, 4, 5, 6, 9]
);
}
fn test_mem_fs_memfile_readat() {
let f = new_memfile(vec![1, 2, 3, 4, 5]);
let mut buf = [0; 3];
assert_eq!(f.read_at(2, &mut buf).unwrap(), 3);
assert_eq!(buf, [3, 4, 5]);
assert_eq!(f.read_at(0, &mut buf[0..1]).unwrap(), 1);
assert_eq!(buf, [1, 4, 5]);
assert_eq!(f.read_at(5, &mut buf).unwrap(), 0);
assert_eq!(buf, [1, 4, 5]);
let mut buf2 = [0; 6];
assert_eq!(f.read_at(0, &mut buf2[0..5]).unwrap(), 5);
assert_eq!(buf2, [1, 2, 3, 4, 5, 0]);
assert_eq!(f.read_at(0, &mut buf2[0..6]).unwrap(), 5);
assert_eq!(buf2, [1, 2, 3, 4, 5, 0]);
}
fn test_mem_fs_open_read_write() {
let fs = MemFS::new();
let path = Path::new("/a/b/hello.txt");
{
let mut w = fs.open_w(&path, false, false).unwrap();
write!(w, "Hello").unwrap();
// Append.
let mut w2 = fs.open_w(&path, true, false).unwrap();
write!(w2, "World").unwrap();
}
{
let mut r = MemFileReader::new(fs.open(&path, false).unwrap(), 0);
let mut s = String::new();
assert_eq!(r.read_to_string(&mut s).unwrap(), 10);
assert_eq!(s, "HelloWorld");
let mut r2 = MemFileReader::new(fs.open(&path, false).unwrap(), 2);
s.clear();
assert_eq!(r2.read_to_string(&mut s).unwrap(), 8);
assert_eq!(s, "lloWorld");
}
assert_eq!(fs.size_of_(&path).unwrap(), 10);
assert!(fs.exists_(&path).unwrap());
assert!(!fs.exists_(&Path::new("/non/existing/path")).unwrap());
}
fn test_mem_fs_open_read_write_append_truncate() {
let fs = MemFS::new();
let path = Path::new("/a/b/hello.txt");
{
let mut w0 = fs.open_w(&path, false, true).unwrap();
write!(w0, "Garbage").unwrap();
// Truncate.
let mut w = fs.open_w(&path, false, true).unwrap();
write!(w, "Xyz").unwrap();
// Write to the beginning.
let mut w2 = fs.open_w(&path, false, false).unwrap();
write!(w2, "OverwritingEverythingWithGarbage").unwrap();
// Overwrite the overwritten stuff.
write!(w, "Xyz").unwrap();
assert!(w.flush().is_ok());
}
{
let mut r = MemFileReader::new(fs.open(&path, false).unwrap(), 0);
let mut s = String::new();
assert_eq!(r.read_to_string(&mut s).unwrap(), 32);
assert_eq!(s, "OveXyzitingEverythingWithGarbage");
}
assert!(fs.exists_(&path).unwrap());
assert_eq!(fs.size_of_(&path).unwrap(), 32);
assert!(!fs.exists_(&Path::new("/non/existing/path")).unwrap());
}
fn test_mem_fs_metadata_operations() {
let fs = MemFS::new();
let path = Path::new("/a/b/hello.file");
let newpath = Path::new("/a/b/hello2.file");
let nonexist = Path::new("/blah");
// Make file/remove file.
{
let mut w = fs.open_w(&path, false, false).unwrap();
write!(w, "Hello").unwrap();
}
assert!(fs.exists_(&path).unwrap());
assert_eq!(fs.size_of_(&path).unwrap(), 5);
fs.delete_(&path).unwrap();
assert!(!fs.exists_(&path).unwrap());
assert!(fs.delete_(&nonexist).is_err());
// rename_ file.
{
let mut w = fs.open_w(&path, false, false).unwrap();
write!(w, "Hello").unwrap();
}
assert!(fs.exists_(&path).unwrap());
assert!(!fs.exists_(&newpath).unwrap());
assert_eq!(fs.size_of_(&path).unwrap(), 5);
assert!(fs.size_of_(&newpath).is_err());
fs.rename_(&path, &newpath).unwrap();
assert!(!fs.exists_(&path).unwrap());
assert!(fs.exists_(&newpath).unwrap());
assert_eq!(fs.size_of_(&newpath).unwrap(), 5);
assert!(fs.size_of_(&path).is_err());
assert!(fs.rename_(&nonexist, &path).is_err());
}
fn s2p(x: &str) -> PathBuf {
Path::new(x).to_owned()
}
fn test_mem_fs_children() {
let fs = MemFS::new();
let (path1, path2, path3) = (
Path::new("/a/1.txt"),
Path::new("/a/2.txt"),
Path::new("/b/1.txt"),
);
for p in &[&path1, &path2, &path3] {
fs.open_w(*p, false, false).unwrap();
}
let children = fs.children_of(&Path::new("/a")).unwrap();
assert!(
(children == vec![s2p("1.txt"), s2p("2.txt")])
|| (children == vec![s2p("2.txt"), s2p("1.txt")])
);
let children = fs.children_of(&Path::new("/a/")).unwrap();
assert!(
(children == vec![s2p("1.txt"), s2p("2.txt")])
|| (children == vec![s2p("2.txt"), s2p("1.txt")])
);
}
fn test_mem_fs_lock() {
let fs = MemFS::new();
let p = Path::new("/a/lock");
{
let mut f = fs.open_w(p, true, true).unwrap();
f.write("abcdef".as_bytes()).expect("write failed");
}
// Locking on new file.
let lock = fs.lock_(p).unwrap();
assert!(fs.lock_(p).is_err());
// Unlock of locked file is ok.
assert!(fs.unlock_(lock).is_ok());
// Lock of unlocked file is ok.
let lock = fs.lock_(p).unwrap();
assert!(fs.lock_(p).is_err());
assert!(fs.unlock_(lock).is_ok());
// Rogue operation.
assert!(fs
.unlock_(env::FileLock {
id: "/a/lock".to_string(),
})
.is_err());
// Non-existent files.
let p2 = Path::new("/a/lock2");
assert!(fs.lock_(p2).is_ok());
assert!(fs
.unlock_(env::FileLock {
id: "/a/lock2".to_string(),
})
.is_ok());
}
fn test_memenv_all() {
let me = MemEnv::new();
let (p1, p2, p3) = (Path::new("/a/b"), Path::new("/a/c"), Path::new("/a/d"));
let nonexist = Path::new("/x/y");
me.open_writable_file(p2).unwrap();
me.open_appendable_file(p3).unwrap();
me.open_sequential_file(p2).unwrap();
me.open_random_access_file(p3).unwrap();
assert!(me.exists(p2).unwrap());
assert_eq!(me.children(Path::new("/a/")).unwrap().len(), 2);
assert_eq!(me.size_of(p2).unwrap(), 0);
me.delete(p2).unwrap();
assert!(me.mkdir(p3).is_err());
me.mkdir(p1).unwrap();
me.rmdir(p3).unwrap();
assert!(me.rmdir(nonexist).is_err());
me.open_writable_file(p1).unwrap();
me.rename(p1, p3).unwrap();
assert!(!me.exists(p1).unwrap());
assert!(me.rename(nonexist, p1).is_err());
me.unlock(me.lock(p3).unwrap()).unwrap();
assert!(me.lock(nonexist).is_ok());
me.new_logger(p1).unwrap();
assert!(me.micros() > 0);
}
}