diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/lucet-wasi-wasmsbx/src/hostcalls | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/lucet-wasi-wasmsbx/src/hostcalls')
5 files changed, 2682 insertions, 0 deletions
diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs.rs b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs.rs new file mode 100644 index 0000000000..db1a0c5d2d --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs.rs @@ -0,0 +1,1358 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +use crate::ctx::WasiCtx; +use crate::fdentry::{determine_type_rights, FdEntry}; +use crate::memory::*; +use crate::{host, wasm32}; + +use super::fs_helpers::*; +use super::timers; +use lucet_runtime::vmctx::Vmctx; + +use nix::libc::{self, c_long, c_void, off_t}; +use std::ffi::OsStr; +use std::mem::MaybeUninit; +use std::os::unix::prelude::{FromRawFd, OsStrExt}; + +pub fn wasi_fd_close(vmctx: &mut Vmctx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { + let mut ctx = vmctx.get_embed_ctx_mut::<WasiCtx>(); + let fd = dec_fd(fd); + if let Some(fdent) = ctx.fds.get(&fd) { + // can't close preopened files + if fdent.preopen_path.is_some() { + return wasm32::__WASI_ENOTSUP; + } + } + if let Some(mut fdent) = ctx.fds.remove(&fd) { + fdent.fd_object.needs_close = false; + match nix::unistd::close(fdent.fd_object.rawfd) { + Ok(_) => wasm32::__WASI_ESUCCESS, + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } + } else { + wasm32::__WASI_EBADF + } +} + +pub fn wasi_fd_fdstat_get( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + fdstat_ptr: wasm32::uintptr_t, // *mut wasm32::__wasi_fdstat_t +) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let mut host_fdstat = match dec_fdstat_byref(vmctx, fdstat_ptr) { + Ok(host_fdstat) => host_fdstat, + Err(e) => return enc_errno(e), + }; + + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let errno = if let Some(fe) = ctx.fds.get(&host_fd) { + host_fdstat.fs_filetype = fe.fd_object.ty; + host_fdstat.fs_rights_base = fe.rights_base; + host_fdstat.fs_rights_inheriting = fe.rights_inheriting; + use nix::fcntl::{fcntl, OFlag, F_GETFL}; + match fcntl(fe.fd_object.rawfd, F_GETFL).map(OFlag::from_bits_truncate) { + Ok(flags) => { + host_fdstat.fs_flags = host::fdflags_from_nix(flags); + wasm32::__WASI_ESUCCESS + } + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } + } else { + wasm32::__WASI_EBADF + }; + enc_fdstat_byref(vmctx, fdstat_ptr, host_fdstat) + .expect("can write back into the pointer we read from"); + errno +} + +pub fn wasi_fd_fdstat_set_flags( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + fdflags: wasm32::__wasi_fdflags_t, +) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let host_fdflags = dec_fdflags(fdflags); + let nix_flags = host::nix_from_fdflags(host_fdflags); + + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + if let Some(fe) = ctx.fds.get(&host_fd) { + match nix::fcntl::fcntl(fe.fd_object.rawfd, nix::fcntl::F_SETFL(nix_flags)) { + Ok(_) => wasm32::__WASI_ESUCCESS, + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } + } else { + wasm32::__WASI_EBADF + } +} + +pub fn wasi_fd_tell( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let fd = dec_fd(fd); + + let host_offset = { + use nix::unistd::{lseek, Whence}; + + let rights = host::__WASI_RIGHT_FD_TELL; + match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => match lseek(fe.fd_object.rawfd, 0, Whence::SeekCur) { + Ok(newoffset) => newoffset, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }, + Err(e) => return enc_errno(e), + } + }; + enc_filesize_byref(vmctx, offset, host_offset as u64) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_seek( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filedelta_t, + whence: wasm32::__wasi_whence_t, + newoffset: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let fd = dec_fd(fd); + let offset = dec_filedelta(offset); + let whence = dec_whence(whence); + + let host_newoffset = { + use nix::unistd::{lseek, Whence}; + let nwhence = match u32::from(whence) { + host::__WASI_WHENCE_CUR => Whence::SeekCur, + host::__WASI_WHENCE_END => Whence::SeekEnd, + host::__WASI_WHENCE_SET => Whence::SeekSet, + _ => return wasm32::__WASI_EINVAL, + }; + + let rights = if offset == 0 && whence as u32 == host::__WASI_WHENCE_CUR { + host::__WASI_RIGHT_FD_TELL + } else { + host::__WASI_RIGHT_FD_SEEK | host::__WASI_RIGHT_FD_TELL + }; + match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => match lseek(fe.fd_object.rawfd, offset, nwhence) { + Ok(newoffset) => newoffset, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }, + Err(e) => return enc_errno(e), + } + }; + enc_filesize_byref(vmctx, newoffset, host_newoffset as u64) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_prestat_get( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + prestat_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let fd = dec_fd(fd); + + let rights = host::__WASI_RIGHT_PATH_OPEN; + match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => { + if let Some(po_path) = &fe.preopen_path { + if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { + return wasm32::__WASI_ENOTDIR; + } + enc_prestat_byref( + vmctx, + prestat_ptr, + host::__wasi_prestat_t { + pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, + u: host::__wasi_prestat_t___wasi_prestat_u { + dir: host::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { + pr_name_len: po_path.as_os_str().as_bytes().len(), + }, + }, + }, + ) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } else { + wasm32::__WASI_ENOTSUP + } + } + Err(e) => enc_errno(e), + } +} + +pub fn wasi_fd_prestat_dir_name( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let fd = dec_fd(fd); + let rights = host::__WASI_RIGHT_PATH_OPEN; + match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => { + if let Some(po_path) = &fe.preopen_path { + if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { + return wasm32::__WASI_ENOTDIR; + } + let path_bytes = po_path.as_os_str().as_bytes(); + if path_bytes.len() > dec_usize(path_len) { + return wasm32::__WASI_ENAMETOOLONG; + } + enc_slice_of(vmctx, path_bytes, path_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } else { + wasm32::__WASI_ENOTSUP + } + } + Err(e) => enc_errno(e), + } +} + +pub fn wasi_fd_read( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nread: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::uio::{readv, IoVec}; + + let fd = dec_fd(fd); + let mut iovs = match dec_iovec_slice(vmctx, iovs_ptr, iovs_len) { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; + + let mut ctx = vmctx.get_embed_ctx_mut::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_READ; + let fe = match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + + let mut iovs: Vec<IoVec<&mut [u8]>> = iovs + .iter_mut() + .map(|iov| unsafe { host::iovec_to_nix_mut(iov) }) + .collect(); + + let host_nread = match readv(fe.fd_object.rawfd, &mut iovs) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; + + if host_nread == 0 { + // we hit eof, so remove the fdentry from the context + let mut fe = ctx.fds.remove(&fd).expect("file entry is still there"); + fe.fd_object.needs_close = false; + } + enc_usize_byref(vmctx, nread, host_nread) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_write( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nwritten: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::uio::{writev, IoVec}; + + let fd = dec_fd(fd); + let iovs = match dec_ciovec_slice(vmctx, iovs_ptr, iovs_len) { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; + + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_WRITE; + let fe = match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + + let iovs: Vec<IoVec<&[u8]>> = iovs + .iter() + .map(|iov| unsafe { host::ciovec_to_nix(iov) }) + .collect(); + + let host_nwritten = match writev(fe.fd_object.rawfd, &iovs) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; + enc_usize_byref(vmctx, nwritten, host_nwritten) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_path_open( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + oflags: wasm32::__wasi_oflags_t, + fs_rights_base: wasm32::__wasi_rights_t, + fs_rights_inheriting: wasm32::__wasi_rights_t, + fs_flags: wasm32::__wasi_fdflags_t, + fd_out_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::errno::Errno; + use nix::fcntl::{openat, AtFlags, OFlag}; + use nix::sys::stat::{fstatat, Mode, SFlag}; + + let dirfd = dec_fd(dirfd); + let dirflags = dec_lookupflags(dirflags); + let oflags = dec_oflags(oflags); + let fs_rights_base = dec_rights(fs_rights_base); + let fs_rights_inheriting = dec_rights(fs_rights_inheriting); + let fs_flags = dec_fdflags(fs_flags); + + // which open mode do we need? + let read = fs_rights_base + & ((host::__WASI_RIGHT_FD_READ | host::__WASI_RIGHT_FD_READDIR) as host::__wasi_rights_t) + != 0; + let write = fs_rights_base + & ((host::__WASI_RIGHT_FD_DATASYNC + | host::__WASI_RIGHT_FD_WRITE + | host::__WASI_RIGHT_FD_ALLOCATE + | host::__WASI_RIGHT_FD_FILESTAT_SET_SIZE) as host::__wasi_rights_t) + != 0; + + let mut nix_all_oflags = if read && write { + OFlag::O_RDWR + } else if read { + OFlag::O_RDONLY + } else { + OFlag::O_WRONLY + }; + + // on non-Capsicum systems, we always want nofollow + nix_all_oflags.insert(OFlag::O_NOFOLLOW); + + // which rights are needed on the dirfd? + let mut needed_base = host::__WASI_RIGHT_PATH_OPEN as host::__wasi_rights_t; + let mut needed_inheriting = fs_rights_base | fs_rights_inheriting; + + // convert open flags + let nix_oflags = host::nix_from_oflags(oflags); + nix_all_oflags.insert(nix_oflags); + if nix_all_oflags.contains(OFlag::O_CREAT) { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE as host::__wasi_rights_t; + } + if nix_all_oflags.contains(OFlag::O_TRUNC) { + needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE as host::__wasi_rights_t; + } + + // convert file descriptor flags + nix_all_oflags.insert(host::nix_from_fdflags(fs_flags)); + if nix_all_oflags.contains(OFlag::O_DSYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC as host::__wasi_rights_t; + } + if nix_all_oflags.intersects(O_RSYNC | OFlag::O_SYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_SYNC as host::__wasi_rights_t; + } + if nix_all_oflags.contains(OFlag::O_DIRECTORY) { + nix_all_oflags.remove(OFlag::O_RDWR); + nix_all_oflags.remove(OFlag::O_WRONLY); + nix_all_oflags.insert(OFlag::O_RDONLY); + } + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + + let (dir, path) = match path_get( + &vmctx, + dirfd, + dirflags, + path, + needed_base, + needed_inheriting, + nix_oflags.contains(OFlag::O_CREAT), + ) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + + // Call openat. Use mode 0o666 so that we follow whatever the user's + // umask is, but don't set the executable flag, because it isn't yet + // meaningful for WASI programs to create executable files. + let new_fd = match openat( + dir, + path.as_os_str(), + nix_all_oflags, + Mode::from_bits_truncate(0o666), + ) { + Ok(fd) => fd, + Err(e) => { + match e.as_errno() { + // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket + Some(Errno::ENXIO) => { + if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { + return wasm32::__WASI_ENOTSUP; + } else { + return wasm32::__WASI_ENXIO; + } + } else { + return wasm32::__WASI_ENXIO; + } + } + // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY + // on a symlink. + Some(Errno::ENOTDIR) + if !(nix_all_oflags & (OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY)).is_empty() => + { + if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFLNK) { + return wasm32::__WASI_ELOOP; + } + } + return wasm32::__WASI_ENOTDIR; + } + // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on + // a symlink. + Some(Errno::EMLINK) if !(nix_all_oflags & OFlag::O_NOFOLLOW).is_empty() => { + return wasm32::__WASI_ELOOP; + } + Some(e) => return wasm32::errno_from_nix(e), + None => return wasm32::__WASI_ENOSYS, + } + } + }; + + // Determine the type of the new file descriptor and which rights contradict with this type + let guest_fd = match unsafe { determine_type_rights(new_fd) } { + Err(e) => { + // if `close` fails, note it but do not override the underlying errno + nix::unistd::close(new_fd).unwrap_or_else(|e| { + dbg!(e); + }); + if let Err(e) = enc_fd_byref(vmctx, fd_out_ptr, wasm32::__wasi_fd_t::max_value()) { + return enc_errno(e); + } + return enc_errno(e); + } + Ok((_ty, max_base, max_inheriting)) => { + let mut fe = unsafe { FdEntry::from_raw_fd(new_fd) }; + fe.rights_base &= max_base; + fe.rights_inheriting &= max_inheriting; + match vmctx.get_embed_ctx_mut::<WasiCtx>().insert_fd_entry(fe) { + Ok(fd) => fd, + Err(e) => return enc_errno(e), + } + } + }; + enc_fd_byref(vmctx, fd_out_ptr, guest_fd) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_filestat_get( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + filestat_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::stat::fstat; + + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let rights = host::__WASI_RIGHT_FD_FILESTAT_GET; + match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => match fstat(fe.fd_object.rawfd) { + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + Ok(filestat) => { + let host_filestat = host::filestat_from_nix(filestat); + enc_filestat_byref(vmctx, filestat_ptr, host_filestat) + .expect("can write into the pointer"); + } + }, + Err(e) => return enc_errno(e), + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_filestat_get( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + filestat_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::fcntl::AtFlags; + use nix::sys::stat::fstatat; + + let dirfd = dec_fd(dirfd); + let dirflags = dec_lookupflags(dirflags); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_FILESTAT_GET; + let (dir, path) = match path_get(&vmctx, dirfd, dirflags, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let atflags = match dirflags { + wasm32::__WASI_LOOKUP_SYMLINK_FOLLOW => AtFlags::empty(), + _ => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + match fstatat(dir, path.as_os_str(), atflags) { + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + Ok(filestat) => { + let host_filestat = host::filestat_from_nix(filestat); + enc_filestat_byref(vmctx, filestat_ptr, host_filestat) + .expect("can write into the pointer"); + wasm32::__WASI_ESUCCESS + } + } +} + +pub fn wasi_path_create_directory( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::errno; + use nix::libc::mkdirat; + + let dirfd = dec_fd(dirfd); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_CREATE_DIRECTORY; + let (dir, path) = match path_get(&vmctx, dirfd, 0, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let path_cstr = match std::ffi::CString::new(path.as_os_str().as_bytes()) { + Ok(path_cstr) => path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + // nix doesn't expose mkdirat() yet + match unsafe { mkdirat(dir, path_cstr.as_ptr(), 0o777) } { + 0 => wasm32::__WASI_ESUCCESS, + _ => wasm32::errno_from_nix(errno::Errno::last()), + } +} + +pub fn wasi_path_unlink_file( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::errno; + use nix::libc::unlinkat; + + let dirfd = dec_fd(dirfd); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_UNLINK_FILE; + let (dir, path) = match path_get(&vmctx, dirfd, 0, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let path_cstr = match std::ffi::CString::new(path.as_os_str().as_bytes()) { + Ok(path_cstr) => path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + // nix doesn't expose unlinkat() yet + match unsafe { unlinkat(dir, path_cstr.as_ptr(), 0) } { + 0 => wasm32::__WASI_ESUCCESS, + _ => { + let mut e = errno::Errno::last(); + // Non-Linux implementations may return EPERM when attempting to remove a + // directory without `REMOVEDIR`. For WASI, adjust this to `EISDIR`. + #[cfg(not(linux))] + { + use nix::fcntl::AtFlags; + use nix::sys::stat::{fstatat, SFlag}; + if e == errno::Errno::EPERM { + if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR) { + e = errno::Errno::EISDIR; + } + } else { + e = errno::Errno::last(); + } + } + } + wasm32::errno_from_nix(e) + } + } +} + +pub fn wasi_fd_allocate( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filesize_t, + len: wasm32::__wasi_filesize_t, +) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_ALLOCATE; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let offset = dec_filesize(offset); + let len = dec_filesize(len); + + #[cfg(target_os = "linux")] + { + let res = + unsafe { libc::posix_fallocate(fe.fd_object.rawfd, offset as off_t, len as off_t) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + } + + #[cfg(not(target_os = "linux"))] + { + use nix::sys::stat::fstat; + use nix::unistd::ftruncate; + + match fstat(fe.fd_object.rawfd) { + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + Ok(st) => { + let current_size = st.st_size as u64; + let wanted_size = match offset.checked_add(len) { + Some(wanted_size) => wanted_size, + None => return wasm32::__WASI_E2BIG, + }; + if wanted_size > i64::max_value() as u64 { + return wasm32::__WASI_E2BIG; + } + if wanted_size > current_size { + if let Err(e) = ftruncate(fe.fd_object.rawfd, wanted_size as off_t) { + return wasm32::errno_from_nix(e.as_errno().unwrap()); + } + } + } + } + } + + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_advise( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filesize_t, + len: wasm32::__wasi_filesize_t, + advice: wasm32::__wasi_advice_t, +) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_ADVISE; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let advice = dec_advice(advice); + + #[cfg(target_os = "linux")] + { + let host_advice = match advice as u32 { + host::__WASI_ADVICE_DONTNEED => libc::POSIX_FADV_DONTNEED, + host::__WASI_ADVICE_SEQUENTIAL => libc::POSIX_FADV_SEQUENTIAL, + host::__WASI_ADVICE_WILLNEED => libc::POSIX_FADV_DONTNEED, + host::__WASI_ADVICE_NOREUSE => libc::POSIX_FADV_NOREUSE, + host::__WASI_ADVICE_RANDOM => libc::POSIX_FADV_RANDOM, + host::__WASI_ADVICE_NORMAL => libc::POSIX_FADV_NORMAL, + _ => return wasm32::__WASI_EINVAL, + }; + let offset = dec_filesize(offset); + let len = dec_filesize(len); + let res = unsafe { + libc::posix_fadvise( + fe.fd_object.rawfd, + offset as off_t, + len as off_t, + host_advice, + ) + }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + } + + #[cfg(not(target_os = "linux"))] + { + let _ = (fe, offset, len); + match advice as u32 { + host::__WASI_ADVICE_DONTNEED + | host::__WASI_ADVICE_SEQUENTIAL + | host::__WASI_ADVICE_WILLNEED + | host::__WASI_ADVICE_NOREUSE + | host::__WASI_ADVICE_RANDOM + | host::__WASI_ADVICE_NORMAL => {} + _ => return wasm32::__WASI_EINVAL, + } + } + + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_datasync(vmctx: &mut Vmctx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_DATASYNC; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let res; + + #[cfg(target_os = "linux")] + { + res = nix::unistd::fdatasync(fe.fd_object.rawfd); + } + + #[cfg(not(target_os = "linux"))] + { + res = nix::unistd::fsync(fe.fd_object.rawfd); + } + + if let Err(e) = res { + return wasm32::errno_from_nix(e.as_errno().unwrap()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_sync(vmctx: &mut Vmctx, fd: wasm32::__wasi_fd_t) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_SYNC; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let res = nix::unistd::fsync(fe.fd_object.rawfd); + if let Err(e) = res { + return wasm32::errno_from_nix(e.as_errno().unwrap()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_fdstat_set_rights( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + fs_rights_base: wasm32::__wasi_rights_t, + fs_rights_inheriting: wasm32::__wasi_rights_t, +) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let mut ctx = vmctx.get_embed_ctx_mut::<WasiCtx>(); + let fe = match ctx.fds.get_mut(&host_fd) { + Some(fe) => fe, + None => return wasm32::__WASI_EBADF, + }; + if fe.rights_base & fs_rights_base != fs_rights_base + || fe.rights_inheriting & fs_rights_inheriting != fs_rights_inheriting + { + return wasm32::__WASI_ENOTCAPABLE; + } + fe.rights_base = fs_rights_base; + fe.rights_inheriting = fs_rights_inheriting; + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_filestat_set_size( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + st_size: wasm32::__wasi_filesize_t, +) -> wasm32::__wasi_errno_t { + use nix::unistd::ftruncate; + + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_FILESTAT_SET_SIZE; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let st_size = dec_filesize(st_size); + if st_size > i64::max_value() as u64 { + return wasm32::__WASI_E2BIG; + } + if let Err(e) = ftruncate(fe.fd_object.rawfd, st_size as off_t) { + return wasm32::errno_from_nix(e.as_errno().unwrap()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_filestat_set_times( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + st_atim: wasm32::__wasi_timestamp_t, + st_mtim: wasm32::__wasi_timestamp_t, + fst_flags: wasm32::__wasi_fstflags_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::time::{TimeSpec, TimeValLike}; + + let host_fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_FILESTAT_SET_TIMES; + let fe = match ctx.get_fd_entry(host_fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let st_atim = dec_timestamp(st_atim); + let mut st_mtim = dec_timestamp(st_mtim); + let fst_flags = dec_fstflags(fst_flags); + if fst_flags & (host::__WASI_FILESTAT_SET_MTIM_NOW as host::__wasi_fstflags_t) != 0 { + let clock_id = libc::CLOCK_REALTIME; + let mut timespec = MaybeUninit::<libc::timespec>::uninit(); + let res = unsafe { timers::clock_gettime_helper(clock_id, timespec.as_mut_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + let timespec = unsafe { timespec.assume_init() }; + let time_ns = match (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + { + Some(time_ns) => time_ns, + None => return wasm32::__WASI_EOVERFLOW, + }; + st_mtim = time_ns; + } + let ts_atime = match fst_flags as u32 { + f if f & host::__WASI_FILESTAT_SET_ATIM_NOW != 0 => libc::timespec { + tv_sec: 0, + tv_nsec: utime_now(), + }, + f if f & host::__WASI_FILESTAT_SET_ATIM != 0 => { + *TimeSpec::nanoseconds(st_atim as i64).as_ref() + } + _ => libc::timespec { + tv_sec: 0, + tv_nsec: utime_omit(), + }, + }; + let ts_mtime = *TimeSpec::nanoseconds(st_mtim as i64).as_ref(); + let times = [ts_atime, ts_mtime]; + let res = unsafe { timers::futimens_helper(fe.fd_object.rawfd, times.as_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_filestat_set_times( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + st_atim: wasm32::__wasi_timestamp_t, + st_mtim: wasm32::__wasi_timestamp_t, + fst_flags: wasm32::__wasi_fstflags_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::time::{TimeSpec, TimeValLike}; + + let dirfd = dec_fd(dirfd); + let dirflags = dec_lookupflags(dirflags); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_FILESTAT_SET_TIMES; + let (dir, path) = match path_get(&vmctx, dirfd, dirflags, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let atflags = match dirflags { + wasm32::__WASI_LOOKUP_SYMLINK_FOLLOW => 0, + _ => libc::AT_SYMLINK_NOFOLLOW, + }; + let st_atim = dec_timestamp(st_atim); + let mut st_mtim = dec_timestamp(st_mtim); + let fst_flags = dec_fstflags(fst_flags); + if fst_flags & (host::__WASI_FILESTAT_SET_MTIM_NOW as host::__wasi_fstflags_t) != 0 { + let clock_id = libc::CLOCK_REALTIME; + let mut timespec = MaybeUninit::<libc::timespec>::uninit(); + let res = unsafe { timers::clock_gettime_helper(clock_id, timespec.as_mut_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + let timespec = unsafe { timespec.assume_init() }; + let time_ns = match (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + { + Some(time_ns) => time_ns, + None => return wasm32::__WASI_EOVERFLOW, + }; + st_mtim = time_ns; + } + let ts_atime = match fst_flags as u32 { + f if f & host::__WASI_FILESTAT_SET_ATIM_NOW != 0 => libc::timespec { + tv_sec: 0, + tv_nsec: utime_now(), + }, + f if f & host::__WASI_FILESTAT_SET_ATIM != 0 => { + *TimeSpec::nanoseconds(st_atim as i64).as_ref() + } + _ => libc::timespec { + tv_sec: 0, + tv_nsec: utime_omit(), + }, + }; + let ts_mtime = *TimeSpec::nanoseconds(st_mtim as i64).as_ref(); + let times = [ts_atime, ts_mtime]; + let path_cstr = match std::ffi::CString::new(path.as_os_str().as_bytes()) { + Ok(path_cstr) => path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let res = unsafe { timers::utimensat_helper(dir, path_cstr.as_ptr(), times.as_ptr(), atflags) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_fd_pread( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + offset: wasm32::__wasi_filesize_t, + nread: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::uio::pread; + use std::cmp; + + let fd = dec_fd(fd); + let iovs = match dec_iovec_slice(vmctx, iovs_ptr, iovs_len) { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_READ; + let fe = match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let offset = dec_filesize(offset); + if offset > i64::max_value() as u64 { + return wasm32::__WASI_EIO; + } + let buf_size = iovs.iter().map(|v| v.buf_len).sum(); + let mut buf = vec![0; buf_size]; + let host_nread = match pread(fe.fd_object.rawfd, &mut buf, offset as off_t) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; + let mut buf_offset = 0; + let mut left = host_nread; + for iov in &iovs { + if left == 0 { + break; + } + let vec_len = cmp::min(iov.buf_len, left); + unsafe { std::slice::from_raw_parts_mut(iov.buf as *mut u8, vec_len) } + .copy_from_slice(&buf[buf_offset..buf_offset + vec_len]); + buf_offset += vec_len; + left -= vec_len; + } + enc_usize_byref(vmctx, nread, host_nread) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_pwrite( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + offset: wasm32::__wasi_filesize_t, + nwritten: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::sys::uio::pwrite; + + let fd = dec_fd(fd); + let iovs = match dec_iovec_slice(vmctx, iovs_ptr, iovs_len) { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_READ; + let fe = match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let offset = dec_filesize(offset); + if offset > i64::max_value() as u64 { + return wasm32::__WASI_EIO; + } + let buf_size = iovs.iter().map(|v| v.buf_len).sum(); + let mut buf = Vec::with_capacity(buf_size); + for iov in &iovs { + buf.extend_from_slice(unsafe { + std::slice::from_raw_parts(iov.buf as *const u8, iov.buf_len) + }); + } + let host_nwritten = match pwrite(fe.fd_object.rawfd, &buf, offset as off_t) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; + enc_usize_byref(vmctx, nwritten, host_nwritten) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_readdir( + vmctx: &mut Vmctx, + fd: wasm32::__wasi_fd_t, + buf: wasm32::uintptr_t, + buf_len: wasm32::size_t, + cookie: wasm32::__wasi_dircookie_t, + bufused: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use libc::{dirent, fdopendir, readdir_r, seekdir}; + + match enc_usize_byref(vmctx, bufused, 0) { + Ok(_) => {} + Err(e) => return enc_errno(e), + }; + let fd = dec_fd(fd); + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + let rights = host::__WASI_RIGHT_FD_READDIR; + let fe = match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + let host_buf = match dec_slice_of::<u8>(vmctx, buf, buf_len) { + Ok(host_buf) => host_buf, + Err(e) => return enc_errno(e), + }; + let host_buf_ptr = host_buf.as_ptr(); + let host_buf_len = host_buf.len(); + let dir = unsafe { fdopendir(fe.fd_object.rawfd) }; + if dir.is_null() { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + let cookie = dec_dircookie(cookie); + if cookie != wasm32::__WASI_DIRCOOKIE_START { + unsafe { seekdir(dir, cookie as c_long) }; + } + let mut entry_buf = MaybeUninit::<dirent>::uninit(); + let mut left = host_buf_len; + let mut host_buf_offset: usize = 0; + while left > 0 { + let mut host_entry: *mut dirent = std::ptr::null_mut(); + let res = unsafe { readdir_r(dir, entry_buf.as_mut_ptr(), &mut host_entry) }; + if res == -1 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + if host_entry.is_null() { + break; + } + unsafe { entry_buf.assume_init() }; + let entry: wasm32::__wasi_dirent_t = match dirent_from_host(&unsafe { *host_entry }) { + Ok(entry) => entry, + Err(e) => return enc_errno(e), + }; + let name_len = entry.d_namlen as usize; + let required_space = std::mem::size_of_val(&entry) + name_len; + if required_space > left { + break; + } + unsafe { + let ptr = host_buf_ptr.offset(host_buf_offset as isize) as *mut c_void + as *mut wasm32::__wasi_dirent_t; + *ptr = entry; + } + host_buf_offset += std::mem::size_of_val(&entry); + let name_ptr = unsafe { *host_entry }.d_name.as_ptr(); + unsafe { + std::ptr::copy_nonoverlapping( + name_ptr as *const _, + host_buf_ptr.offset(host_buf_offset as isize) as *mut _, + name_len, + ) + }; + host_buf_offset += name_len; + left -= required_space; + } + let host_bufused = host_buf_len - left; + enc_usize_byref(vmctx, bufused, host_bufused) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_fd_renumber( + vmctx: &mut Vmctx, + from: wasm32::__wasi_fd_t, + to: wasm32::__wasi_fd_t, +) -> wasm32::__wasi_errno_t { + let from = dec_fd(from); + let to = dec_fd(to); + let mut ctx = vmctx.get_embed_ctx_mut::<WasiCtx>(); + let fe_from = match ctx.fds.get(&from) { + Some(fe_from) => fe_from, + None => return wasm32::__WASI_EBADF, + }; + let fe_to = match ctx.fds.get(&to) { + Some(fe_to) => fe_to, + None => return wasm32::__WASI_EBADF, + }; + if let Err(e) = nix::unistd::dup2(fe_from.fd_object.rawfd, fe_to.fd_object.rawfd) { + return wasm32::errno_from_nix(e.as_errno().unwrap()); + } + let fe_from_rawfd = fe_from.fd_object.rawfd; + ctx.fds.remove(&(fe_from_rawfd as host::__wasi_fd_t)); + + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_link( + vmctx: &mut Vmctx, + old_dirfd: wasm32::__wasi_fd_t, + _old_flags: wasm32::__wasi_lookupflags_t, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + new_dirfd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::libc::linkat; + + let old_dirfd = dec_fd(old_dirfd); + let new_dirfd = dec_fd(new_dirfd); + let old_path = match dec_slice_of::<u8>(vmctx, old_path_ptr, old_path_len) { + Ok(old_path_bytes) => OsStr::from_bytes(old_path_bytes), + Err(e) => return enc_errno(e), + }; + let new_path = match dec_slice_of::<u8>(vmctx, new_path_ptr, new_path_len) { + Ok(new_path_bytes) => OsStr::from_bytes(new_path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_LINK_SOURCE; + let (old_dir, old_path) = + match path_get(&vmctx, old_dirfd, 0, old_path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_LINK_TARGET; + let (new_dir, new_path) = + match path_get(&vmctx, new_dirfd, 0, new_path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let old_path_cstr = match std::ffi::CString::new(old_path.as_bytes()) { + Ok(old_path_cstr) => old_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let new_path_cstr = match std::ffi::CString::new(new_path.as_bytes()) { + Ok(new_path_cstr) => new_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + + // Not setting AT_SYMLINK_FOLLOW fails on most filesystems + let atflags = libc::AT_SYMLINK_FOLLOW; + let res = unsafe { + linkat( + old_dir, + old_path_cstr.as_ptr(), + new_dir, + new_path_cstr.as_ptr(), + atflags, + ) + }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_readlink( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, + bufused: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + use nix::fcntl::readlinkat; + use std::cmp; + + match enc_usize_byref(vmctx, bufused, 0) { + Ok(_) => {} + Err(e) => return enc_errno(e), + }; + let dirfd = dec_fd(dirfd); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_READLINK; + let (dir, path) = match path_get(&vmctx, dirfd, 0, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let dummy_buf = &mut [0u8]; + let mut buf = if buf_len > 0 { + match dec_slice_of_mut::<u8>(vmctx, buf_ptr, buf_len) { + Ok(buf) => buf, + Err(e) => return enc_errno(e), + } + } else { + dummy_buf + }; + let target_path = match readlinkat(dir, path.as_os_str(), &mut buf) { + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + Ok(target_path) => target_path, + }; + let host_bufused = cmp::min(buf_len as usize, target_path.len()); + match enc_usize_byref(vmctx, bufused, host_bufused) { + Ok(_) => {} + Err(e) => return enc_errno(e), + }; + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_remove_directory( + vmctx: &mut Vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::errno; + use nix::libc::{unlinkat, AT_REMOVEDIR}; + + let dirfd = dec_fd(dirfd); + let path = match dec_slice_of::<u8>(vmctx, path_ptr, path_len) { + Ok(path_bytes) => OsStr::from_bytes(path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_REMOVE_DIRECTORY; + let (dir, path) = match path_get(&vmctx, dirfd, 0, path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let path_cstr = match std::ffi::CString::new(path.as_os_str().as_bytes()) { + Ok(path_cstr) => path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + // nix doesn't expose unlinkat() yet + match unsafe { unlinkat(dir, path_cstr.as_ptr(), AT_REMOVEDIR) } { + 0 => wasm32::__WASI_ESUCCESS, + _ => wasm32::errno_from_nix(errno::Errno::last()), + } +} + +pub fn wasi_path_rename( + vmctx: &mut Vmctx, + old_dirfd: wasm32::__wasi_fd_t, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + new_dirfd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::libc::renameat; + + let old_dirfd = dec_fd(old_dirfd); + let new_dirfd = dec_fd(new_dirfd); + let old_path = match dec_slice_of::<u8>(vmctx, old_path_ptr, old_path_len) { + Ok(old_path_bytes) => OsStr::from_bytes(old_path_bytes), + Err(e) => return enc_errno(e), + }; + let new_path = match dec_slice_of::<u8>(vmctx, new_path_ptr, new_path_len) { + Ok(new_path_bytes) => OsStr::from_bytes(new_path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_RENAME_SOURCE; + let (old_dir, old_path) = + match path_get(&vmctx, old_dirfd, 0, old_path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_RENAME_TARGET; + let (new_dir, new_path) = + match path_get(&vmctx, new_dirfd, 0, new_path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let old_path_cstr = match std::ffi::CString::new(old_path.as_bytes()) { + Ok(old_path_cstr) => old_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let new_path_cstr = match std::ffi::CString::new(new_path.as_bytes()) { + Ok(new_path_cstr) => new_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let res = unsafe { + renameat( + old_dir, + old_path_cstr.as_ptr(), + new_dir, + new_path_cstr.as_ptr(), + ) + }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_path_symlink( + vmctx: &mut Vmctx, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + dirfd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use nix::libc::symlinkat; + + let dirfd = dec_fd(dirfd); + let old_path = match dec_slice_of::<u8>(vmctx, old_path_ptr, old_path_len) { + Ok(old_path_bytes) => OsStr::from_bytes(old_path_bytes), + Err(e) => return enc_errno(e), + }; + let new_path = match dec_slice_of::<u8>(vmctx, new_path_ptr, new_path_len) { + Ok(new_path_bytes) => OsStr::from_bytes(new_path_bytes), + Err(e) => return enc_errno(e), + }; + let rights = host::__WASI_RIGHT_PATH_SYMLINK; + let (dir, new_path) = match path_get(&vmctx, dirfd, 0, new_path, rights.into(), 0, false) { + Ok((dir, path)) => (dir, path), + Err(e) => return enc_errno(e), + }; + let old_path_cstr = match std::ffi::CString::new(old_path.as_bytes()) { + Ok(old_path_cstr) => old_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let new_path_cstr = match std::ffi::CString::new(new_path.as_bytes()) { + Ok(new_path_cstr) => new_path_cstr, + Err(_) => return wasm32::__WASI_EINVAL, + }; + let res = unsafe { symlinkat(old_path_cstr.as_ptr(), dir, new_path_cstr.as_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + wasm32::__WASI_ESUCCESS +} diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs_helpers.rs b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs_helpers.rs new file mode 100644 index 0000000000..a82924e481 --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/fs_helpers.rs @@ -0,0 +1,306 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] +use crate::ctx::WasiCtx; +use crate::host; + +use lucet_runtime::vmctx::Vmctx; + +use nix::libc::{self, c_long}; +use std::ffi::{OsStr, OsString}; +use std::os::unix::prelude::{OsStrExt, OsStringExt, RawFd}; + +#[cfg(target_os = "linux")] +pub const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC; + +#[cfg(not(target_os = "linux"))] +pub const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC; + +/// Normalizes a path to ensure that the target path is located under the directory provided. +/// +/// This is a workaround for not having Capsicum support in the OS. +pub fn path_get<P: AsRef<OsStr>>( + vmctx: &Vmctx, + dirfd: host::__wasi_fd_t, + dirflags: host::__wasi_lookupflags_t, + path: P, + needed_base: host::__wasi_rights_t, + needed_inheriting: host::__wasi_rights_t, + needs_final_component: bool, +) -> Result<(RawFd, OsString), host::__wasi_errno_t> { + use nix::errno::Errno; + use nix::fcntl::{openat, readlinkat, OFlag}; + use nix::sys::stat::Mode; + + const MAX_SYMLINK_EXPANSIONS: usize = 128; + + /// close all the intermediate file descriptors, but make sure not to drop either the original + /// dirfd or the one we return (which may be the same dirfd) + fn ret_dir_success(dir_stack: &mut Vec<RawFd>) -> RawFd { + let ret_dir = dir_stack.pop().expect("there is always a dirfd to return"); + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + nix::unistd::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + ret_dir + } + + /// close all file descriptors other than the base directory, and return the errno for + /// convenience with `return` + fn ret_error( + dir_stack: &mut Vec<RawFd>, + errno: host::__wasi_errno_t, + ) -> Result<(RawFd, OsString), host::__wasi_errno_t> { + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + nix::unistd::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + Err(errno) + } + + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let dirfe = ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; + + // Stack of directory file descriptors. Index 0 always corresponds with the directory provided + // to this function. Entering a directory causes a file descriptor to be pushed, while handling + // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply + // escaping the base directory. + let mut dir_stack = vec![dirfe.fd_object.rawfd]; + + // Stack of paths left to process. This is initially the `path` argument to this function, but + // any symlinks we encounter are processed by pushing them on the stack. + let mut path_stack = vec![path.as_ref().to_owned().into_vec()]; + + // Track the number of symlinks we've expanded, so we can return `ELOOP` after too many. + let mut symlink_expansions = 0; + + // Buffer to read links into; defined outside of the loop so we don't reallocate it constantly. + let mut readlink_buf = vec![0u8; libc::PATH_MAX as usize + 1]; + + // TODO: rewrite this using a custom posix path type, with a component iterator that respects + // trailing slashes. This version does way too much allocation, and is way too fiddly. + loop { + let component = if let Some(cur_path) = path_stack.pop() { + // eprintln!( + // "cur_path = {:?}", + // std::str::from_utf8(cur_path.as_slice()).unwrap() + // ); + let mut split = cur_path.splitn(2, |&c| c == b'/'); + let head = split.next(); + let tail = split.next(); + match (head, tail) { + (None, _) => { + // split always returns at least a singleton iterator with an empty slice + panic!("unreachable"); + } + // path is empty + (Some([]), None) => { + return ret_error(&mut dir_stack, host::__WASI_ENOENT as host::__wasi_errno_t); + } + // path starts with `/`, is absolute + (Some([]), Some(_)) => { + return ret_error( + &mut dir_stack, + host::__WASI_ENOTCAPABLE as host::__wasi_errno_t, + ); + } + // the final component of the path with no trailing slash + (Some(component), None) => component.to_vec(), + (Some(component), Some(rest)) => { + if rest.iter().all(|&c| c == b'/') { + // the final component of the path with trailing slashes; put one trailing + // slash back on + let mut component = component.to_vec(); + component.push('/' as u8); + component + } else { + // non-final component; push the rest back on the stack + path_stack.push(rest.to_vec()); + component.to_vec() + } + } + } + } else { + // if the path stack is ever empty, we return rather than going through the loop again + panic!("unreachable"); + }; + + // eprintln!( + // "component = {:?}", + // std::str::from_utf8(component.as_slice()).unwrap() + // ); + + match component.as_slice() { + b"." => { + // skip component + } + b".." => { + // pop a directory + let dirfd = dir_stack.pop().expect("dir_stack is never empty"); + + // we're not allowed to pop past the original directory + if dir_stack.is_empty() { + return ret_error( + &mut dir_stack, + host::__WASI_ENOTCAPABLE as host::__wasi_errno_t, + ); + } else { + nix::unistd::close(dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + // should the component be a directory? it should if there is more path left to process, or + // if it has a trailing slash and `needs_final_component` is not set + component + if !path_stack.is_empty() + || (component.ends_with(b"/") && !needs_final_component) => + { + match openat( + *dir_stack.last().expect("dir_stack is never empty"), + component, + OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW, + Mode::empty(), + ) { + Ok(new_dir) => { + dir_stack.push(new_dir); + continue; + } + Err(e) + // Check to see if it was a symlink. Linux indicates + // this with ENOTDIR because of the O_DIRECTORY flag. + if e.as_errno() == Some(Errno::ELOOP) + || e.as_errno() == Some(Errno::EMLINK) + || e.as_errno() == Some(Errno::ENOTDIR) => + { + // attempt symlink expansion + match readlinkat( + *dir_stack.last().expect("dir_stack is never empty"), + component, + readlink_buf.as_mut_slice(), + ) { + Ok(link_path) => { + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return ret_error( + &mut dir_stack, + host::__WASI_ELOOP as host::__wasi_errno_t, + ); + } + + let mut link_path = link_path.as_bytes().to_vec(); + + // append a trailing slash if the component leading to it has one, so + // that we preserve any ENOTDIR that might come from trying to open a + // non-directory + if component.ends_with(b"/") { + link_path.push(b'/'); + } + + path_stack.push(link_path); + continue; + } + Err(e) => { + return ret_error( + &mut dir_stack, + host::errno_from_nix(e.as_errno().unwrap()), + ); + } + } + } + Err(e) => { + return ret_error( + &mut dir_stack, + host::errno_from_nix(e.as_errno().unwrap()), + ); + } + } + } + // the final component + component => { + // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt + // symlink expansion + if component.ends_with(b"/") || (dirflags & host::__WASI_LOOKUP_SYMLINK_FOLLOW) != 0 + { + match readlinkat( + *dir_stack.last().expect("dir_stack is never empty"), + component, + readlink_buf.as_mut_slice(), + ) { + Ok(link_path) => { + symlink_expansions += 1; + if symlink_expansions > MAX_SYMLINK_EXPANSIONS { + return ret_error( + &mut dir_stack, + host::__WASI_ELOOP as host::__wasi_errno_t, + ); + } + + let mut link_path = link_path.as_bytes().to_vec(); + + // append a trailing slash if the component leading to it has one, so + // that we preserve any ENOTDIR that might come from trying to open a + // non-directory + if component.ends_with(b"/") { + link_path.push(b'/'); + } + + path_stack.push(link_path); + continue; + } + Err(e) => { + let errno = e.as_errno().unwrap(); + if errno != Errno::EINVAL && errno != Errno::ENOENT { + // only return an error if this path is not actually a symlink + return ret_error(&mut dir_stack, host::errno_from_nix(errno)); + } + } + } + } + + // not a symlink, so we're done; + return Ok(( + ret_dir_success(&mut dir_stack), + OsStr::from_bytes(component).to_os_string(), + )); + } + } + + if path_stack.is_empty() { + // no further components to process. means we've hit a case like "." or "a/..", or if the + // input path has trailing slashes and `needs_final_component` is not set + return Ok(( + ret_dir_success(&mut dir_stack), + OsStr::new(".").to_os_string(), + )); + } else { + continue; + } + } +} + +#[cfg(not(target_os = "macos"))] +pub fn utime_now() -> c_long { + libc::UTIME_NOW +} + +#[cfg(target_os = "macos")] +pub fn utime_now() -> c_long { + -1 +} + +#[cfg(not(target_os = "macos"))] +pub fn utime_omit() -> c_long { + libc::UTIME_OMIT +} + +#[cfg(target_os = "macos")] +pub fn utime_omit() -> c_long { + -2 +} diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/misc.rs b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/misc.rs new file mode 100644 index 0000000000..39ab5109c1 --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/misc.rs @@ -0,0 +1,457 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +use crate::ctx::WasiCtx; +use crate::memory::*; +use crate::{host, wasm32}; + +use super::timers; + +use cast::From as _0; +use lucet_runtime::lucet_hostcall_terminate; +use lucet_runtime::vmctx::Vmctx; + +use nix::convert_ioctl_res; +use nix::libc::{self, c_int}; +use std::cmp; +use std::mem::MaybeUninit; +use std::time::SystemTime; + +// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` +nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); + +fn wasi_clock_to_relative_ns_delay( + wasi_clock: host::__wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_clock_t, +) -> u128 { + if wasi_clock.flags != wasm32::__WASI_SUBSCRIPTION_CLOCK_ABSTIME { + return wasi_clock.timeout as u128; + } + let now: u128 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Current date is before the epoch") + .as_nanos(); + let deadline = wasi_clock.timeout as u128; + deadline.saturating_sub(now) +} + +#[derive(Debug, Copy, Clone)] +struct ClockEventData { + delay: u128, + userdata: host::__wasi_userdata_t, +} +#[derive(Debug, Copy, Clone)] +struct FdEventData { + fd: c_int, + type_: host::__wasi_eventtype_t, + userdata: host::__wasi_userdata_t, +} + +pub fn wasi_proc_exit(_vmctx: &mut Vmctx, rval: wasm32::__wasi_exitcode_t) -> ! { + lucet_hostcall_terminate!(dec_exitcode(rval)); +} + +pub fn wasi_args_get( + vmctx: &mut Vmctx, + argv_ptr: wasm32::uintptr_t, + argv_buf: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let mut argv_buf_offset = 0; + let mut argv = vec![]; + + for arg in ctx.args.iter() { + let arg_bytes = arg.as_bytes_with_nul(); + let arg_ptr = argv_buf + argv_buf_offset; + + if let Err(e) = enc_slice_of(vmctx, arg_bytes, arg_ptr) { + return enc_errno(e); + } + + argv.push(arg_ptr); + + argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( + wasm32::uintptr_t::cast(arg_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } + enc_slice_of(vmctx, argv.as_slice(), argv_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_args_sizes_get( + vmctx: &mut Vmctx, + argc_ptr: wasm32::uintptr_t, + argv_buf_size_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let argc = ctx.args.len(); + let argv_size = ctx + .args + .iter() + .map(|arg| arg.as_bytes_with_nul().len()) + .sum(); + if let Err(e) = enc_usize_byref(vmctx, argc_ptr, argc) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(vmctx, argv_buf_size_ptr, argv_size) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_sched_yield(_vmctx: &mut Vmctx) -> wasm32::__wasi_errno_t { + unsafe { libc::sched_yield() }; + wasm32::__WASI_ESUCCESS +} + +pub fn wasi_clock_res_get( + vmctx: &mut Vmctx, + clock_id: wasm32::__wasi_clockid_t, + resolution_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; + + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = MaybeUninit::<libc::timespec>::uninit(); + let res = unsafe { timers::clock_getres_helper(clock_id, timespec.as_mut_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + let timespec = unsafe { timespec.assume_init() }; + + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map(|resolution| { + // a supported clock can never return zero; this case will probably never get hit, but + // make sure we follow the spec + if resolution == 0 { + wasm32::__WASI_EINVAL + } else { + enc_timestamp_byref(vmctx, resolution_ptr, resolution) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } + }) + .unwrap_or(wasm32::__WASI_EOVERFLOW) +} + +pub fn wasi_clock_time_get( + vmctx: &mut Vmctx, + clock_id: wasm32::__wasi_clockid_t, + // ignored for now, but will be useful once we put optional limits on precision to reduce side + // channels + _precision: wasm32::__wasi_timestamp_t, + time_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; + + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = MaybeUninit::<libc::timespec>::uninit(); + let res = unsafe { timers::clock_gettime_helper(clock_id, timespec.as_mut_ptr()) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + let timespec = unsafe { timespec.assume_init() }; + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map(|time| { + enc_timestamp_byref(vmctx, time_ptr, time) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + }) + .unwrap_or(wasm32::__WASI_EOVERFLOW) +} + +pub fn wasi_environ_get( + vmctx: &mut Vmctx, + environ_ptr: wasm32::uintptr_t, + environ_buf: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let mut environ_buf_offset = 0; + let mut environ = vec![]; + + for pair in ctx.env.iter() { + let env_bytes = pair.as_bytes_with_nul(); + let env_ptr = environ_buf + environ_buf_offset; + + if let Err(e) = enc_slice_of(vmctx, env_bytes, env_ptr) { + return enc_errno(e); + } + + environ.push(env_ptr); + + environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( + wasm32::uintptr_t::cast(env_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } + enc_slice_of(vmctx, environ.as_slice(), environ_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) +} + +pub fn wasi_environ_sizes_get( + vmctx: &mut Vmctx, + environ_count_ptr: wasm32::uintptr_t, + environ_size_ptr: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + let ctx = vmctx.get_embed_ctx::<WasiCtx>(); + + let environ_count = ctx.env.len(); + if let Some(environ_size) = ctx.env.iter().try_fold(0, |acc: u32, pair| { + acc.checked_add(pair.as_bytes_with_nul().len() as u32) + }) { + if let Err(e) = enc_usize_byref(vmctx, environ_count_ptr, environ_count) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(vmctx, environ_size_ptr, environ_size as usize) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS + } else { + wasm32::__WASI_EOVERFLOW + } +} + +pub fn wasi_random_get( + vmctx: &mut Vmctx, + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, +) -> wasm32::__wasi_errno_t { + use rand::{thread_rng, RngCore}; + + let buf = match dec_slice_of_mut::<u8>(vmctx, buf_ptr, buf_len) { + Ok(buf) => buf, + Err(e) => return enc_errno(e), + }; + thread_rng().fill_bytes(buf); + + return wasm32::__WASI_ESUCCESS; +} + +fn _wasi_poll_oneoff_handle_timeout_event( + output_slice: &mut [wasm32::__wasi_event_t], + timeout: Option<ClockEventData>, +) -> wasm32::size_t { + if let Some(ClockEventData { userdata, .. }) = timeout { + let output_event = host::__wasi_event_t { + userdata, + type_: wasm32::__WASI_EVENTTYPE_CLOCK, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + }, + }; + output_slice[0] = enc_event(output_event); + 1 + } else { + // shouldn't happen + 0 + } +} + +fn _wasi_poll_oneoff_handle_fd_event<'t>( + output_slice: &mut [wasm32::__wasi_event_t], + events: impl Iterator<Item = (&'t FdEventData, &'t nix::poll::PollFd)>, +) -> wasm32::size_t { + let mut output_slice_cur = output_slice.iter_mut(); + let mut revents_count = 0; + for (fd_event, poll_fd) in events { + let revents = match poll_fd.revents() { + Some(revents) => revents, + None => continue, + }; + let mut nbytes = 0; + if fd_event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ { + let _ = unsafe { fionread(fd_event.fd, &mut nbytes) }; + } + let output_event = if revents.contains(nix::poll::EventFlags::POLLNVAL) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EBADF, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLERR) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EIO, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLHUP) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLIN) + | revents.contains(nix::poll::EventFlags::POLLOUT) + { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: nbytes as host::__wasi_filesize_t, + flags: 0, + }, + }, + } + } else { + continue; + }; + *output_slice_cur.next().unwrap() = enc_event(output_event); + revents_count += 1; + } + revents_count +} + +pub fn wasi_poll_oneoff( + vmctx: &mut Vmctx, + input: wasm32::uintptr_t, + output: wasm32::uintptr_t, + nsubscriptions: wasm32::size_t, + nevents: wasm32::uintptr_t, +) -> wasm32::__wasi_errno_t { + if nsubscriptions as u64 > wasm32::__wasi_filesize_t::max_value() { + return wasm32::__WASI_EINVAL; + } + enc_pointee(vmctx, nevents, 0).unwrap(); + + let input_slice = + dec_slice_of::<wasm32::__wasi_subscription_t>(vmctx, input, nsubscriptions).unwrap(); + + let output_slice = + dec_slice_of_mut::<wasm32::__wasi_event_t>(vmctx, output, nsubscriptions).unwrap(); + + let input: Vec<_> = input_slice.iter().map(|x| dec_subscription(x)).collect(); + + let timeout = input + .iter() + .filter_map(|event| match event { + Ok(event) if event.type_ == wasm32::__WASI_EVENTTYPE_CLOCK => Some(ClockEventData { + delay: wasi_clock_to_relative_ns_delay(unsafe { event.u.clock }) / 1_000_000, + userdata: event.userdata, + }), + _ => None, + }) + .min_by_key(|event| event.delay); + let fd_events: Vec<_> = input + .iter() + .filter_map(|event| match event { + Ok(event) + if event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ + || event.type_ == wasm32::__WASI_EVENTTYPE_FD_WRITE => + { + Some(FdEventData { + fd: unsafe { event.u.fd_readwrite.fd } as c_int, + type_: event.type_, + userdata: event.userdata, + }) + } + _ => None, + }) + .collect(); + if fd_events.is_empty() && timeout.is_none() { + return wasm32::__WASI_ESUCCESS; + } + let mut poll_fds: Vec<_> = fd_events + .iter() + .map(|event| { + let mut flags = nix::poll::EventFlags::empty(); + match event.type_ { + wasm32::__WASI_EVENTTYPE_FD_READ => flags.insert(nix::poll::EventFlags::POLLIN), + wasm32::__WASI_EVENTTYPE_FD_WRITE => flags.insert(nix::poll::EventFlags::POLLOUT), + // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE + // Nothing else has been defined in the specification, and these are also the only two + // events we filtered before. If we get something else here, the code has a serious bug. + _ => unreachable!(), + }; + nix::poll::PollFd::new(event.fd, flags) + }) + .collect(); + let timeout = timeout.map(|ClockEventData { delay, userdata }| ClockEventData { + delay: cmp::min(delay, c_int::max_value() as u128), + userdata, + }); + let poll_timeout = timeout.map(|timeout| timeout.delay as c_int).unwrap_or(-1); + let ready = loop { + match nix::poll::poll(&mut poll_fds, poll_timeout) { + Err(_) => { + if nix::errno::Errno::last() == nix::errno::Errno::EINTR { + continue; + } + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + Ok(ready) => break ready as usize, + } + }; + let events_count = if ready == 0 { + _wasi_poll_oneoff_handle_timeout_event(output_slice, timeout) + } else { + let events = fd_events.iter().zip(poll_fds.iter()).take(ready); + _wasi_poll_oneoff_handle_fd_event(output_slice, events) + }; + if let Err(e) = enc_pointee(vmctx, nevents, events_count) { + return enc_errno(e); + } + wasm32::__WASI_ESUCCESS +} diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/mod.rs b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/mod.rs new file mode 100644 index 0000000000..ed9b5e038d --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/mod.rs @@ -0,0 +1,448 @@ +//! Hostcalls that implement +//! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). +//! +//! This code borrows heavily from [wasmtime-wasi](https://github.com/CraneStation/wasmtime-wasi), +//! which in turn borrows from cloudabi-utils. See `LICENSE.wasmtime-wasi` for license information. + +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +mod fs; +mod fs_helpers; +mod misc; +mod timers; + +use crate::wasm32; + +use fs::*; +use lucet_runtime::lucet_hostcalls; +use misc::*; + +lucet_hostcalls! { + #[no_mangle] pub unsafe extern "C" + fn __wasi_proc_exit(&mut vmctx, rval: wasm32::__wasi_exitcode_t,) -> ! { + wasi_proc_exit(vmctx, rval) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_args_get( + &mut vmctx, + argv_ptr: wasm32::uintptr_t, + argv_buf: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_args_get(vmctx, argv_ptr, argv_buf) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_args_sizes_get(&mut vmctx, + argc_ptr: wasm32::uintptr_t, + argv_buf_size_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_args_sizes_get(vmctx, argc_ptr, argv_buf_size_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_sched_yield(&mut vmctx,) -> wasm32::__wasi_errno_t { + wasi_sched_yield(vmctx) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_clock_res_get( + &mut vmctx, + clock_id: wasm32::__wasi_clockid_t, + resolution_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_clock_res_get(vmctx, clock_id, resolution_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_clock_time_get( + &mut vmctx, + clock_id: wasm32::__wasi_clockid_t, + precision: wasm32::__wasi_timestamp_t, + time_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_clock_time_get(vmctx, clock_id, precision, time_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_environ_get( + &mut vmctx, + environ_ptr: wasm32::uintptr_t, + environ_buf: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_environ_get(vmctx, environ_ptr, environ_buf) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_environ_sizes_get( + &mut vmctx, + environ_count_ptr: wasm32::uintptr_t, + environ_size_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_environ_sizes_get(vmctx, environ_count_ptr, environ_size_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_close( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_close(vmctx, fd) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_fdstat_get( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + fdstat_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_fdstat_get(vmctx, fd, fdstat_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_fdstat_set_flags( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + fdflags: wasm32::__wasi_fdflags_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_fdstat_set_flags(vmctx, fd, fdflags) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_tell(&mut vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_tell(vmctx, fd, offset) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_seek(&mut vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filedelta_t, + whence: wasm32::__wasi_whence_t, + newoffset: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_seek(vmctx, fd, offset, whence, newoffset) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_prestat_get( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + prestat_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_prestat_get(vmctx, fd, prestat_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_prestat_dir_name( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_prestat_dir_name(vmctx, fd, path_ptr, path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_read( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nread: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_read(vmctx, fd, iovs_ptr, iovs_len, nread) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_write( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nwritten: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_write(vmctx, fd, iovs_ptr, iovs_len, nwritten) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_open( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + oflags: wasm32::__wasi_oflags_t, + fs_rights_base: wasm32::__wasi_rights_t, + fs_rights_inheriting: wasm32::__wasi_rights_t, + fs_flags: wasm32::__wasi_fdflags_t, + fd_out_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_open(vmctx, dirfd, dirflags, path_ptr, path_len, + oflags, fs_rights_base, fs_rights_inheriting, fs_flags, + fd_out_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_random_get( + &mut vmctx, + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_random_get(vmctx, buf_ptr, buf_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_poll_oneoff( + &mut vmctx, + input: wasm32::uintptr_t, + output: wasm32::uintptr_t, + nsubscriptions: wasm32::size_t, + nevents: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_poll_oneoff(vmctx, input, output, nsubscriptions, nevents) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_filestat_get( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + filestat_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_filestat_get(vmctx, fd, filestat_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_filestat_get( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + filestat_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_filestat_get(vmctx, dirfd, dirflags, path_ptr, + path_len, filestat_ptr) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_create_directory( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_create_directory(vmctx, dirfd, path_ptr, path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_unlink_file( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_unlink_file(vmctx, dirfd, path_ptr, path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_allocate( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filesize_t, + len: wasm32::__wasi_filesize_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_allocate(vmctx, fd, offset, len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_advise( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filesize_t, + len: wasm32::__wasi_filesize_t, + advice: wasm32::__wasi_advice_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_advise(vmctx, fd, offset, len, advice) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_datasync( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_datasync(vmctx, fd) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_sync( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_sync(vmctx, fd) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_fdstat_set_rights( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + fs_rights_base: wasm32::__wasi_rights_t, + fs_rights_inheriting: wasm32::__wasi_rights_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_fdstat_set_rights(vmctx, fd, fs_rights_base, fs_rights_inheriting) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_filestat_set_size( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + st_size: wasm32::__wasi_filesize_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_filestat_set_size(vmctx, fd, st_size) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_filestat_set_times( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + st_atim: wasm32::__wasi_timestamp_t, + st_mtim: wasm32::__wasi_timestamp_t, + fst_flags: wasm32::__wasi_fstflags_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_filestat_set_times(vmctx, fd, st_atim, st_mtim, fst_flags) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_pread( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + offset: wasm32::__wasi_filesize_t, + nread: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_pread(vmctx, fd, iovs_ptr, iovs_len, offset, nread) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_pwrite( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + offset: wasm32::__wasi_filesize_t, + nwritten: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_pwrite(vmctx, fd, iovs_ptr, iovs_len, offset, nwritten) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_readdir( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + buf: wasm32::uintptr_t, + buf_len: wasm32::size_t, + cookie: wasm32::__wasi_dircookie_t, + bufused: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_readdir(vmctx, fd, buf, buf_len, cookie, bufused) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_fd_renumber( + &mut vmctx, + from: wasm32::__wasi_fd_t, + to: wasm32::__wasi_fd_t, + ) -> wasm32::__wasi_errno_t { + wasi_fd_renumber(vmctx, from, to) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_filestat_set_times( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + st_atim: wasm32::__wasi_timestamp_t, + st_mtim: wasm32::__wasi_timestamp_t, + fst_flags: wasm32::__wasi_fstflags_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_filestat_set_times(vmctx, dirfd, dirflags, path_ptr, path_len, st_atim, st_mtim, fst_flags) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_link( + &mut vmctx, + old_fd: wasm32::__wasi_fd_t, + old_flags: wasm32::__wasi_lookupflags_t, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + new_fd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_link(vmctx, old_fd, old_flags, old_path_ptr, old_path_len, + new_fd, new_path_ptr, new_path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_readlink( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, + bufused: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_readlink(vmctx, dirfd, path_ptr, path_len, buf_ptr, buf_len, bufused) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_remove_directory( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_remove_directory(vmctx, dirfd, path_ptr, path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_rename( + &mut vmctx, + old_dirfd: wasm32::__wasi_fd_t, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + new_dirfd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_rename(vmctx, old_dirfd, old_path_ptr, old_path_len, + new_dirfd, new_path_ptr, new_path_len) + } + + #[no_mangle] pub unsafe extern "C" + fn __wasi_path_symlink( + &mut vmctx, + old_path_ptr: wasm32::uintptr_t, + old_path_len: wasm32::size_t, + dir_fd: wasm32::__wasi_fd_t, + new_path_ptr: wasm32::uintptr_t, + new_path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + wasi_path_symlink(vmctx, old_path_ptr, old_path_len, + dir_fd, new_path_ptr, new_path_len) + } +} + +#[doc(hidden)] +pub fn ensure_linked() { + unsafe { + std::ptr::read_volatile(__wasi_proc_exit as *const extern "C" fn()); + } +} diff --git a/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/timers.rs b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/timers.rs new file mode 100644 index 0000000000..926a998445 --- /dev/null +++ b/third_party/rust/lucet-wasi-wasmsbx/src/hostcalls/timers.rs @@ -0,0 +1,113 @@ +use nix::libc::{self, c_int, c_char}; + +#[cfg(not(target_os = "macos"))] +mod notmac +{ + use super::*; + + pub unsafe fn clock_gettime_helper(clock_id: libc::clockid_t, tp: *mut libc::timespec) -> c_int { + libc::clock_gettime(clock_id, tp) + } + + pub unsafe fn clock_getres_helper(clock_id: libc::clockid_t, res: *mut libc::timespec) -> c_int { + libc::clock_getres(clock_id, res) + } + + pub unsafe fn futimens_helper(fd: c_int, times: *const libc::timespec) -> c_int { + libc::futimens(fd, times) + } + + pub unsafe fn utimensat_helper(dirfd: c_int, path: *const c_char, times: *const libc::timespec, flag: c_int) -> c_int { + libc::utimensat(dirfd, path, times, flag) + } +} + +#[cfg(target_os = "macos")] +mod mac +{ + use super::*; + use std::mem::MaybeUninit; + use std::sync::Once; + + use mach::mach_time::*; + + // Referring these 3 sources + //https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x + //https://stackoverflow.com/questions/11680461/monotonic-clock-on-osx + //https://gist.github.com/lifthrasiir/393ffb3e9900709fa2e3ae2a540b635f + + static mut CONVERSION_FACTOR : f64 = 0.0; + static INIT : Once = Once::new(); + + unsafe fn get_cached_conversion_factor() -> f64 { + unsafe { + INIT.call_once(|| { + let mut timebase = MaybeUninit::<mach_timebase_info_data_t>::uninit(); + mach_timebase_info(timebase.as_mut_ptr()); + let timebase = unsafe { timebase.assume_init() }; + + let numer_d : f64 = timebase.numer as f64; + let denom_d : f64 = timebase.denom as f64; + + CONVERSION_FACTOR = numer_d / denom_d; + }); + } + CONVERSION_FACTOR + } + + pub unsafe fn clock_gettime_helper(clock_id: libc::clockid_t, tp: *mut libc::timespec) -> c_int { + if !(clock_id == libc::CLOCK_REALTIME || clock_id == libc::CLOCK_MONOTONIC) { + (*libc::__error()) = libc::EINVAL; + return -1; + } + + if clock_id == libc::CLOCK_REALTIME { + let mut micro = MaybeUninit::<libc::timeval>::uninit(); + libc::gettimeofday(micro.as_mut_ptr(), core::ptr::null_mut()); + let micro = unsafe { micro.assume_init() }; + + (*tp).tv_sec = micro.tv_sec; + (*tp).tv_nsec = i64::from(micro.tv_usec) * 1000; + return 0; + } else { + let time : u64 = mach_absolute_time(); + let time_d : f64 = time as f64; + let conv : f64 = get_cached_conversion_factor(); + let nseconds : f64 = time_d * conv; + let seconds : f64 = nseconds / 1e9; + (*tp).tv_sec = seconds as i64; + (*tp).tv_nsec = nseconds as i64; + return 0; + } + } + + pub unsafe fn clock_getres_helper(clock_id: libc::clockid_t, res: *mut libc::timespec) -> c_int { + if !(clock_id == libc::CLOCK_REALTIME || clock_id == libc::CLOCK_MONOTONIC) { + (*libc::__error()) = libc::EINVAL; + return -1; + } + + (*res).tv_sec = 0 as i64; + (*res).tv_nsec = + if clock_id == libc::CLOCK_REALTIME { + 1000 as i64 + } else { + 1 as i64 + }; + return 0; + } + + pub unsafe fn futimens_helper(_fd: c_int, _times: *const libc::timespec) -> c_int { + panic!("futimens not implemented"); + } + + pub unsafe fn utimensat_helper(_dirfd: c_int, _path: *const c_char, _times: *const libc::timespec, _flag: c_int) -> c_int { + panic!("utimensat not implemented"); + } +} + +#[cfg(not(target_os = "macos"))] +pub use notmac::*; + +#[cfg(target_os = "macos")] +pub use mac::*;
\ No newline at end of file |