2019-03-13 14:22:32 -07:00
|
|
|
use zbox::{init_env, RepoOpener, Repo, OpenOptions};
|
2019-03-12 10:39:48 -07:00
|
|
|
use std::io::Read;
|
|
|
|
use std::path::{Path, PathBuf};
|
2019-03-13 14:22:32 -07:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::io;
|
2019-03-12 10:39:48 -07:00
|
|
|
|
|
|
|
pub type Fd = isize;
|
|
|
|
|
2019-03-13 14:22:32 -07:00
|
|
|
pub struct Vfs {
|
|
|
|
pub repo: Repo,
|
|
|
|
pub fd_map: BTreeMap<Fd, zbox::File>, // best because we look for lowest fd
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
|
|
|
|
2019-03-13 14:22:32 -07:00
|
|
|
impl Vfs {
|
2019-03-12 10:39:48 -07:00
|
|
|
/// Like `VfsBacking::from_tar_bytes` except it also decompresses from the zstd format.
|
|
|
|
pub fn from_tar_zstd_bytes<Reader: Read>(tar_bytes: Reader) -> Result<Self, failure::Error> {
|
|
|
|
let result = zstd::decode_all(tar_bytes);
|
|
|
|
let decompressed_data = result.unwrap();
|
2019-03-13 14:22:32 -07:00
|
|
|
Vfs::from_tar_bytes(&decompressed_data[..])
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a vfs from raw bytes in tar format
|
|
|
|
pub fn from_tar_bytes<Reader: Read>(tar_bytes: Reader) -> Result<Self, failure::Error> {
|
|
|
|
let mut ar = tar::Archive::new(tar_bytes);
|
2019-03-13 14:22:32 -07:00
|
|
|
init_env();
|
|
|
|
let mut repo = RepoOpener::new().create(true).open("mem://wasmer_fs", "").unwrap();
|
2019-03-12 10:39:48 -07:00
|
|
|
for entry in ar.entries()? {
|
|
|
|
let mut entry = entry?;
|
2019-03-13 14:22:32 -07:00
|
|
|
let path = convert_to_absolute_path(entry.path().unwrap());
|
|
|
|
let mut file = OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.open(&mut repo, path)?;
|
|
|
|
io::copy(&mut entry, &mut file)?;
|
|
|
|
file.finish().unwrap();
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs = Vfs {
|
|
|
|
repo,
|
|
|
|
fd_map: BTreeMap::new(),
|
2019-03-12 10:39:48 -07:00
|
|
|
};
|
|
|
|
Ok(vfs)
|
|
|
|
}
|
2019-03-13 14:22:32 -07:00
|
|
|
|
|
|
|
/// like read(2), will read the data for the file descriptor
|
|
|
|
pub fn read_file(
|
|
|
|
&mut self,
|
|
|
|
fd: Fd,
|
|
|
|
buf: &mut [u8],
|
|
|
|
) -> Result<usize, failure::Error> {
|
|
|
|
self.fd_map
|
|
|
|
.get_mut(&fd)
|
|
|
|
.ok_or(VfsError::FileDescriptorNotExist)?
|
|
|
|
.read(buf)
|
|
|
|
.map_err(|e| e.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// like open(2), creates a file descriptor for the path if it exists
|
|
|
|
pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> Result<Fd, failure::Error> {
|
|
|
|
let mut repo = &mut self.repo;
|
|
|
|
let path = convert_to_absolute_path(path);
|
|
|
|
let file = OpenOptions::new().open(&mut repo, path)?;
|
|
|
|
let fd = if self.fd_map.len() == 0 {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let fd = *match self.fd_map.keys().max() {
|
|
|
|
Some(fd) => fd,
|
|
|
|
None => return Err(VfsError::CouldNotGetNextLowestFileDescriptor.into())
|
|
|
|
};
|
|
|
|
fd + 1
|
|
|
|
};
|
|
|
|
self.fd_map.insert(fd, file);
|
|
|
|
Ok(fd)
|
|
|
|
}
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Fail)]
|
2019-03-13 14:22:32 -07:00
|
|
|
pub enum VfsError {
|
2019-03-12 10:39:48 -07:00
|
|
|
#[fail(display = "File descriptor does not exist.")]
|
|
|
|
FileDescriptorNotExist,
|
2019-03-13 14:22:32 -07:00
|
|
|
#[fail(display = "Error when trying to read maximum file descriptor.")]
|
|
|
|
CouldNotGetNextLowestFileDescriptor,
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
|
|
|
|
2019-03-13 14:22:32 -07:00
|
|
|
|
|
|
|
fn convert_to_absolute_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
|
|
|
let path = path.as_ref();
|
|
|
|
if path.is_relative() {
|
|
|
|
std::path::PathBuf::from("/").join(path)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
path.to_path_buf()
|
|
|
|
}
|
2019-03-12 10:39:48 -07:00
|
|
|
}
|
|
|
|
|
2019-03-13 14:22:32 -07:00
|
|
|
|
2019-03-12 10:39:48 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod open_test {
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
2019-03-13 14:22:32 -07:00
|
|
|
use crate::vfs::vfs::Vfs;
|
2019-03-12 10:39:48 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn open_files() {
|
|
|
|
// SETUP: create temp dir and files
|
|
|
|
let tmp_dir = tempdir::TempDir::new("open_files").unwrap();
|
|
|
|
let file_path = tmp_dir.path().join("foo.txt");
|
|
|
|
let mut tmp_file = File::create(file_path.clone()).unwrap();
|
|
|
|
writeln!(tmp_file, "foo foo foo").unwrap();
|
|
|
|
let tar_data = vec![];
|
|
|
|
let mut ar = tar::Builder::new(tar_data);
|
|
|
|
ar.append_path_with_name(file_path, "foo.txt").unwrap();
|
|
|
|
let archive = ar.into_inner().unwrap();
|
|
|
|
// SETUP: create virtual filesystem with tar data
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
2019-03-12 10:39:48 -07:00
|
|
|
// ASSERT:
|
|
|
|
assert!(
|
|
|
|
vfs_result.is_ok(),
|
2019-03-12 12:01:45 -07:00
|
|
|
"Failed to create file system from archive"
|
2019-03-12 10:39:48 -07:00
|
|
|
);
|
|
|
|
let mut vfs = vfs_result.unwrap();
|
|
|
|
// open the file, get a file descriptor
|
|
|
|
let open_result = vfs.open_file("foo.txt");
|
|
|
|
assert!(
|
|
|
|
open_result.is_ok(),
|
|
|
|
"Failed to open file in the virtual filesystem."
|
|
|
|
);
|
|
|
|
// open the same file twice, and expect different descriptors
|
|
|
|
let fd_1 = open_result.unwrap();
|
|
|
|
let open_result_2 = vfs.open_file("foo.txt");
|
|
|
|
assert!(
|
|
|
|
open_result_2.is_ok(),
|
|
|
|
"Failed to open the same file twice in the virtual filesystem."
|
|
|
|
);
|
|
|
|
let fd_2 = open_result_2.unwrap();
|
|
|
|
assert_ne!(fd_1, fd_2, "Open produced the same file descriptor twice.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn open_non_existent_file() {
|
|
|
|
// SETUP: create temp dir and files
|
|
|
|
let tmp_dir = tempdir::TempDir::new("open_non_existent_file").unwrap();
|
|
|
|
let file_path = tmp_dir.path().join("foo.txt");
|
|
|
|
let mut tmp_file = File::create(file_path.clone()).unwrap();
|
|
|
|
writeln!(tmp_file, "foo foo foo").unwrap();
|
|
|
|
let tar_data = vec![];
|
|
|
|
let mut ar = tar::Builder::new(tar_data);
|
|
|
|
ar.append_path_with_name(file_path, "foo.txt").unwrap();
|
|
|
|
let archive = ar.into_inner().unwrap();
|
|
|
|
// SETUP: create virtual filesystem with tar data
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
2019-03-12 10:39:48 -07:00
|
|
|
// ASSERT:
|
|
|
|
assert!(
|
|
|
|
vfs_result.is_ok(),
|
2019-03-12 12:01:45 -07:00
|
|
|
"Failed to create file system from archive"
|
2019-03-12 10:39:48 -07:00
|
|
|
);
|
|
|
|
let mut vfs = vfs_result.unwrap();
|
|
|
|
// read the file
|
|
|
|
let open_result = vfs.open_file("foo.txt");
|
|
|
|
assert!(open_result.is_ok(), "Failed to read file from vfs");
|
|
|
|
// open a non-existent file
|
|
|
|
let open_result_2 = vfs.open_file("bar.txt");
|
|
|
|
assert!(
|
|
|
|
open_result_2.is_err(),
|
|
|
|
"Somehow opened a non-existent file."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod read_test {
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
|
|
|
use tempdir;
|
2019-03-13 14:22:32 -07:00
|
|
|
use crate::vfs::vfs::Vfs;
|
2019-03-12 10:39:48 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn empty_archive() {
|
|
|
|
// SETUP: create temp dir and files
|
|
|
|
let empty_archive = vec![];
|
|
|
|
// SETUP: create virtual filesystem with tar data
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs_result = Vfs::from_tar_bytes(&empty_archive[..]);
|
2019-03-12 10:39:48 -07:00
|
|
|
// ASSERT:
|
|
|
|
assert!(
|
|
|
|
vfs_result.is_ok(),
|
|
|
|
"Failed to create file system from empty archive"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn single_file_archive() {
|
|
|
|
// SETUP: create temp dir and files
|
|
|
|
let tmp_dir = tempdir::TempDir::new("single_file_archive").unwrap();
|
|
|
|
let foo_file_path = tmp_dir.path().join("foo.txt");
|
|
|
|
let mut foo_tmp_file = File::create(foo_file_path.clone()).unwrap();
|
|
|
|
writeln!(foo_tmp_file, "foo foo foo").unwrap();
|
|
|
|
let tar_data = vec![];
|
|
|
|
let mut ar = tar::Builder::new(tar_data);
|
|
|
|
ar.append_path_with_name(foo_file_path, "foo.txt").unwrap();
|
|
|
|
let archive = ar.into_inner().unwrap();
|
|
|
|
// SETUP: create virtual filesystem with tar data
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
2019-03-12 10:39:48 -07:00
|
|
|
// ASSERT:
|
|
|
|
assert!(
|
|
|
|
vfs_result.is_ok(),
|
2019-03-12 12:01:45 -07:00
|
|
|
"Failed to create file system from archive"
|
2019-03-12 10:39:48 -07:00
|
|
|
);
|
|
|
|
let mut vfs = vfs_result.unwrap();
|
|
|
|
// read the file
|
|
|
|
let fd = vfs.open_file("foo.txt").unwrap();
|
2019-03-13 14:22:32 -07:00
|
|
|
let mut actual_data: [u8; 12] = [0; 12];
|
2019-03-12 10:39:48 -07:00
|
|
|
let read_result = vfs.read_file(fd, &mut actual_data);
|
|
|
|
assert!(read_result.is_ok(), "Failed to read file from vfs");
|
|
|
|
let expected_data = "foo foo foo\n".as_bytes();
|
|
|
|
assert_eq!(actual_data, expected_data, "Contents were not equal");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn two_files_in_archive() {
|
|
|
|
// SETUP: create temp dir and files
|
|
|
|
let tmp_dir = tempdir::TempDir::new("two_files_in_archive").unwrap();
|
|
|
|
let foo_file_path = tmp_dir.path().join("foo.txt");
|
|
|
|
let bar_file_path = tmp_dir.path().join("bar.txt");
|
|
|
|
let mut foo_tmp_file = File::create(foo_file_path.clone()).unwrap();
|
|
|
|
let mut bar_tmp_file = File::create(bar_file_path.clone()).unwrap();
|
|
|
|
writeln!(foo_tmp_file, "foo foo foo").unwrap();
|
|
|
|
writeln!(bar_tmp_file, "bar bar").unwrap();
|
|
|
|
let tar_data = vec![];
|
|
|
|
let mut ar = tar::Builder::new(tar_data);
|
|
|
|
ar.append_path_with_name(foo_file_path, "foo.txt").unwrap();
|
|
|
|
ar.append_path_with_name(bar_file_path, "bar.txt").unwrap();
|
|
|
|
let archive = ar.into_inner().unwrap();
|
|
|
|
// SETUP: create virtual filesystem with tar data
|
2019-03-13 14:22:32 -07:00
|
|
|
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
2019-03-12 10:39:48 -07:00
|
|
|
// ASSERT:
|
|
|
|
assert!(
|
|
|
|
vfs_result.is_ok(),
|
2019-03-12 12:01:45 -07:00
|
|
|
"Failed to create file system from archive"
|
2019-03-12 10:39:48 -07:00
|
|
|
);
|
|
|
|
let mut vfs = vfs_result.unwrap();
|
|
|
|
// read the file
|
|
|
|
let foo_fd = vfs.open_file("foo.txt").unwrap();
|
|
|
|
let bar_fd = vfs.open_file("bar.txt").unwrap();
|
2019-03-13 14:22:32 -07:00
|
|
|
let mut foo_actual_data: [u8; 12] = [0; 12];
|
2019-03-12 10:39:48 -07:00
|
|
|
let foo_read_result = vfs.read_file(foo_fd, &mut foo_actual_data);
|
2019-03-13 14:22:32 -07:00
|
|
|
let mut bar_actual_data: [u8; 8] = [0; 8];
|
2019-03-12 10:39:48 -07:00
|
|
|
let bar_read_result = vfs.read_file(bar_fd, &mut bar_actual_data);
|
|
|
|
assert!(foo_read_result.is_ok(), "Failed to read foo.txt from vfs");
|
|
|
|
assert!(bar_read_result.is_ok(), "Failed to read bar.txt from vfs");
|
2019-03-13 14:22:32 -07:00
|
|
|
let foo_expected_data: &[u8; 12] = b"foo foo foo\n";
|
|
|
|
let bar_expected_data: &[u8; 8] = b"bar bar\n";
|
2019-03-12 10:39:48 -07:00
|
|
|
assert_eq!(
|
2019-03-13 14:22:32 -07:00
|
|
|
&foo_actual_data, foo_expected_data,
|
2019-03-12 10:39:48 -07:00
|
|
|
"Contents of `foo.txt` is not correct"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-13 14:22:32 -07:00
|
|
|
&bar_actual_data, bar_expected_data,
|
2019-03-12 10:39:48 -07:00
|
|
|
"Contents of `bar.txt` is not correct"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|