mirror of
https://github.com/fluencelabs/wasmer
synced 2024-12-05 02:20:19 +00:00
enhance vfs, capture errors, more methods to support emscripten
create vfs module guarded by flag for emscripten vfs behavior cleanup warning fixes wip emscripten vfs wip metadata for virtual file make an fd an i32 fix the feature flag for llvm on emscripten more wip wip add logging and fix one logical error
This commit is contained in:
parent
edacb0a8a7
commit
b4c890814e
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -93,6 +93,11 @@ dependencies = [
|
||||
"which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
@ -1490,6 +1495,7 @@ dependencies = [
|
||||
name = "wasmer"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wabt 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1529,7 +1535,9 @@ dependencies = [
|
||||
name = "wasmer-emscripten"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1780,6 +1788,7 @@ dependencies = [
|
||||
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
|
||||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum bindgen 0.46.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f7f7f0701772b17de73e4f5cbcb1dd6926f4706cba4c1ab62c5367f8bdc94e1"
|
||||
"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce2571a6cd634670daa2977cc894c1cc2ba57c563c498e5a82c35446f34d056e"
|
||||
"checksum blob 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19803aa44ff8b43123bbe369efaddcb638ea7dc332e543972dd95ac7cb148b92"
|
||||
|
@ -26,6 +26,7 @@ wasmer-runtime = { path = "lib/runtime" }
|
||||
wasmer-runtime-core = { path = "lib/runtime-core" }
|
||||
wasmer-emscripten = { path = "lib/emscripten" }
|
||||
wasmer-runtime-abi = { path = "lib/runtime-abi", optional = true }
|
||||
errno = "0.2.4"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
wasmer-llvm-backend = { path = "lib/llvm-backend", optional = true }
|
||||
|
@ -10,12 +10,17 @@ build = "build/mod.rs"
|
||||
|
||||
[dependencies]
|
||||
wasmer-runtime-core = { path = "../runtime-core", version = "0.2.1" }
|
||||
wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0" }
|
||||
wasmer-runtime-abi = { path = "../runtime-abi", optional = true }
|
||||
lazy_static = "1.2.0"
|
||||
libc = "0.2.49"
|
||||
byteorder = "1"
|
||||
time = "0.1.41"
|
||||
errno = "0.2.4"
|
||||
bit_field = "0.9.0"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0", optional = true }
|
||||
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.1.0", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
rand = "0.6"
|
||||
@ -23,13 +28,10 @@ rand = "0.6"
|
||||
[dev-dependencies]
|
||||
wabt = "0.7.2"
|
||||
|
||||
[target.'cfg(not(windows))'.dev-dependencies]
|
||||
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.1.0" }
|
||||
|
||||
[build-dependencies]
|
||||
glob = "0.2.11"
|
||||
|
||||
[features]
|
||||
clif = []
|
||||
llvm = []
|
||||
clif = ["wasmer-clif-backend"]
|
||||
llvm = ["wasmer-llvm-backend"]
|
||||
vfs = ["wasmer-runtime-abi"]
|
||||
|
27
lib/emscripten/src/env/mod.rs
vendored
27
lib/emscripten/src/env/mod.rs
vendored
@ -75,3 +75,30 @@ pub fn ___assert_fail(_ctx: &mut Ctx, _a: c_int, _b: c_int, _c: c_int, _d: c_int
|
||||
// TODO: Implement like emscripten expects regarding memory/page size
|
||||
// TODO raise an error
|
||||
}
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn _getgrnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
debug!("emscripten::_getgrnam {}", name_ptr);
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let _ = name_ptr;
|
||||
|
||||
#[repr(C)]
|
||||
struct GuestGroup {
|
||||
gr_name: u32,
|
||||
gr_passwd: u32,
|
||||
gr_gid: u32,
|
||||
gr_mem: u32,
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let group_struct_offset = call_malloc(ctx, std::mem::size_of::<GuestGroup>() as _);
|
||||
let group_struct_ptr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), group_struct_offset) as *mut GuestGroup;
|
||||
(*group_struct_ptr).gr_name = 0;
|
||||
(*group_struct_ptr).gr_passwd = 0;
|
||||
(*group_struct_ptr).gr_gid = 0;
|
||||
(*group_struct_ptr).gr_mem = 0;
|
||||
group_struct_offset as c_int
|
||||
}
|
||||
}
|
||||
|
32
lib/emscripten/src/env/unix/mod.rs
vendored
32
lib/emscripten/src/env/unix/mod.rs
vendored
@ -1,14 +1,11 @@
|
||||
/// NOTE: These syscalls only support wasm_32 for now because they take u32 offset
|
||||
use libc::{
|
||||
c_int, getenv, getgrnam as libc_getgrnam, getpwnam as libc_getpwnam, putenv, setenv, sysconf,
|
||||
unsetenv,
|
||||
};
|
||||
use libc::{c_int, getenv, getpwnam as libc_getpwnam, putenv, setenv, sysconf, unsetenv};
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use crate::env::call_malloc;
|
||||
use crate::utils::{copy_cstr_into_wasm, copy_terminated_array_of_cstrs};
|
||||
use crate::utils::copy_cstr_into_wasm;
|
||||
use wasmer_runtime_core::vm::Ctx;
|
||||
|
||||
// #[no_mangle]
|
||||
@ -103,6 +100,7 @@ pub fn _getpwnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn _getgrnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
debug!("emscripten::_getgrnam {}", name_ptr);
|
||||
@ -121,7 +119,7 @@ pub fn _getgrnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let group = &*libc_getgrnam(name.as_ptr());
|
||||
let group = &*libc::getgrnam(name.as_ptr());
|
||||
let group_struct_offset = call_malloc(ctx, mem::size_of::<GuestGroup>() as _);
|
||||
|
||||
let group_struct_ptr =
|
||||
@ -140,3 +138,25 @@ pub fn _sysconf(_ctx: &mut Ctx, name: c_int) -> i32 {
|
||||
// TODO: Implement like emscripten expects regarding memory/page size
|
||||
unsafe { sysconf(name) as i32 } // TODO review i64
|
||||
}
|
||||
|
||||
pub fn _initgroups(_ctx: &mut Ctx, user_offset: u32, gid: u32) -> c_int {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
unsafe fn copy_terminated_array_of_cstrs(_ctx: &mut Ctx, cstrs: *mut *mut c_char) -> u32 {
|
||||
let _total_num = {
|
||||
let mut ptr = cstrs;
|
||||
let mut counter = 0;
|
||||
while !(*ptr).is_null() {
|
||||
counter += 1;
|
||||
ptr = ptr.add(1);
|
||||
}
|
||||
counter
|
||||
};
|
||||
debug!(
|
||||
"emscripten::copy_terminated_array_of_cstrs::total_num: {}",
|
||||
_total_num
|
||||
);
|
||||
0
|
||||
}
|
||||
|
1
lib/emscripten/src/env/windows/mod.rs
vendored
1
lib/emscripten/src/env/windows/mod.rs
vendored
@ -97,6 +97,7 @@ pub fn _getpwnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn _getgrnam(ctx: &mut Ctx, name_ptr: c_int) -> c_int {
|
||||
debug!("emscripten::_getgrnam {}", name_ptr);
|
||||
|
@ -54,6 +54,9 @@ pub use self::utils::{
|
||||
get_emscripten_table_size, is_emscripten_module,
|
||||
};
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
use crate::syscalls::EmscriptenVfs;
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
use wasmer_runtime_abi::vfs::vfs::Vfs;
|
||||
|
||||
@ -122,7 +125,7 @@ pub struct EmscriptenData<'a> {
|
||||
pub dyn_call_vijj: Option<Func<'a, (i32, i32, i32, i32, i32, i32)>>,
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
pub vfs: Option<Vfs>,
|
||||
pub vfs: Option<EmscriptenVfs>,
|
||||
}
|
||||
|
||||
impl<'a> EmscriptenData<'a> {
|
||||
@ -241,8 +244,11 @@ pub fn run_emscripten_instance(
|
||||
{
|
||||
data.vfs = match module.info().custom_sections.get("wasmer:fs") {
|
||||
Some(bytes) => match Vfs::from_compressed_bytes(&bytes[..]) {
|
||||
Ok(vfs_backing) => Some(vfs_backing),
|
||||
Err(e) => None,
|
||||
Ok(vfs) => {
|
||||
let emscripten_vfs = EmscriptenVfs::new(vfs);
|
||||
Some(emscripten_vfs)
|
||||
}
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
@ -459,6 +465,7 @@ pub fn generate_emscripten_env(globals: &mut EmscriptenGlobals) -> ImportObject
|
||||
"_getpagesize" => func!(crate::env::_getpagesize),
|
||||
"_sysconf" => func!(crate::env::_sysconf),
|
||||
"_getaddrinfo" => func!(crate::env::_getaddrinfo),
|
||||
"_initgroups" => func!(crate::env::_initgroups),
|
||||
|
||||
// Null func
|
||||
"nullFunc_i" => func!(crate::nullfunc::nullfunc_i),
|
||||
|
144
lib/emscripten/src/syscalls/emscripten_vfs.rs
Normal file
144
lib/emscripten/src/syscalls/emscripten_vfs.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use crate::syscalls::emscripten_vfs::FileHandle::{Socket, VirtualFile};
|
||||
use crate::varargs::VarArgs;
|
||||
use std::cmp::{Eq, Ord, Ordering, PartialEq};
|
||||
use std::collections::BTreeMap;
|
||||
use std::env::home_dir;
|
||||
use std::fmt::Display;
|
||||
use wasmer_runtime_abi::vfs::device_file;
|
||||
use wasmer_runtime_abi::vfs::vfs::Vfs;
|
||||
|
||||
pub type Fd = i32;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VirtualFd(pub Fd);
|
||||
|
||||
impl Ord for VirtualFd {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VirtualFd {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.0.cmp(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for VirtualFd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for VirtualFd {}
|
||||
|
||||
impl Display for VirtualFd {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "Fd({})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileHandle {
|
||||
VirtualFile(Fd),
|
||||
Socket(Fd),
|
||||
}
|
||||
|
||||
pub type Map<K, V> = BTreeMap<K, V>;
|
||||
pub type FdMap = Map<VirtualFd, FileHandle>;
|
||||
|
||||
pub struct EmscriptenVfs {
|
||||
pub fd_map: FdMap,
|
||||
pub vfs: Vfs,
|
||||
}
|
||||
|
||||
impl EmscriptenVfs {
|
||||
pub fn new(vfs: Vfs) -> Self {
|
||||
let mut fd_map = FdMap::new();
|
||||
|
||||
vfs.fd_map
|
||||
.keys()
|
||||
.map(|handle| (handle, handle))
|
||||
.for_each(|(virtual_handle, handle)| {
|
||||
let vfd = VirtualFd(*virtual_handle);
|
||||
fd_map.insert(vfd, FileHandle::VirtualFile(*handle));
|
||||
});
|
||||
|
||||
// let _ = repo.create_dir(PathBuf::from("/dev/"));
|
||||
// let stdin = repo.create_file(PathBuf::from("/dev/stdin"))?;
|
||||
// let stdout = repo.create_file(PathBuf::from("/dev/stdout"))?;
|
||||
// let stderr = repo.create_file(PathBuf::from("/dev/stderr"))?;
|
||||
|
||||
let stdin_fd = VirtualFd(0);
|
||||
let stdin_handle = FileHandle::VirtualFile(0);
|
||||
let stdout_fd = VirtualFd(1);
|
||||
let stdout_handle = FileHandle::VirtualFile(1);
|
||||
let stderr_fd = VirtualFd(2);
|
||||
let stderr_handle = FileHandle::VirtualFile(2);
|
||||
|
||||
fd_map.insert(stdin_fd, stdin_handle);
|
||||
fd_map.insert(stdout_fd, stdout_handle);
|
||||
fd_map.insert(stderr_fd, stderr_handle);
|
||||
|
||||
EmscriptenVfs { fd_map, vfs }
|
||||
}
|
||||
|
||||
pub fn close(&mut self, vfd: &VirtualFd) -> () {
|
||||
match self.fd_map.get(&vfd) {
|
||||
Some(FileHandle::VirtualFile(handle)) => {
|
||||
self.vfs.close(handle);
|
||||
},
|
||||
Some(FileHandle::Socket(fd)) => unsafe {
|
||||
libc::close(*fd);
|
||||
},
|
||||
None => panic!(),
|
||||
}
|
||||
self.fd_map.remove(&vfd);
|
||||
}
|
||||
|
||||
pub fn next_lowest_fd(&self) -> VirtualFd {
|
||||
next_lowest(&self.fd_map)
|
||||
}
|
||||
|
||||
pub fn get_host_socket_fd(&self, vfd: &VirtualFd) -> Option<Fd> {
|
||||
match self.fd_map.get(&vfd) {
|
||||
Some(FileHandle::Socket(fd)) => Some(*fd),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_virtual_file_handle(&self, vfd: VirtualFd) -> Option<Fd> {
|
||||
match self.fd_map.get(&vfd) {
|
||||
Some(FileHandle::VirtualFile(fd)) => Some(*fd),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> VirtualFd {
|
||||
let fd = self.vfs.open_file(path).unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
let file = FileHandle::VirtualFile(fd);
|
||||
self.fd_map.insert(vfd.clone(), file);
|
||||
vfd
|
||||
}
|
||||
|
||||
pub fn new_socket_fd(&mut self, host_fd: Fd) -> VirtualFd {
|
||||
let vfd = self.next_lowest_fd();
|
||||
self.fd_map.insert(vfd.clone(), FileHandle::Socket(host_fd));
|
||||
vfd
|
||||
}
|
||||
}
|
||||
|
||||
fn next_lowest(fd_map: &FdMap) -> VirtualFd {
|
||||
let mut next_lowest_fd = 0;
|
||||
for (vfd, _) in fd_map.iter() {
|
||||
let host_fd = vfd.0;
|
||||
if host_fd == next_lowest_fd {
|
||||
next_lowest_fd += 1;
|
||||
} else if host_fd < next_lowest_fd {
|
||||
panic!("Should not be here.");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
VirtualFd(next_lowest_fd)
|
||||
}
|
@ -4,13 +4,27 @@ mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
mod vfs;
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
mod emscripten_vfs;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use self::unix::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use self::windows::*;
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
pub use self::vfs::*;
|
||||
|
||||
#[cfg(feature = "vfs")]
|
||||
pub use self::emscripten_vfs::*;
|
||||
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
use super::utils::copy_stat_into_wasm;
|
||||
|
||||
use super::varargs::VarArgs;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
/// NOTE: TODO: These syscalls only support wasm_32 for now because they assume offsets are u32
|
||||
@ -21,10 +35,7 @@ use libc::{
|
||||
c_void,
|
||||
chdir,
|
||||
// fcntl, setsockopt, getppid
|
||||
close,
|
||||
dup2,
|
||||
exit,
|
||||
fstat,
|
||||
getpid,
|
||||
// iovec,
|
||||
lseek,
|
||||
@ -32,7 +43,6 @@ use libc::{
|
||||
// readv,
|
||||
rmdir,
|
||||
// writev,
|
||||
stat,
|
||||
write,
|
||||
// sockaddr_in,
|
||||
};
|
||||
@ -65,27 +75,8 @@ pub fn ___syscall3(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 {
|
||||
ret as _
|
||||
}
|
||||
|
||||
/// read
|
||||
#[cfg(feature = "vfs")]
|
||||
pub fn ___syscall3(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 {
|
||||
// -> ssize_t
|
||||
debug!("emscripten::___syscall3 (read - vfs) {}", which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let count: i32 = varargs.get(ctx);
|
||||
debug!("=> fd: {}, buf_offset: {}, count: {}", fd, buf, count);
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as *mut u8;
|
||||
let mut buf_slice = unsafe { slice::from_raw_parts_mut(buf_addr, count as _) };
|
||||
let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
let ret = match &mut emscripten_data.vfs {
|
||||
Some(vfs) => vfs.read_file(fd as _, &mut buf_slice).unwrap(),
|
||||
None => 0,
|
||||
};
|
||||
debug!("=> ret: {}", ret);
|
||||
ret as _
|
||||
}
|
||||
|
||||
/// write
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall4(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall4 (write) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
@ -96,29 +87,13 @@ pub fn ___syscall4(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int
|
||||
unsafe { write(fd, buf_addr, count as _) as i32 }
|
||||
}
|
||||
|
||||
/// open
|
||||
#[cfg(feature = "vfs")]
|
||||
pub fn ___syscall5(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall5 (open vfs) {}", which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
let pathname_addr = emscripten_memory_pointer!(ctx.memory(0), pathname) as *const i8;
|
||||
let path_str = unsafe { std::ffi::CStr::from_ptr(pathname_addr).to_str().unwrap() };
|
||||
let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
let fd = if let Some(vfs) = &mut emscripten_data.vfs {
|
||||
vfs.open_file(path_str).unwrap_or(-1)
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
debug!("=> fd: {}", fd);
|
||||
return fd as _;
|
||||
}
|
||||
|
||||
/// close
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall6(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall6 (close) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
debug!("fd: {}", fd);
|
||||
unsafe { close(fd) }
|
||||
unsafe { libc::close(fd) }
|
||||
}
|
||||
|
||||
// chdir
|
||||
@ -139,6 +114,8 @@ pub fn ___syscall10(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
/// chmod
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall15(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
debug!("emscripten::___syscall15");
|
||||
-1
|
||||
@ -194,20 +171,22 @@ pub fn ___syscall60(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
// dup2
|
||||
/// dup2
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall63(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall63 (dup2) {}", _which);
|
||||
|
||||
let src: i32 = varargs.get(ctx);
|
||||
let dst: i32 = varargs.get(ctx);
|
||||
|
||||
unsafe { dup2(src, dst) }
|
||||
unsafe { libc::dup2(src, dst) }
|
||||
}
|
||||
|
||||
// getppid
|
||||
pub fn ___syscall64(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
debug!("emscripten::___syscall64 (getppid)");
|
||||
unsafe { getpid() }
|
||||
let result = unsafe { getpid() };
|
||||
result
|
||||
}
|
||||
|
||||
pub fn ___syscall66(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
@ -395,6 +374,7 @@ pub fn ___syscall199(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
}
|
||||
|
||||
// stat64
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall195(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall195 (stat64) {}", _which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
@ -403,8 +383,8 @@ pub fn ___syscall195(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
let pathname_addr = emscripten_memory_pointer!(ctx.memory(0), pathname) as *const i8;
|
||||
|
||||
unsafe {
|
||||
let mut _stat: stat = std::mem::zeroed();
|
||||
let ret = stat(pathname_addr, &mut _stat);
|
||||
let mut _stat: libc::stat = std::mem::zeroed();
|
||||
let ret = libc::stat(pathname_addr, &mut _stat);
|
||||
debug!("ret: {}", ret);
|
||||
if ret != 0 {
|
||||
return ret;
|
||||
@ -414,7 +394,8 @@ pub fn ___syscall195(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
0
|
||||
}
|
||||
|
||||
// fstat64
|
||||
/// fstat64
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall197(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall197 (fstat64) {}", _which);
|
||||
let fd: c_int = varargs.get(ctx);
|
||||
@ -422,7 +403,7 @@ pub fn ___syscall197(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
|
||||
unsafe {
|
||||
let mut stat = std::mem::zeroed();
|
||||
let ret = fstat(fd, &mut stat);
|
||||
let ret = libc::fstat(fd, &mut stat);
|
||||
debug!("ret: {}", ret);
|
||||
if ret != 0 {
|
||||
return ret;
|
||||
|
@ -8,7 +8,6 @@ use libc::{
|
||||
c_char,
|
||||
c_int,
|
||||
c_void,
|
||||
chown,
|
||||
// fcntl, setsockopt, getppid
|
||||
connect,
|
||||
dup2,
|
||||
@ -22,11 +21,8 @@ use libc::{
|
||||
ioctl,
|
||||
// iovec,
|
||||
listen,
|
||||
mkdir,
|
||||
msghdr,
|
||||
pid_t,
|
||||
pread,
|
||||
pwrite,
|
||||
// readv,
|
||||
recvfrom,
|
||||
recvmsg,
|
||||
@ -56,7 +52,7 @@ use libc::{
|
||||
};
|
||||
use wasmer_runtime_core::vm::Ctx;
|
||||
|
||||
use std::mem;
|
||||
use std::{mem, slice};
|
||||
|
||||
// Linking to functions that are not provided by rust libc
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -94,6 +90,7 @@ pub fn ___syscall5(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int
|
||||
}
|
||||
|
||||
// chown
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall212(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall212 (chown) {}", _which);
|
||||
|
||||
@ -103,25 +100,28 @@ pub fn ___syscall212(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
|
||||
let pathname_addr = emscripten_memory_pointer!(ctx.memory(0), pathname) as *const i8;
|
||||
|
||||
unsafe { chown(pathname_addr, owner, group) }
|
||||
unsafe { libc::chown(pathname_addr, owner, group) }
|
||||
}
|
||||
|
||||
// mkdir
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall39(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall39 (mkdir) {}", _which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
let mode: u32 = varargs.get(ctx);
|
||||
let pathname_addr = emscripten_memory_pointer!(ctx.memory(0), pathname) as *const i8;
|
||||
unsafe { mkdir(pathname_addr, mode as _) }
|
||||
unsafe { libc::mkdir(pathname_addr, mode as _) }
|
||||
}
|
||||
|
||||
// getgid
|
||||
//#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall201(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
debug!("emscripten::___syscall201 (getgid)");
|
||||
unsafe {
|
||||
let result = unsafe {
|
||||
// Maybe fix: Emscripten returns 0 always
|
||||
getgid() as i32
|
||||
}
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
// getgid32
|
||||
@ -169,6 +169,7 @@ pub fn ___syscall330(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> pid_
|
||||
}
|
||||
|
||||
/// ioctl
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall54(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall54 (ioctl) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
@ -211,6 +212,7 @@ pub fn ___syscall54(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int
|
||||
}
|
||||
|
||||
// socketcall
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall102 (socketcall) {}", _which);
|
||||
@ -251,18 +253,22 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
ioctl(fd, FIOCLEX);
|
||||
};
|
||||
|
||||
type T = u32;
|
||||
let payload = 1 as *const T as _;
|
||||
unsafe {
|
||||
let err = errno::errno();
|
||||
|
||||
// type T = u32;
|
||||
// let payload = 1 as *const T as _;
|
||||
let result = unsafe {
|
||||
setsockopt(
|
||||
fd,
|
||||
SOL_SOCKET,
|
||||
SO_NOSIGPIPE,
|
||||
payload,
|
||||
mem::size_of::<T>() as socklen_t,
|
||||
);
|
||||
0 as *const _,
|
||||
4,
|
||||
)
|
||||
};
|
||||
|
||||
let err2 = errno::errno();
|
||||
|
||||
debug!(
|
||||
"=> domain: {} (AF_INET/2), type: {} (SOCK_STREAM/1), protocol: {} = fd: {}",
|
||||
domain, ty, protocol, fd
|
||||
@ -280,11 +286,12 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
|
||||
// Debug received address
|
||||
let _proper_address = address as *const GuestSockaddrIn;
|
||||
debug!(
|
||||
unsafe {
|
||||
debug!(
|
||||
"=> address.sin_family: {:?}, address.sin_port: {:?}, address.sin_addr.s_addr: {:?}",
|
||||
(*_proper_address).sin_family, (*_proper_address).sin_port, (*_proper_address).sin_addr.s_addr
|
||||
);
|
||||
|
||||
}
|
||||
let status = unsafe { bind(socket, address, address_len) };
|
||||
// debug!("=> status: {}", status);
|
||||
debug!(
|
||||
@ -334,11 +341,24 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
|
||||
let fd = unsafe { accept(socket, address, address_len_addr) };
|
||||
|
||||
use bit_field::BitArray;
|
||||
|
||||
unsafe {
|
||||
let address_linux =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_addr) as *mut LinuxSockAddr;
|
||||
(*address_linux).sa_family = (*address).sa_family as u16;
|
||||
(*address_linux).sa_data = (*address).sa_data;
|
||||
// let sa_data = (*address).sa_data;
|
||||
// let sa_data_slice: &[i8] = slice::from_raw_parts((*address).sa_data, 14);
|
||||
let x = (*address).sa_data[0];
|
||||
let y = (*address).sa_data[1];
|
||||
let raw_family: [i8; 2] = [x, y];
|
||||
let zz = std::mem::transmute::<[i8; 2], i16>(raw_family);
|
||||
|
||||
// let sin_family = &sa_data_slice[0..2];
|
||||
// let sin_port = &sa_data_slice[2..4];
|
||||
let _proper_address = address as *const GuestSockaddrIn;
|
||||
let x = 10;
|
||||
};
|
||||
|
||||
// set_cloexec
|
||||
@ -434,7 +454,8 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
let value_addr = emscripten_memory_pointer!(ctx.memory(0), value) as _;
|
||||
let option_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), option_len) as *mut socklen_t;
|
||||
unsafe { getsockopt(socket, level, name, value_addr, option_len_addr) }
|
||||
let result = unsafe { getsockopt(socket, level, name, value_addr, option_len_addr) };
|
||||
result
|
||||
}
|
||||
16 => {
|
||||
debug!("socket: sendmsg");
|
||||
@ -461,7 +482,8 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
}
|
||||
}
|
||||
|
||||
// pread
|
||||
/// pread
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall180(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall180 (pread) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
@ -475,10 +497,11 @@ pub fn ___syscall180(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
|
||||
let buf_ptr = emscripten_memory_pointer!(ctx.memory(0), buf) as _;
|
||||
|
||||
unsafe { pread(fd, buf_ptr, count as _, offset) as _ }
|
||||
unsafe { libc::pread(fd, buf_ptr, count as _, offset) as _ }
|
||||
}
|
||||
|
||||
// pwrite
|
||||
/// pwrite
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall181(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall181 (pwrite) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
@ -491,7 +514,7 @@ pub fn ___syscall181(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
let offset: i64 = varargs.get(ctx);
|
||||
|
||||
let buf_ptr = emscripten_memory_pointer!(ctx.memory(0), buf) as _;
|
||||
let status = unsafe { pwrite(fd, buf_ptr, count as _, offset) as _ };
|
||||
let status = unsafe { libc::pwrite(fd, buf_ptr, count as _, offset) as _ };
|
||||
debug!(
|
||||
"=> fd: {}, buf: {}, count: {}, offset: {} = status:{}",
|
||||
fd, buf, count, offset, status
|
||||
@ -518,6 +541,7 @@ pub fn ___syscall114(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> pid_
|
||||
}
|
||||
|
||||
// select
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn ___syscall142(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall142 (newselect) {}", _which);
|
||||
@ -528,13 +552,44 @@ pub fn ___syscall142(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
|
||||
let exceptfds: u32 = varargs.get(ctx);
|
||||
let _timeout: i32 = varargs.get(ctx);
|
||||
|
||||
assert!(nfds <= 64, "`nfds` must be less than or equal to 64");
|
||||
assert!(exceptfds == 0, "`exceptfds` is not supporrted");
|
||||
let readfds_set_ptr = emscripten_memory_pointer!(ctx.memory(0), readfds) as *mut _;
|
||||
let readfds_set_u8_ptr = readfds_set_ptr as *mut u8;
|
||||
let writefds_set_ptr = emscripten_memory_pointer!(ctx.memory(0), writefds) as *mut _;
|
||||
let writefds_set_u8_ptr = writefds_set_ptr as *mut u8;
|
||||
|
||||
let nfds = nfds as _;
|
||||
let readfds_slice = unsafe { slice::from_raw_parts_mut(readfds_set_u8_ptr, nfds) };
|
||||
let writefds_slice = unsafe { slice::from_raw_parts_mut(writefds_set_u8_ptr, nfds) };
|
||||
let nfds = nfds as _;
|
||||
|
||||
use bit_field::BitArray;
|
||||
|
||||
let mut bits = vec![];
|
||||
for virtual_fd in 0..nfds {
|
||||
let bit_flag = readfds_slice.get_bit(virtual_fd as usize);
|
||||
if !bit_flag {
|
||||
continue;
|
||||
}
|
||||
bits.push(virtual_fd);
|
||||
}
|
||||
|
||||
let readfds_ptr = emscripten_memory_pointer!(ctx.memory(0), readfds) as _;
|
||||
let writefds_ptr = emscripten_memory_pointer!(ctx.memory(0), writefds) as _;
|
||||
|
||||
unsafe { select(nfds, readfds_ptr, writefds_ptr, 0 as _, 0 as _) }
|
||||
// let rd = unsafe { libc::read(bits[0], 0 as *mut _, 0)};
|
||||
|
||||
let err = errno::errno();
|
||||
|
||||
let result = unsafe { select(nfds, readfds_ptr, writefds_ptr, 0 as _, 0 as _) };
|
||||
|
||||
assert!(nfds <= 64, "`nfds` must be less than or equal to 64");
|
||||
assert!(exceptfds == 0, "`exceptfds` is not supporrted");
|
||||
|
||||
let err = errno::errno();
|
||||
debug!("gah again: {}", err);
|
||||
|
||||
result
|
||||
// unsafe { select(nfds, readfds_ptr, writefds_ptr, 0 as _, 0 as _) }
|
||||
}
|
||||
|
||||
// setpgid
|
||||
|
899
lib/emscripten/src/syscalls/vfs.rs
Normal file
899
lib/emscripten/src/syscalls/vfs.rs
Normal file
@ -0,0 +1,899 @@
|
||||
use crate::emscripten_set_up_memory;
|
||||
use crate::env::get_emscripten_data;
|
||||
use crate::syscalls::emscripten_vfs::FileHandle::{Socket, VirtualFile};
|
||||
use crate::syscalls::emscripten_vfs::{FileHandle, VirtualFd};
|
||||
use crate::utils::{copy_stat_into_wasm, read_string_from_wasm};
|
||||
use crate::varargs::VarArgs;
|
||||
use libc::stat;
|
||||
use std::os::raw::c_int;
|
||||
use std::slice;
|
||||
use wasmer_runtime_abi::vfs::vfs::Fd;
|
||||
use wasmer_runtime_core::vm::Ctx;
|
||||
|
||||
// Another conditional constant for name resolution: Macos et iOS use
|
||||
// SO_NOSIGPIPE as a setsockopt flag to disable SIGPIPE emission on socket.
|
||||
// Other platforms do otherwise.
|
||||
#[cfg(target_os = "darwin")]
|
||||
use libc::SO_NOSIGPIPE;
|
||||
use std::ffi::c_void;
|
||||
|
||||
#[cfg(not(target_os = "darwin"))]
|
||||
const SO_NOSIGPIPE: c_int = 0;
|
||||
|
||||
/// read
|
||||
pub fn ___syscall3(ctx: &mut Ctx, _: i32, mut varargs: VarArgs) -> i32 {
|
||||
debug!("emscripten::___syscall3 (read - vfs)",);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let count: i32 = varargs.get(ctx);
|
||||
|
||||
debug!("=> fd: {}, buf_offset: {}, count: {}", fd, buf, count);
|
||||
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as *mut u8;
|
||||
let mut buf_slice = unsafe { slice::from_raw_parts_mut(buf_addr, count as _) };
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
let virtual_file_handle = vfs.get_virtual_file_handle(vfd).unwrap();
|
||||
|
||||
let ret = vfs
|
||||
.vfs
|
||||
.read_file(virtual_file_handle as _, &mut buf_slice)
|
||||
.unwrap();
|
||||
debug!("=> read syscall returns: {}", ret);
|
||||
ret as _
|
||||
}
|
||||
|
||||
/// write
|
||||
pub fn ___syscall4(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall4 (write - vfs) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let count: i32 = varargs.get(ctx);
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf);
|
||||
|
||||
let buf_slice = unsafe { slice::from_raw_parts_mut(buf_addr, count as _) };
|
||||
let vfd = VirtualFd(fd);
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
|
||||
let count: usize = match vfs.fd_map.get(&vfd) {
|
||||
Some(FileHandle::VirtualFile(handle)) => {
|
||||
vfs.vfs
|
||||
.write_file(*handle as _, buf_slice, count as _, 0)
|
||||
.unwrap();
|
||||
count as usize
|
||||
}
|
||||
Some(FileHandle::Socket(host_fd)) => unsafe {
|
||||
libc::write(*host_fd, buf_addr as _, count as _) as usize
|
||||
},
|
||||
None => panic!(),
|
||||
};
|
||||
|
||||
let string = read_string_from_wasm(ctx.memory(0), buf);
|
||||
|
||||
debug!(
|
||||
"=> fd: {} (host {}), buf: {}, count: {}\n",
|
||||
vfd.0, fd, buf, count
|
||||
);
|
||||
debug!("=> data:\n \"{}\"", string);
|
||||
count as c_int
|
||||
}
|
||||
|
||||
/// open
|
||||
pub fn ___syscall5(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall5 (open vfs) {}", _which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
let pathname_addr = emscripten_memory_pointer!(ctx.memory(0), pathname) as *const i8;
|
||||
let path_str = unsafe { std::ffi::CStr::from_ptr(pathname_addr).to_str().unwrap() };
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let fd = vfs.vfs.open_file(path_str).unwrap();
|
||||
let virtual_file_handle = FileHandle::VirtualFile(fd);
|
||||
let virtual_fd = vfs.next_lowest_fd();
|
||||
let fd = virtual_fd.0;
|
||||
assert!(
|
||||
!vfs.fd_map.contains_key(&virtual_fd),
|
||||
"Emscripten vfs should not contain file descriptor."
|
||||
);
|
||||
vfs.fd_map.insert(virtual_fd, virtual_file_handle);
|
||||
debug!("=> opening `{}` with new virtual fd: {}", path_str, fd);
|
||||
debug!("{}", path_str);
|
||||
return fd as _;
|
||||
}
|
||||
|
||||
/// close
|
||||
pub fn ___syscall6(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall6 (close vfs) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
debug!("closing virtual fd {}...", fd);
|
||||
|
||||
// let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
let mut vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
|
||||
match vfs.fd_map.get(&vfd) {
|
||||
Some(VirtualFile(handle)) => {
|
||||
vfs.vfs.close(handle).unwrap();
|
||||
vfs.fd_map.remove(&vfd);
|
||||
0
|
||||
}
|
||||
Some(Socket(host_fd)) => unsafe {
|
||||
let result = libc::close(*host_fd);
|
||||
if result == 0 {
|
||||
vfs.fd_map.remove(&vfd);
|
||||
0
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
},
|
||||
_ => -1,
|
||||
}
|
||||
}
|
||||
|
||||
/// chmod
|
||||
pub fn ___syscall15(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
debug!("emscripten::___syscall15 (chmod)");
|
||||
debug!("chmod always returns 0.");
|
||||
0
|
||||
}
|
||||
|
||||
// mkdir
|
||||
pub fn ___syscall39(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall39 (mkdir vfs) {}", _which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
let _mode: u32 = varargs.get(ctx);
|
||||
let path = read_string_from_wasm(ctx.memory(0), pathname);
|
||||
let root = std::path::PathBuf::from("/");
|
||||
let absolute_path = root.join(&path);
|
||||
// debug!("mkdir: {}", absolute_path.display());
|
||||
let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
let ret = if let Some(vfs) = &mut emscripten_data.vfs {
|
||||
match vfs.vfs.make_dir(&absolute_path) {
|
||||
Ok(_) => 0,
|
||||
Err(_) => -1,
|
||||
}
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
// debug!("mkdir returns {}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
/// ioctl
|
||||
pub fn ___syscall54(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall54 (ioctl) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let request: u32 = varargs.get(ctx);
|
||||
debug!("virtual fd: {}, op: {}", fd, request);
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
|
||||
let host_fd = match vfs.fd_map.get(&vfd) {
|
||||
Some(Socket(host_fd)) => *host_fd,
|
||||
Some(_) => 0,
|
||||
_ => panic!("Should not ioctl on a vbox file."),
|
||||
};
|
||||
|
||||
// Got the equivalents here: https://code.woboq.org/linux/linux/include/uapi/asm-generic/ioctls.h.html
|
||||
match request as _ {
|
||||
21537 => {
|
||||
// FIONBIO
|
||||
let argp: u32 = varargs.get(ctx);
|
||||
let argp_ptr = emscripten_memory_pointer!(ctx.memory(0), argp) as *mut c_void;
|
||||
let ret = unsafe { libc::ioctl(host_fd, libc::FIONBIO, argp_ptr) };
|
||||
debug!("ret(FIONBIO): {}", ret);
|
||||
ret
|
||||
// 0
|
||||
}
|
||||
21523 => {
|
||||
// TIOCGWINSZ
|
||||
let argp: u32 = varargs.get(ctx);
|
||||
let argp_ptr = emscripten_memory_pointer!(ctx.memory(0), argp) as *mut c_void;
|
||||
let ret = unsafe { libc::ioctl(host_fd, libc::TIOCGWINSZ, argp_ptr) };
|
||||
debug!("ret(TIOCGWINSZ): {} (harcoded to 0)", ret);
|
||||
// ret
|
||||
// TODO: We hardcode the value to have emscripten tests pass, as for some reason
|
||||
// when the capturer is active, ioctl returns -1 instead of 0
|
||||
if ret == -1 {
|
||||
0
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
"emscripten::___syscall54 -> non implemented case {}",
|
||||
request
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// dup2
|
||||
pub fn ___syscall63(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall63 (dup2) {}", _which);
|
||||
|
||||
let src: i32 = varargs.get(ctx);
|
||||
let dst: i32 = varargs.get(ctx);
|
||||
|
||||
let src = VirtualFd(src);
|
||||
let dst = VirtualFd(dst);
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
|
||||
// if the src is a valid file descriptor, then continue
|
||||
if !vfs.fd_map.contains_key(&src) {
|
||||
return -1;
|
||||
}
|
||||
// if src and dst are identical, do nothing
|
||||
if src == dst {
|
||||
return 0;
|
||||
}
|
||||
// test if the destination needs to closed first, if so, close it atomically (or fake it)
|
||||
if vfs.fd_map.contains_key(&dst) {
|
||||
vfs.close(&dst);
|
||||
}
|
||||
|
||||
let dst_file_handle = match vfs.fd_map.get(&src) {
|
||||
Some(FileHandle::VirtualFile(handle)) => {
|
||||
let new_handle: i32 = vfs.vfs.duplicate_handle(handle);
|
||||
FileHandle::VirtualFile(new_handle)
|
||||
}
|
||||
Some(FileHandle::Socket(src_host_fd)) => unsafe {
|
||||
// get a dst file descriptor, or just use the underlying dup syscall
|
||||
let dst_host_fd = libc::dup(*src_host_fd);
|
||||
if dst_host_fd == -1 {
|
||||
panic!()
|
||||
}
|
||||
FileHandle::Socket(dst_host_fd)
|
||||
},
|
||||
None => panic!(),
|
||||
};
|
||||
|
||||
vfs.fd_map.insert(dst.clone(), dst_file_handle);
|
||||
|
||||
let dst = dst.0;
|
||||
|
||||
debug!("emscripten::___syscall63 (dup2) returns {}", dst);
|
||||
|
||||
dst
|
||||
}
|
||||
|
||||
/// pread
|
||||
pub fn ___syscall180(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall180 (pread) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let count: i32 = varargs.get(ctx);
|
||||
let offset: i32/*i64*/ = varargs.get(ctx);
|
||||
// debug!(
|
||||
// "=> fd: {}, buf_offset: {}, count: {}, offset: {}",
|
||||
// fd, buf, count, offset
|
||||
// );
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as *mut u8;
|
||||
let buf_slice = unsafe { slice::from_raw_parts_mut(buf_addr, count as _) };
|
||||
let mut buf_slice_with_offset: &mut [u8] = &mut buf_slice[(offset as usize)..];
|
||||
|
||||
// let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
// let ret = match &mut emscripten_data.vfs {
|
||||
// Some(vfs) => vfs.vfs.read_file(fd as _, &mut buf_slice_with_offset).unwrap(),
|
||||
// None => 0,
|
||||
// };
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
let virtual_file_handle = vfs.get_virtual_file_handle(vfd).unwrap();
|
||||
let ret = vfs
|
||||
.vfs
|
||||
.read_file(virtual_file_handle as _, &mut buf_slice_with_offset)
|
||||
.unwrap();
|
||||
|
||||
// debug!("=> pread returns: {}", ret);
|
||||
ret as _
|
||||
}
|
||||
|
||||
/// pwrite
|
||||
pub fn ___syscall181(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall181 (pwrite) {}", _which);
|
||||
let fd: i32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let count: u32 = varargs.get(ctx);
|
||||
let offset: i32 = varargs.get(ctx);
|
||||
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf);
|
||||
|
||||
let buf_slice = unsafe { slice::from_raw_parts_mut(buf_addr, count as _) };
|
||||
|
||||
// let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
// let count = if let Some(vfs) = &mut emscripten_data.vfs {
|
||||
// vfs.vfs.write_file(fd as _, buf_slice, count as _, offset as _)
|
||||
// .unwrap()
|
||||
// } else {
|
||||
// 0
|
||||
// };
|
||||
|
||||
let vfd = VirtualFd(fd);
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let virtual_file_handle = vfs.get_virtual_file_handle(vfd).unwrap();
|
||||
vfs.vfs
|
||||
.write_file(virtual_file_handle as _, buf_slice, count as _, offset as _)
|
||||
.unwrap();
|
||||
|
||||
count as _
|
||||
}
|
||||
|
||||
// stat64
|
||||
#[cfg(feature = "vfs")]
|
||||
pub fn ___syscall195(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall195 (stat64) {}", _which);
|
||||
let pathname: u32 = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let path_string = read_string_from_wasm(ctx.memory(0), pathname);
|
||||
debug!("path extract for `stat` syscall: {}", &path_string);
|
||||
let path = std::path::PathBuf::from(path_string);
|
||||
|
||||
let emscripten_data = crate::env::get_emscripten_data(ctx);
|
||||
let ret = match &mut emscripten_data.vfs {
|
||||
Some(vfs) => {
|
||||
let metadata = vfs.vfs.get_path_metadata(&path).unwrap();
|
||||
let len = metadata.len();
|
||||
unsafe {
|
||||
let mut stat: stat = std::mem::zeroed();
|
||||
stat.st_size = len as _;
|
||||
debug!("stat size: {}", len);
|
||||
copy_stat_into_wasm(ctx, buf, &stat as _);
|
||||
}
|
||||
0
|
||||
}
|
||||
None => -1,
|
||||
};
|
||||
debug!("stat return: {}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
/// fstat64
|
||||
pub fn ___syscall197(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall197 (fstat64) {}", _which);
|
||||
let fd: c_int = varargs.get(ctx);
|
||||
let buf: u32 = varargs.get(ctx);
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(fd);
|
||||
let ret = match vfs.fd_map.get(&vfd) {
|
||||
Some(FileHandle::VirtualFile(internal_handle)) => {
|
||||
let metadata = vfs.vfs.get_file_metadata(internal_handle).unwrap();
|
||||
let len = metadata.len;
|
||||
let mode = if metadata.is_file {
|
||||
libc::S_IFREG
|
||||
} else {
|
||||
libc::S_IFDIR
|
||||
};
|
||||
unsafe {
|
||||
let mut stat: stat = std::mem::zeroed();
|
||||
stat.st_mode = mode as _;
|
||||
stat.st_size = len as _;
|
||||
debug!("fstat size: {}", len);
|
||||
copy_stat_into_wasm(ctx, buf, &stat as _);
|
||||
}
|
||||
0
|
||||
}
|
||||
Some(FileHandle::Socket(host_fd)) => panic!(),
|
||||
None => -1,
|
||||
};
|
||||
debug!("fstat return: {}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
// getgid
|
||||
//pub fn ___syscall201(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 {
|
||||
// debug!("emscripten::___syscall201 (getgid)");
|
||||
// 0
|
||||
//}
|
||||
|
||||
// socketcall
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall102 (socketcall) {}", _which);
|
||||
let call: u32 = varargs.get(ctx);
|
||||
let mut socket_varargs: VarArgs = varargs.get(ctx);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
type libc_sa_family_t = u16;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
type libc_sa_family_t = libc::sa_family_t;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
type libc_in_port_t = u16;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
type libc_in_port_t = libc::in_port_t;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
type libc_in_addr_t = u32;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
type libc_in_addr_t = libc::in_addr_t;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GuestSockaddrIn {
|
||||
pub sin_family: libc_sa_family_t, // u16
|
||||
pub sin_port: libc_in_port_t, // u16
|
||||
pub sin_addr: GuestInAddr, // u32
|
||||
pub sin_zero: [u8; 8], // u8 * 8
|
||||
// 2 + 2 + 4 + 8 = 16
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GuestInAddr {
|
||||
pub s_addr: libc_in_addr_t, // u32
|
||||
}
|
||||
|
||||
pub struct LinuxSockAddr {
|
||||
pub sa_family: u16,
|
||||
pub sa_data: [libc::c_char; 14],
|
||||
}
|
||||
|
||||
match call {
|
||||
1 => {
|
||||
debug!("socket: socket");
|
||||
// socket (domain: c_int, ty: c_int, protocol: c_int) -> c_int
|
||||
let domain: i32 = socket_varargs.get(ctx);
|
||||
let ty: i32 = socket_varargs.get(ctx);
|
||||
let protocol: i32 = socket_varargs.get(ctx);
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
|
||||
let test_errno = errno::errno();
|
||||
|
||||
// create the host socket
|
||||
let host_fd = unsafe { libc::socket(domain, ty, protocol) };
|
||||
let vfd = vfs.new_socket_fd(host_fd);
|
||||
|
||||
debug!("--- host fd from libc::socket: {} ---", host_fd);
|
||||
debug!("--- reference fd in vfs from libc::socket: {} ---", vfd);
|
||||
|
||||
// set_cloexec
|
||||
let ioctl_result = unsafe { libc::ioctl(host_fd, libc::FIOCLEX) };
|
||||
|
||||
use libc::{setsockopt, socklen_t, SOL_SOCKET};
|
||||
|
||||
let err = errno::errno();
|
||||
|
||||
type T = u32;
|
||||
let payload = 1 as *const T as _;
|
||||
let setsockopt_result = unsafe {
|
||||
setsockopt(
|
||||
host_fd,
|
||||
SOL_SOCKET,
|
||||
SO_NOSIGPIPE,
|
||||
payload,
|
||||
std::mem::size_of::<T>() as libc::socklen_t,
|
||||
)
|
||||
};
|
||||
|
||||
let err2 = errno::errno();
|
||||
|
||||
debug!(
|
||||
"=> domain: {} (AF_INET/2), type: {} (SOCK_STREAM/1), protocol: {} = fd: {}",
|
||||
domain, ty, protocol, vfd
|
||||
);
|
||||
|
||||
vfd.0 as _
|
||||
}
|
||||
2 => {
|
||||
debug!("socket: bind");
|
||||
// bind (socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int
|
||||
// TODO: Emscripten has a different signature.
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len = socket_varargs.get(ctx);
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
let thinfy = libc::AF_INET;
|
||||
let thinfy2 = libc::AF_UNIX;
|
||||
|
||||
// Debug received address
|
||||
let _proper_address = address as *const GuestSockaddrIn;
|
||||
let _other_proper_address = address as *const libc::sockaddr;
|
||||
unsafe {
|
||||
debug!(
|
||||
"=> address.sin_family: {:?}, address.sin_port: {:?}, address.sin_addr.s_addr: {:?}",
|
||||
(*_proper_address).sin_family, (*_proper_address).sin_port, (*_proper_address).sin_addr.s_addr
|
||||
);
|
||||
let ex = 10;
|
||||
}
|
||||
let status = unsafe { libc::bind(host_socket_fd as _, address, address_len) };
|
||||
// debug!("=> status: {}", status);
|
||||
debug!(
|
||||
"=> socketfd: {}, address: {:?}, address_len: {} = status: {}",
|
||||
socket, address, address_len, status
|
||||
);
|
||||
status
|
||||
// -1
|
||||
}
|
||||
3 => {
|
||||
debug!("socket: connect");
|
||||
// connect (socket: c_int, address: *const sockaddr, len: socklen_t) -> c_int
|
||||
// TODO: Emscripten has a different signature.
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len = socket_varargs.get(ctx);
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
unsafe { libc::connect(host_socket_fd as _, address, address_len) }
|
||||
}
|
||||
4 => {
|
||||
debug!("socket: listen");
|
||||
// listen (socket: c_int, backlog: c_int) -> c_int
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let backlog: i32 = socket_varargs.get(ctx);
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
let status = unsafe { libc::listen(host_socket_fd, backlog) };
|
||||
debug!(
|
||||
"=> socketfd: {}, backlog: {} = status: {}",
|
||||
socket, backlog, status
|
||||
);
|
||||
status
|
||||
}
|
||||
5 => {
|
||||
debug!("socket: accept");
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let address_addr: u32 = socket_varargs.get(ctx);
|
||||
let address_len: u32 = socket_varargs.get(ctx);
|
||||
let address =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_addr) as *mut libc::sockaddr;
|
||||
let address_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_len) as *mut libc::socklen_t;
|
||||
|
||||
let host_socket_fd = {
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
host_socket_fd
|
||||
};
|
||||
|
||||
debug!(
|
||||
"=> socket: {}(host {}), address: {:?}, address_len: {}",
|
||||
socket, host_socket_fd, address, address_len
|
||||
);
|
||||
|
||||
let new_accept_host_fd =
|
||||
unsafe { libc::accept(host_socket_fd, address, address_len_addr) };
|
||||
|
||||
unsafe {
|
||||
let address_linux =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_addr) as *mut LinuxSockAddr;
|
||||
(*address_linux).sa_family = (*address).sa_family as u16;
|
||||
(*address_linux).sa_data = (*address).sa_data;
|
||||
let x = 10;
|
||||
};
|
||||
|
||||
// set_cloexec
|
||||
let ioctl_result = unsafe { libc::ioctl(new_accept_host_fd, libc::FIOCLEX) };
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let new_vfd = vfs.new_socket_fd(new_accept_host_fd);
|
||||
|
||||
debug!("new accept fd: {}(host {})", new_vfd.0, new_accept_host_fd);
|
||||
|
||||
new_vfd.0 as _
|
||||
}
|
||||
6 => {
|
||||
debug!("socket: getsockname");
|
||||
// getsockname (socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len: u32 = socket_varargs.get(ctx);
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
let address_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_len) as *mut libc::socklen_t;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let socket_fd = vfs.fd_map.get(&vfd).unwrap();
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe { libc::getsockname(host_socket_fd as _, address, address_len_addr) }
|
||||
}
|
||||
7 => {
|
||||
debug!("socket: getpeername");
|
||||
// getpeername (socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len: u32 = socket_varargs.get(ctx);
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
let address_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_len) as *mut libc::socklen_t;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe { libc::getpeername(host_socket_fd as _, address, address_len_addr) }
|
||||
}
|
||||
11 => {
|
||||
debug!("socket: sendto");
|
||||
// sendto (socket: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let buf: u32 = socket_varargs.get(ctx);
|
||||
let flags = socket_varargs.get(ctx);
|
||||
let len: i32 = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len = socket_varargs.get(ctx);
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as _;
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe {
|
||||
libc::sendto(
|
||||
host_socket_fd as _,
|
||||
buf_addr,
|
||||
flags,
|
||||
len,
|
||||
address,
|
||||
address_len,
|
||||
) as i32
|
||||
}
|
||||
}
|
||||
12 => {
|
||||
debug!("socket: recvfrom");
|
||||
// recvfrom (socket: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let buf: u32 = socket_varargs.get(ctx);
|
||||
let len: i32 = socket_varargs.get(ctx);
|
||||
let flags = socket_varargs.get(ctx);
|
||||
let address: u32 = socket_varargs.get(ctx);
|
||||
let address_len: u32 = socket_varargs.get(ctx);
|
||||
let buf_addr = emscripten_memory_pointer!(ctx.memory(0), buf) as _;
|
||||
let address = emscripten_memory_pointer!(ctx.memory(0), address) as *mut libc::sockaddr;
|
||||
let address_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), address_len) as *mut libc::socklen_t;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe {
|
||||
libc::recvfrom(
|
||||
host_socket_fd,
|
||||
buf_addr,
|
||||
flags,
|
||||
len,
|
||||
address,
|
||||
address_len_addr,
|
||||
) as i32
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
debug!("socket: setsockopt");
|
||||
// NOTE: Emscripten seems to be passing the wrong values to this syscall
|
||||
// level: Em passes 1 as SOL_SOCKET; SOL_SOCKET is 0xffff in BSD
|
||||
// name: Em passes SO_ACCEPTCONN, but Nginx complains about REUSEADDR
|
||||
// https://github.com/openbsd/src/blob/master/sys/sys/socket.h#L156
|
||||
// setsockopt (socket: c_int, level: c_int, name: c_int, value: *const c_void, option_len: socklen_t) -> c_int
|
||||
|
||||
let socket = socket_varargs.get(ctx);
|
||||
// SOL_SOCKET = 0xffff (BSD, Linux)
|
||||
let level: i32 = libc::SOL_SOCKET;
|
||||
let x_level: u32 = socket_varargs.get(ctx);
|
||||
// SO_REUSEADDR = 0x4 (BSD, Linux)
|
||||
let name: i32 = libc::SO_REUSEADDR;
|
||||
let _: u32 = socket_varargs.get(ctx);
|
||||
let value: u32 = socket_varargs.get(ctx);
|
||||
let option_len = socket_varargs.get(ctx);
|
||||
let value_addr = emscripten_memory_pointer!(ctx.memory(0), value) as _; // Endian problem
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
let ret = unsafe {
|
||||
libc::setsockopt(host_socket_fd as _, level, name, value_addr, option_len)
|
||||
};
|
||||
|
||||
let bf = unsafe { slice::from_raw_parts(value_addr as *const u8, option_len as _) };
|
||||
|
||||
debug!("=> socketfd: {}, level: {} (SOL_SOCKET/0xffff), name: {} (SO_REUSEADDR/4), value_addr: {:?}, option_len: {} = status: {}", socket, level, name, value_addr, option_len, ret);
|
||||
ret
|
||||
}
|
||||
15 => {
|
||||
debug!("socket: getsockopt");
|
||||
// getsockopt (sockfd: c_int, level: c_int, optname: c_int, optval: *mut c_void, optlen: *mut socklen_t) -> c_int
|
||||
use libc::socklen_t;
|
||||
let socket = socket_varargs.get(ctx);
|
||||
let level: i32 = socket_varargs.get(ctx);
|
||||
let name: i32 = socket_varargs.get(ctx);
|
||||
let value: u32 = socket_varargs.get(ctx);
|
||||
let option_len: u32 = socket_varargs.get(ctx);
|
||||
let value_addr = emscripten_memory_pointer!(ctx.memory(0), value) as _;
|
||||
let option_len_addr =
|
||||
emscripten_memory_pointer!(ctx.memory(0), option_len) as *mut socklen_t;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
let result = unsafe {
|
||||
libc::getsockopt(host_socket_fd, level, name, value_addr, option_len_addr)
|
||||
};
|
||||
|
||||
if result == -1 {
|
||||
let err = errno::errno();
|
||||
debug!("socket: getsockopt -- error -- {}", err);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
16 => {
|
||||
debug!("socket: sendmsg");
|
||||
// sendmsg (fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let msg: u32 = socket_varargs.get(ctx);
|
||||
let flags: i32 = socket_varargs.get(ctx);
|
||||
let msg_addr = emscripten_memory_pointer!(ctx.memory(0), msg) as *const libc::msghdr;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe { libc::sendmsg(host_socket_fd as _, msg_addr, flags) as i32 }
|
||||
}
|
||||
17 => {
|
||||
debug!("socket: recvmsg");
|
||||
// recvmsg (fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t
|
||||
let socket: i32 = socket_varargs.get(ctx);
|
||||
let msg: u32 = socket_varargs.get(ctx);
|
||||
let flags: i32 = socket_varargs.get(ctx);
|
||||
let msg_addr = emscripten_memory_pointer!(ctx.memory(0), msg) as *mut libc::msghdr;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
let vfd = VirtualFd(socket);
|
||||
let host_socket_fd = vfs.get_host_socket_fd(&vfd).unwrap();
|
||||
|
||||
unsafe { libc::recvmsg(host_socket_fd as _, msg_addr, flags) as i32 }
|
||||
}
|
||||
_ => {
|
||||
// others
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// select
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn ___syscall142(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall142 (newselect) {}", _which);
|
||||
|
||||
let nfds: i32 = varargs.get(ctx);
|
||||
let readfds: u32 = varargs.get(ctx);
|
||||
let writefds: u32 = varargs.get(ctx);
|
||||
let _exceptfds: u32 = varargs.get(ctx);
|
||||
let timeout: i32 = varargs.get(ctx);
|
||||
|
||||
assert!(nfds <= 64, "`nfds` must be less than or equal to 64");
|
||||
// assert_eq!(exceptfds, 0, "`exceptfds` is not supporrted");
|
||||
|
||||
let readfds_set_ptr = emscripten_memory_pointer!(ctx.memory(0), readfds) as *mut _;
|
||||
let readfds_set_u8_ptr = readfds_set_ptr as *mut u8;
|
||||
let writefds_set_ptr = emscripten_memory_pointer!(ctx.memory(0), writefds) as *mut _;
|
||||
let writefds_set_u8_ptr = writefds_set_ptr as *mut u8;
|
||||
|
||||
let nfds = nfds as _;
|
||||
let readfds_slice = unsafe { slice::from_raw_parts_mut(readfds_set_u8_ptr, nfds) };
|
||||
let writefds_slice = unsafe { slice::from_raw_parts_mut(writefds_set_u8_ptr, nfds) };
|
||||
|
||||
use bit_field::BitArray;
|
||||
|
||||
let vfs = crate::env::get_emscripten_data(ctx).vfs.as_mut().unwrap();
|
||||
|
||||
let mut file_descriptors_to_watch = vec![];
|
||||
for virtual_fd in 0..nfds {
|
||||
let bit_flag = readfds_slice.get_bit(virtual_fd as usize);
|
||||
if !bit_flag {
|
||||
continue;
|
||||
}
|
||||
file_descriptors_to_watch.push(virtual_fd);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
let mut max = -1;
|
||||
|
||||
let mut read_mappings = vec![];
|
||||
|
||||
for virtual_fd in 0..nfds {
|
||||
let bit_flag = readfds_slice.get_bit(virtual_fd);
|
||||
if !bit_flag {
|
||||
continue;
|
||||
}
|
||||
let virtual_file = vfs.fd_map.get(&VirtualFd(virtual_fd as _));
|
||||
match virtual_file {
|
||||
Some(FileHandle::VirtualFile(fd)) => {
|
||||
count = count + 1;
|
||||
}
|
||||
Some(FileHandle::Socket(host_fd)) => {
|
||||
count = count + 1;
|
||||
unsafe {
|
||||
let virtual_fd = virtual_fd as i32;
|
||||
let fd = *host_fd;
|
||||
if fd > max {
|
||||
max = fd;
|
||||
}
|
||||
read_mappings.push((virtual_fd, fd));
|
||||
};
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
}
|
||||
|
||||
for mapping in read_mappings.clone() {
|
||||
let (virtual_fd, fd) = mapping;
|
||||
unsafe {
|
||||
libc::FD_CLR(virtual_fd, readfds_set_ptr);
|
||||
libc::FD_SET(fd, readfds_set_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// #[repr(C)]
|
||||
// pub struct timeval {
|
||||
// pub tv_sec: libc::c_long, // time t
|
||||
// pub tv_usec: libc::c_long, // long int
|
||||
// }
|
||||
|
||||
let timeval_ptr = emscripten_memory_pointer!(ctx.memory(0), timeout) as *mut libc::timeval;
|
||||
let mut tval = unsafe {
|
||||
libc::timeval {
|
||||
tv_sec: (*timeval_ptr).tv_sec,
|
||||
tv_usec: (*timeval_ptr).tv_usec,
|
||||
}
|
||||
};
|
||||
let mut tval_ptr: *mut libc::timeval = &mut tval;
|
||||
|
||||
let sz = max as i32 + 1;
|
||||
// let result = unsafe { libc::select(sz, readfds_set_ptr, writefds_set_ptr, 0 as _, timeval_ptr as *mut libc::timeval) };
|
||||
let result = unsafe { libc::select(sz, readfds_set_ptr, writefds_set_ptr, 0 as _, 0 as _) };
|
||||
|
||||
assert!(nfds <= 64, "`nfds` must be less than or equal to 64");
|
||||
// assert!(exceptfds == 0, "`exceptfds` is not supporrted");
|
||||
|
||||
let err = errno::errno();
|
||||
debug!("gah again: {}", err);
|
||||
|
||||
// let len = read_mappings.len();
|
||||
|
||||
for mapping in read_mappings {
|
||||
let (virtual_fd, fd) = mapping;
|
||||
unsafe {
|
||||
libc::FD_CLR(fd, readfds_set_ptr);
|
||||
libc::FD_SET(virtual_fd, readfds_set_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
for input in file_descriptors_to_watch {
|
||||
unsafe {
|
||||
let in_set = libc::FD_ISSET(input as _, readfds_set_ptr);
|
||||
assert!(in_set);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// chown
|
||||
pub fn ___syscall212(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall212 (chown) {}", _which);
|
||||
let _pathname: u32 = varargs.get(ctx);
|
||||
let _owner: u32 = varargs.get(ctx);
|
||||
let _group: u32 = varargs.get(ctx);
|
||||
debug!("syscall `chown` always returns 0");
|
||||
0
|
||||
}
|
@ -69,6 +69,7 @@ pub fn ___syscall212(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_i
|
||||
}
|
||||
|
||||
// mkdir
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall39(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall39 (mkdir) {}", which);
|
||||
#[cfg(not(feature = "debug"))]
|
||||
@ -106,6 +107,7 @@ pub fn ___syscall54(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_in
|
||||
}
|
||||
|
||||
// socketcall
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub fn ___syscall102(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall102 (socketcall) {}", which);
|
||||
@ -114,16 +116,18 @@ pub fn ___syscall102(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_i
|
||||
-1
|
||||
}
|
||||
|
||||
// pread
|
||||
pub fn ___syscall180(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_int {
|
||||
/// pread
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall180(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall180 (pread) {}", which);
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let _ = which;
|
||||
-1
|
||||
}
|
||||
|
||||
// pwrite
|
||||
pub fn ___syscall181(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_int {
|
||||
/// pwrite
|
||||
#[cfg(not(feature = "vfs"))]
|
||||
pub fn ___syscall181(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int {
|
||||
debug!("emscripten::___syscall181 (pwrite) {}", which);
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let _ = which;
|
||||
|
@ -90,24 +90,6 @@ pub unsafe fn allocate_cstr_on_stack<'a>(ctx: &'a mut Ctx, s: &str) -> (u32, &'a
|
||||
(offset, slice)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub unsafe fn copy_terminated_array_of_cstrs(_ctx: &mut Ctx, cstrs: *mut *mut c_char) -> u32 {
|
||||
let _total_num = {
|
||||
let mut ptr = cstrs;
|
||||
let mut counter = 0;
|
||||
while !(*ptr).is_null() {
|
||||
counter += 1;
|
||||
ptr = ptr.add(1);
|
||||
}
|
||||
counter
|
||||
};
|
||||
debug!(
|
||||
"emscripten::copy_terminated_array_of_cstrs::total_num: {}",
|
||||
_total_num
|
||||
);
|
||||
0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GuestStat {
|
||||
st_dev: u32,
|
||||
@ -188,9 +170,7 @@ mod tests {
|
||||
|
||||
#[cfg(not(any(feature = "llvm", feature = "clif")))]
|
||||
fn get_compiler() -> impl Compiler {
|
||||
panic!("compiler not specified, activate a compiler via features");
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
CraneliftCompiler::new()
|
||||
panic!("compiler not specified, activate a compiler via features")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -62,10 +62,31 @@ macro_rules! assert_emscripten_output {
|
||||
}
|
||||
|
||||
pub fn assert_emscripten_output(wasm_bytes: &[u8], raw_expected_str: &str) {
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
use wasmer_emscripten::{generate_emscripten_env, stdio::StdioCapturer, EmscriptenGlobals};
|
||||
use wasmer_runtime_core::backend::Compiler;
|
||||
|
||||
let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &CraneliftCompiler::new())
|
||||
#[cfg(feature = "clif")]
|
||||
fn get_compiler() -> impl Compiler {
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
CraneliftCompiler::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
fn get_compiler() -> impl Compiler {
|
||||
use wasmer_llvm_backend::LLVMCompiler;
|
||||
LLVMCompiler::new()
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "llvm", feature = "clif")))]
|
||||
fn get_compiler() -> impl Compiler {
|
||||
panic!("compiler not specified, activate a compiler via features");
|
||||
use wasmer_clif_backend::CraneliftCompiler;
|
||||
CraneliftCompiler::new()
|
||||
}
|
||||
|
||||
let compiler = get_compiler();
|
||||
|
||||
let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &compiler)
|
||||
.expect("WASM can't be compiled");
|
||||
|
||||
let mut emscripten_globals = EmscriptenGlobals::new(&module);
|
||||
|
52
lib/runtime-abi/src/vfs/device_file.rs
Normal file
52
lib/runtime-abi/src/vfs/device_file.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::vfs::file_like::{FileLike, Metadata};
|
||||
use failure::Error;
|
||||
|
||||
pub struct Stdin;
|
||||
pub struct Stdout;
|
||||
pub struct Stderr;
|
||||
|
||||
impl FileLike for Stdin {
|
||||
fn write(&mut self, buf: &[u8], _count: usize, _offset: usize) -> Result<usize, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn read(&mut self, _buf: &mut [u8]) -> Result<usize, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Result<Metadata, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
//impl FileLike for Stdout {
|
||||
// fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//
|
||||
// fn close(self) -> Result<(), Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//
|
||||
// fn metadata(&self) -> Result<Metadata, Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//impl FileLike for Stderr {
|
||||
// fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//
|
||||
// fn close(self) -> Result<(), Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//
|
||||
// fn metadata(&self) -> Result<Metadata, Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//}
|
18
lib/runtime-abi/src/vfs/file_like.rs
Normal file
18
lib/runtime-abi/src/vfs/file_like.rs
Normal file
@ -0,0 +1,18 @@
|
||||
pub type Fd = isize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Metadata {
|
||||
pub len: usize,
|
||||
pub is_file: bool,
|
||||
}
|
||||
|
||||
pub trait FileLike {
|
||||
/// write
|
||||
fn write(&mut self, buf: &[u8], count: usize, offset: usize) -> Result<usize, failure::Error>;
|
||||
/// like read(2), will read the data for the file descriptor
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, failure::Error>;
|
||||
/// close
|
||||
fn close(&self) -> Result<(), failure::Error>;
|
||||
// get metadata
|
||||
fn metadata(&self) -> Result<Metadata, failure::Error>;
|
||||
}
|
@ -1,2 +1,5 @@
|
||||
pub mod device_file;
|
||||
pub mod file_like;
|
||||
pub mod vfs;
|
||||
pub mod vfs_header;
|
||||
pub mod virtual_file;
|
||||
|
@ -1,15 +1,20 @@
|
||||
use crate::vfs::file_like::FileLike;
|
||||
use crate::vfs::vfs_header::{header_from_bytes, ArchiveType, CompressionType};
|
||||
use crate::vfs::virtual_file::VirtualFile;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use tar::EntryType;
|
||||
use zbox::{init_env, OpenOptions, Repo, RepoOpener};
|
||||
|
||||
pub type Fd = isize;
|
||||
pub type Fd = i32;
|
||||
|
||||
pub struct Vfs {
|
||||
pub repo: Repo,
|
||||
pub fd_map: BTreeMap<Fd, zbox::File>, // best because we look for lowest fd
|
||||
repo: Repo,
|
||||
pub fd_map: BTreeMap<Fd, Rc<dyn FileLike>>,
|
||||
pub import_errors: Vec<VfsAggregateError>,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
@ -20,6 +25,7 @@ impl Vfs {
|
||||
Vfs::from_tar_bytes(&decompressed_data[..])
|
||||
}
|
||||
|
||||
/// Match on the type of the compressed-archive and select the correct unpack method
|
||||
pub fn from_compressed_bytes(compressed_data_slice: &[u8]) -> Result<Self, failure::Error> {
|
||||
let data_bytes = &compressed_data_slice[4..];
|
||||
match header_from_bytes(compressed_data_slice)? {
|
||||
@ -30,60 +36,255 @@ impl Vfs {
|
||||
|
||||
/// 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);
|
||||
init_env();
|
||||
let mut repo = RepoOpener::new()
|
||||
.create(true)
|
||||
.open("mem://wasmer_fs", "")
|
||||
.unwrap();
|
||||
for entry in ar.entries()? {
|
||||
let mut entry = entry?;
|
||||
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();
|
||||
}
|
||||
|
||||
let mut fd_map: BTreeMap<Fd, Rc<dyn FileLike>> = BTreeMap::new();
|
||||
|
||||
// TODO: What to do about the creation of the device files?
|
||||
let _ = repo.create_dir(PathBuf::from("/dev/"));
|
||||
let stdin = repo.create_file(PathBuf::from("/dev/stdin"))?;
|
||||
let stdout = repo.create_file(PathBuf::from("/dev/stdout"))?;
|
||||
let stderr = repo.create_file(PathBuf::from("/dev/stderr"))?;
|
||||
|
||||
use crate::vfs::device_file;
|
||||
fd_map.insert(0, Rc::new(device_file::Stdin {}));
|
||||
fd_map.insert(1, Rc::new(device_file::Stdin {})); // TODO FIX ME
|
||||
fd_map.insert(2, Rc::new(device_file::Stdin {}));
|
||||
|
||||
let errors = tar::Archive::new(tar_bytes)
|
||||
.entries()?
|
||||
.map(|entry| {
|
||||
let mut entry: tar::Entry<Reader> = entry?;
|
||||
let path = entry.path()?;
|
||||
let path = convert_to_absolute_path(path);
|
||||
let result = match (entry.header().entry_type(), path.parent()) {
|
||||
(EntryType::Regular, Some(parent)) => {
|
||||
if let Err(e) = repo.create_dir_all(parent) {
|
||||
if e == zbox::Error::AlreadyExists || e == zbox::Error::IsRoot {
|
||||
} else {
|
||||
return Err(VfsAggregateError::ZboxError(e));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
let mut file = repo.create_file(&path)?;
|
||||
if entry.header().size().unwrap_or(0) > 0 {
|
||||
io::copy(&mut entry, &mut file)?;
|
||||
file.finish()?;
|
||||
}
|
||||
}
|
||||
(EntryType::Directory, _) => {
|
||||
if let Err(e) = repo.create_dir_all(path) {
|
||||
if e == zbox::Error::AlreadyExists || e == zbox::Error::IsRoot {
|
||||
} else {
|
||||
return Err(VfsAggregateError::ZboxError(e));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
_ => return Err(VfsAggregateError::UnsupportedFileType),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Vec<Result<(), VfsAggregateError>>>();
|
||||
|
||||
let vfs = Vfs {
|
||||
repo,
|
||||
fd_map: BTreeMap::new(),
|
||||
fd_map,
|
||||
import_errors: vec![],
|
||||
};
|
||||
Ok(vfs)
|
||||
}
|
||||
|
||||
/// 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
|
||||
let mut data = self
|
||||
.fd_map
|
||||
.get_mut(&fd)
|
||||
.ok_or(VfsError::FileDescriptorNotExist)?
|
||||
.read(buf)
|
||||
.map_err(|e| e.into())
|
||||
.ok_or(VfsError::FileDescriptorNotExist(fd))?;
|
||||
match Rc::get_mut(&mut data) {
|
||||
Some(file) => file.read(buf),
|
||||
None => Err(VfsError::CouldNotGetMutableReferenceToFile.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
|
||||
let file = OpenOptions::new().write(true).open(&mut self.repo, &path)?;
|
||||
let mut next_lowest_fd = 0;
|
||||
for (fd, _) in self.fd_map.iter() {
|
||||
if *fd == next_lowest_fd {
|
||||
next_lowest_fd += 1;
|
||||
} else if *fd < next_lowest_fd {
|
||||
panic!("Should not be here.");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let virtual_file = VirtualFile::new(file);
|
||||
self.fd_map.insert(next_lowest_fd, Rc::new(virtual_file));
|
||||
Ok(next_lowest_fd)
|
||||
}
|
||||
|
||||
fn next_lowest(&self) -> Fd {
|
||||
let mut next_lowest_fd = 0;
|
||||
for (fd, _) in self.fd_map.iter() {
|
||||
if *fd == next_lowest_fd {
|
||||
next_lowest_fd += 1;
|
||||
} else if *fd < next_lowest_fd {
|
||||
panic!("Should not be here.");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
next_lowest_fd
|
||||
}
|
||||
|
||||
/// like dup2, but better for this abstraction layer
|
||||
pub fn duplicate_handle(&mut self, handle: &Fd) -> Fd {
|
||||
let dup = match self.fd_map.get(handle) {
|
||||
Some(file) => file.clone(),
|
||||
None => panic!(),
|
||||
};
|
||||
self.fd_map.insert(fd, file);
|
||||
Ok(fd)
|
||||
let new_handle = self.next_lowest();
|
||||
assert!(!self.fd_map.contains_key(&new_handle));
|
||||
self.fd_map.insert(new_handle, dup);
|
||||
new_handle
|
||||
}
|
||||
|
||||
/// like dup2
|
||||
pub fn duplicate_file_descriptor(
|
||||
&mut self,
|
||||
source_fd: Fd,
|
||||
target_fd: Fd,
|
||||
) -> Result<Fd, failure::Error> {
|
||||
// find the file and check if the target descriptor is already open
|
||||
let (target_is_open_file, file) = {
|
||||
let fd_map = &self.fd_map;
|
||||
let source_file = fd_map.get(&source_fd);
|
||||
let target_file = fd_map.get(&target_fd);
|
||||
match (source_file, target_file) {
|
||||
// the source is not already open
|
||||
(None, _) => Err(VfsError::SourceFileDescriptorDoesNotExist),
|
||||
// the target fd is already open, close it first
|
||||
(_, Some(file)) => Ok((true, file.clone())),
|
||||
// normal case
|
||||
(Some(file), None) => Ok((false, file.clone())),
|
||||
}
|
||||
}?;
|
||||
// if the target fd is already open, close it first
|
||||
if target_is_open_file {
|
||||
let fd_map = &mut self.fd_map;
|
||||
fd_map.remove(&target_fd);
|
||||
fd_map.insert(target_fd, file.clone());
|
||||
} else {
|
||||
let fd_map = &mut self.fd_map;
|
||||
fd_map.insert(target_fd, file.clone());
|
||||
}
|
||||
Ok(target_fd)
|
||||
}
|
||||
|
||||
/// close
|
||||
pub fn close(&mut self, fd: &Fd) -> Result<(), failure::Error> {
|
||||
let result = if let Some(file) = self.fd_map.remove(fd) {
|
||||
file.close()
|
||||
} else {
|
||||
// this file did not exist in the virtual file system, maybe throw an error in the future
|
||||
Ok(())
|
||||
};
|
||||
assert!(!self.fd_map.contains_key(&fd));
|
||||
result
|
||||
}
|
||||
|
||||
/// get metadata with file descriptor
|
||||
pub fn get_file_metadata(
|
||||
&self,
|
||||
fd: &Fd,
|
||||
) -> Result<crate::vfs::file_like::Metadata, failure::Error> {
|
||||
match self.fd_map.get(&fd) {
|
||||
None => Err(VfsError::FileWithFileDescriptorNotExist(*fd).into()),
|
||||
Some(file) => {
|
||||
// let file = file.clone();
|
||||
let file = file.clone();
|
||||
file.metadata()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// get metadata with path
|
||||
pub fn get_path_metadata<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<zbox::Metadata, failure::Error> {
|
||||
let path = convert_to_absolute_path(path);
|
||||
self.repo.metadata(path).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn make_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<(), failure::Error> {
|
||||
self.repo.create_dir_all(path).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// write to a file with the file descriptor
|
||||
pub fn write_file(
|
||||
&mut self,
|
||||
fd: Fd,
|
||||
buf: &[u8],
|
||||
count: usize,
|
||||
offset: usize,
|
||||
) -> Result<usize, failure::Error> {
|
||||
let mut file = self
|
||||
.fd_map
|
||||
.get_mut(&fd)
|
||||
.ok_or(VfsError::FileWithFileDescriptorNotExist(fd))?;
|
||||
let file = Rc::get_mut(&mut file);
|
||||
match file {
|
||||
Some(file) => file.write(buf, count, offset),
|
||||
None => Ok(count) // BAD!!! Switch to Rc<RefCell>
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum VfsError {
|
||||
#[fail(display = "File with file descriptor \"{}\" does not exist.", _0)]
|
||||
FileWithFileDescriptorNotExist(Fd),
|
||||
#[fail(display = "File descriptor does not exist.")]
|
||||
FileDescriptorNotExist,
|
||||
#[fail(display = "Error when trying to read maximum file descriptor.")]
|
||||
CouldNotGetNextLowestFileDescriptor,
|
||||
FileDescriptorNotExist(Fd),
|
||||
#[fail(display = "Source file descriptor does not exist.")]
|
||||
SourceFileDescriptorDoesNotExist,
|
||||
#[fail(display = "Target file descriptor already exists.")]
|
||||
TargetFileDescriptorAlreadyExists,
|
||||
#[fail(display = "Could not get a mutable reference to the file because it is in use.")]
|
||||
CouldNotGetMutableReferenceToFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum VfsAggregateError {
|
||||
#[fail(display = "Entry error.")]
|
||||
EntryError(std::io::Error),
|
||||
#[fail(display = "IO error.")]
|
||||
IoError(std::io::Error),
|
||||
#[fail(display = "Zbox error.")]
|
||||
ZboxError(zbox::Error),
|
||||
#[fail(display = "Unsupported file type.")]
|
||||
UnsupportedFileType,
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for VfsAggregateError {
|
||||
fn from(error: std::io::Error) -> VfsAggregateError {
|
||||
VfsAggregateError::EntryError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<zbox::Error> for VfsAggregateError {
|
||||
fn from(error: zbox::Error) -> VfsAggregateError {
|
||||
VfsAggregateError::ZboxError(error)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_absolute_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
@ -102,7 +303,7 @@ mod open_test {
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn open_files() {
|
||||
fn open_and_close_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");
|
||||
@ -135,6 +336,55 @@ mod open_test {
|
||||
);
|
||||
let fd_2 = open_result_2.unwrap();
|
||||
assert_ne!(fd_1, fd_2, "Open produced the same file descriptor twice.");
|
||||
assert!(fd_2 > 0, "File descriptor was less than 0.");
|
||||
|
||||
// try opening as absolute path
|
||||
let open_result_3 = vfs.open_file("/foo.txt");
|
||||
assert!(
|
||||
open_result_3.is_ok(),
|
||||
"Failed to open the same file twice in the virtual filesystem."
|
||||
);
|
||||
let fd_3 = open_result_3.unwrap();
|
||||
assert!(fd_3 > 0, "File descriptor was less than 0.");
|
||||
|
||||
let close_result = vfs.close(fd_3);
|
||||
assert!(close_result.is_ok(), "Close failed.");
|
||||
|
||||
// re-open the file, assert the file descriptor is the same
|
||||
let open_result_4 = vfs.open_file("/foo.txt");
|
||||
assert!(
|
||||
open_result_4.is_ok(),
|
||||
"Failed to close a file, then the file again in the virtual filesystem."
|
||||
);
|
||||
let fd_4 = open_result_4.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fd_3, fd_4,
|
||||
"Expected the lowest available file descriptor to be used."
|
||||
);
|
||||
|
||||
// close a lower file descriptor
|
||||
let close_result_2 = vfs.close(fd_1);
|
||||
assert!(close_result_2.is_ok(), "Close failed");
|
||||
|
||||
// re-open the file, assert the file descriptor is the same
|
||||
let open_result_5 = vfs.open_file("/foo.txt");
|
||||
assert!(
|
||||
open_result_5.is_ok(),
|
||||
"Failed to open a file, open more files, then close the file, and then open it again and get the lowest file descriptor in in the virtual filesystem."
|
||||
);
|
||||
let fd_5 = open_result_5.unwrap();
|
||||
assert_eq!(
|
||||
fd_5, fd_1,
|
||||
"Expected the lowest available file descriptor to be used."
|
||||
);
|
||||
|
||||
// re-open the file, assert the file descriptor is correct
|
||||
let open_result_6 = vfs.open_file("/foo.txt");
|
||||
assert!(open_result_6.is_ok());
|
||||
// we re-opened a file which took the recently opened low file descriptor. Now we get the next lowest file descriptor.
|
||||
let fd_6 = open_result_6.unwrap();
|
||||
assert_eq!(fd_6, fd_4 + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -171,6 +421,7 @@ mod open_test {
|
||||
#[cfg(test)]
|
||||
mod read_test {
|
||||
use crate::vfs::vfs::Vfs;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use tempdir;
|
||||
@ -186,6 +437,14 @@ mod read_test {
|
||||
vfs_result.is_ok(),
|
||||
"Failed to create file system from empty archive"
|
||||
);
|
||||
// assert import errors
|
||||
let vfs = vfs_result.unwrap();
|
||||
assert_eq!(
|
||||
vfs.import_errors.len(),
|
||||
0,
|
||||
"Expected no import errors. Found {} errors.",
|
||||
vfs.import_errors.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -214,6 +473,14 @@ mod read_test {
|
||||
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");
|
||||
|
||||
// assert import errors
|
||||
assert_eq!(
|
||||
vfs.import_errors.len(),
|
||||
0,
|
||||
"Expected no import errors. Found {} errors.",
|
||||
vfs.import_errors.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -258,5 +525,130 @@ mod read_test {
|
||||
&bar_actual_data, bar_expected_data,
|
||||
"Contents of `bar.txt` is not correct"
|
||||
);
|
||||
// assert import errors
|
||||
assert_eq!(
|
||||
vfs.import_errors.len(),
|
||||
0,
|
||||
"Expected no import errors. Found {} errors.",
|
||||
vfs.import_errors.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_nested_files_in_archive() {
|
||||
// SETUP: create temp dir and files
|
||||
let tmp_dir = tempdir::TempDir::new("two_nested_files_in_archive").unwrap();
|
||||
let baz_dir_path = tmp_dir.path().join("foo").join("bar");
|
||||
fs::create_dir_all(baz_dir_path.clone()).unwrap();
|
||||
let quuz_dir_path = tmp_dir.path().join("qux").join("quuz");
|
||||
fs::create_dir_all(quuz_dir_path.clone()).unwrap();
|
||||
let baz_file_path = baz_dir_path.join("baz.txt");
|
||||
let quuz_file_path = quuz_dir_path.join("quuz.txt");
|
||||
let mut baz_tmp_file = File::create(baz_file_path.clone()).unwrap();
|
||||
let mut quuz_tmp_file = File::create(quuz_file_path.clone()).unwrap();
|
||||
writeln!(baz_tmp_file, "baz baz baz baz").unwrap();
|
||||
writeln!(quuz_tmp_file, "quuz").unwrap();
|
||||
let tar_data = vec![];
|
||||
let mut ar = tar::Builder::new(tar_data);
|
||||
ar.append_path_with_name(baz_file_path, "foo/bar/baz.txt")
|
||||
.unwrap();
|
||||
ar.append_path_with_name(quuz_file_path, "qux/quux/quuz.txt")
|
||||
.unwrap();
|
||||
let archive = ar.into_inner().unwrap();
|
||||
// SETUP: create virtual filesystem with tar data
|
||||
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
||||
// ASSERT:
|
||||
assert!(
|
||||
vfs_result.is_ok(),
|
||||
"Failed to create file system from archive"
|
||||
);
|
||||
let mut vfs = vfs_result.unwrap();
|
||||
// read the file
|
||||
let baz_fd = vfs.open_file("foo/bar/baz.txt").unwrap();
|
||||
let quuz_fd = vfs.open_file("qux/quux/quuz.txt").unwrap();
|
||||
let mut baz_actual_data: [u8; 16] = [0; 16];
|
||||
let baz_read_result = vfs.read_file(baz_fd, &mut baz_actual_data);
|
||||
let mut quuz_actual_data: [u8; 5] = [0; 5];
|
||||
let quuz_read_result = vfs.read_file(quuz_fd, &mut quuz_actual_data);
|
||||
assert!(
|
||||
baz_read_result.is_ok(),
|
||||
"Failed to read foo/bar/baz.txt from vfs"
|
||||
);
|
||||
assert!(
|
||||
quuz_read_result.is_ok(),
|
||||
"Failed to read qux/quux/quuz.txt from vfs"
|
||||
);
|
||||
let baz_expected_data: &[u8; 16] = b"baz baz baz baz\n";
|
||||
let quuz_expected_data: &[u8; 5] = b"quuz\n";
|
||||
assert_eq!(
|
||||
&baz_actual_data, baz_expected_data,
|
||||
"Contents of `foo/bar/baz.txt` is not correct"
|
||||
);
|
||||
assert_eq!(
|
||||
&quuz_actual_data, quuz_expected_data,
|
||||
"Contents of `qux/quux/quuz.txt` is not correct"
|
||||
);
|
||||
// assert import errors
|
||||
assert_eq!(
|
||||
vfs.import_errors.len(),
|
||||
0,
|
||||
"Expected no import errors. Found {} errors.",
|
||||
vfs.import_errors.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dup_test {
|
||||
use crate::vfs::vfs::{Fd, Vfs};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn duplicates_file_descriptor() {
|
||||
// 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
|
||||
let vfs_result = Vfs::from_tar_bytes(&archive[..]);
|
||||
// ASSERT:
|
||||
assert!(
|
||||
vfs_result.is_ok(),
|
||||
"Failed to create file system from archive"
|
||||
);
|
||||
let mut vfs = vfs_result.unwrap();
|
||||
|
||||
let source_fd = vfs.open_file("foo.txt").unwrap();
|
||||
let target_fd: Fd = 10;
|
||||
assert_ne!(
|
||||
source_fd, target_fd,
|
||||
"Test setup failed. The source descriptor is identical to desired target descriptor."
|
||||
);
|
||||
|
||||
let mut fds = vec![];
|
||||
fds.push(Arc::new(100));
|
||||
fds.push(Arc::new(200));
|
||||
|
||||
let result = vfs.duplicate_file_descriptor(source_fd, target_fd);
|
||||
|
||||
assert!(result.is_ok(), "Failed to duplicated file descriptor.");
|
||||
// assert import errors
|
||||
assert_eq!(
|
||||
vfs.import_errors.len(),
|
||||
0,
|
||||
"Expected no import errors. Found {} errors.",
|
||||
vfs.import_errors.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
41
lib/runtime-abi/src/vfs/virtual_file.rs
Normal file
41
lib/runtime-abi/src/vfs/virtual_file.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use failure::Error;
|
||||
|
||||
use crate::vfs::file_like::{FileLike, Metadata};
|
||||
|
||||
pub struct VirtualFile {
|
||||
zbox_file: zbox::File,
|
||||
}
|
||||
|
||||
impl VirtualFile {
|
||||
pub fn new(file: zbox::File) -> Self {
|
||||
VirtualFile { zbox_file: file }
|
||||
}
|
||||
}
|
||||
|
||||
impl FileLike for VirtualFile {
|
||||
fn write(&mut self, buf: &[u8], count: usize, offset: usize) -> Result<usize, Error> {
|
||||
use std::io::{Seek, SeekFrom};
|
||||
self.zbox_file.seek(SeekFrom::Start(offset as u64))?;
|
||||
let _ = self.zbox_file.write_once(&buf[..count])?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
use std::io::Read;
|
||||
self.zbox_file.read(buf).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Result<Metadata, Error> {
|
||||
self.zbox_file
|
||||
.metadata()
|
||||
.map(|m| Metadata {
|
||||
len: m.len(),
|
||||
is_file: m.is_file(),
|
||||
})
|
||||
.map_err(|e: zbox::Error| e.into())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user