use crate::entry::{Descriptor, Entry}; use crate::sandboxed_tty_writer::SandboxedTTYWriter; use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; use crate::wasi::{types, AsBytes, Errno, Result}; use crate::WasiCtx; use crate::{clock, fd, path, poll}; use log::{debug, error, trace}; use std::cell::Ref; use std::convert::TryInto; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::ops::DerefMut; use wiggle_runtime::{GuestBorrows, GuestPtr}; impl<'a> WasiSnapshotPreview1 for WasiCtx { fn args_get<'b>( &self, argv: &GuestPtr<'b, GuestPtr<'b, u8>>, argv_buf: &GuestPtr<'b, u8>, ) -> Result<()> { trace!("args_get(argv_ptr={:?}, argv_buf={:?})", argv, argv_buf); let mut argv = argv.clone(); let mut argv_buf = argv_buf.clone(); for arg in &self.args { let arg_bytes = arg.as_bytes_with_nul(); let elems = arg_bytes.len().try_into()?; argv_buf.as_array(elems).copy_from_slice(arg_bytes)?; argv.write(argv_buf)?; argv = argv.add(1)?; argv_buf = argv_buf.add(elems)?; } Ok(()) } fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> { trace!("args_sizes_get"); let argc = self.args.len().try_into()?; let mut argv_size: types::Size = 0; for arg in &self.args { let arg_len = arg.as_bytes_with_nul().len().try_into()?; argv_size = argv_size.checked_add(arg_len).ok_or(Errno::Overflow)?; } trace!(" | *argc_ptr={:?}", argc); trace!(" | *argv_buf_size_ptr={:?}", argv_size); Ok((argc, argv_size)) } fn environ_get<'b>( &self, environ: &GuestPtr<'b, GuestPtr<'b, u8>>, environ_buf: &GuestPtr<'b, u8>, ) -> Result<()> { trace!( "environ_get(environ={:?}, environ_buf={:?})", environ, environ_buf ); let mut environ = environ.clone(); let mut environ_buf = environ_buf.clone(); for e in &self.env { let environ_bytes = e.as_bytes_with_nul(); let elems = environ_bytes.len().try_into()?; environ_buf.as_array(elems).copy_from_slice(environ_bytes)?; environ.write(environ_buf)?; environ = environ.add(1)?; environ_buf = environ_buf.add(elems)?; } Ok(()) } fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> { trace!("environ_sizes_get"); let environ_count = self.env.len().try_into()?; let mut environ_size: types::Size = 0; for environ in &self.env { let env_len = environ.as_bytes_with_nul().len().try_into()?; environ_size = environ_size.checked_add(env_len).ok_or(Errno::Overflow)?; } trace!(" | *environ_count_ptr={:?}", environ_count); trace!(" | *environ_size_ptr={:?}", environ_size); Ok((environ_count, environ_size)) } fn clock_res_get(&self, id: types::Clockid) -> Result { trace!("clock_res_get(id={:?})", id); let resolution = clock::res_get(id)?; trace!(" | *resolution_ptr={:?}", resolution); Ok(resolution) } fn clock_time_get( &self, id: types::Clockid, precision: types::Timestamp, ) -> Result { trace!("clock_time_get(id={:?}, precision={:?})", id, precision); let time = clock::time_get(id)?; trace!(" | *time_ptr={:?}", time); Ok(time) } fn fd_advise( &self, fd: types::Fd, offset: types::Filesize, len: types::Filesize, advice: types::Advice, ) -> Result<()> { trace!( "fd_advise(fd={:?}, offset={}, len={}, advice={})", fd, offset, len, advice ); let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut(types::Rights::FD_ADVISE, types::Rights::empty())? .as_file_mut()?; match file { Descriptor::OsHandle(fd) => fd::advise(&fd, advice, offset, len), Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len), _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } } } fn fd_allocate( &self, fd: types::Fd, offset: types::Filesize, len: types::Filesize, ) -> Result<()> { trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len); let entry = self.get_entry(fd)?; let file = entry .as_descriptor(types::Rights::FD_ALLOCATE, types::Rights::empty())? .as_file()?; match file { Descriptor::OsHandle(fd) => { let metadata = fd.metadata()?; let current_size = metadata.len(); let wanted_size = offset.checked_add(len).ok_or(Errno::TooBig)?; // This check will be unnecessary when rust-lang/rust#63326 is fixed if wanted_size > i64::max_value() as u64 { return Err(Errno::TooBig); } if wanted_size > current_size { fd.set_len(wanted_size)?; } Ok(()) } Descriptor::VirtualFile(virt) => virt.allocate(offset, len), _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } } } fn fd_close(&self, fd: types::Fd) -> Result<()> { trace!("fd_close(fd={:?})", fd); if let Ok(fe) = self.get_entry(fd) { // can't close preopened files if fe.preopen_path.is_some() { return Err(Errno::Notsup); } } self.remove_entry(fd)?; Ok(()) } fn fd_datasync(&self, fd: types::Fd) -> Result<()> { trace!("fd_datasync(fd={:?})", fd); let entry = self.get_entry(fd)?; let file = entry.as_descriptor(types::Rights::FD_DATASYNC, types::Rights::empty())?; match file { Descriptor::OsHandle(fd) => fd.sync_data()?, Descriptor::VirtualFile(virt) => virt.datasync()?, other => other.as_os_handle().sync_data()?, }; Ok(()) } fn fd_fdstat_get(&self, fd: types::Fd) -> Result { trace!("fd_fdstat_get(fd={:?})", fd); let fe = self.get_entry(fd)?; let wasi_file = fe.as_descriptor(types::Rights::empty(), types::Rights::empty())?; let fs_flags = match wasi_file { Descriptor::OsHandle(wasi_fd) => fd::fdstat_get(&wasi_fd)?, Descriptor::VirtualFile(virt) => virt.fdstat_get(), other => fd::fdstat_get(&other.as_os_handle())?, }; let fdstat = types::Fdstat { fs_filetype: fe.file_type, fs_rights_base: fe.rights_base, fs_rights_inheriting: fe.rights_inheriting, fs_flags, }; trace!(" | *buf={:?}", fdstat); Ok(fdstat) } fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> { trace!("fd_fdstat_set_flags(fd={:?}, fdflags={})", fd, flags); let mut entry = self.get_entry_mut(fd)?; let descriptor = entry.as_descriptor_mut(types::Rights::FD_FDSTAT_SET_FLAGS, types::Rights::empty())?; match descriptor { Descriptor::OsHandle(handle) => { let set_result = fd::fdstat_set_flags(&handle, flags)?.map(Descriptor::OsHandle); if let Some(new_descriptor) = set_result { *descriptor = new_descriptor; } } Descriptor::VirtualFile(handle) => { handle.fdstat_set_flags(flags)?; } _ => { let set_result = fd::fdstat_set_flags(&descriptor.as_os_handle(), flags)? .map(Descriptor::OsHandle); if let Some(new_descriptor) = set_result { *descriptor = new_descriptor; } } }; Ok(()) } fn fd_fdstat_set_rights( &self, fd: types::Fd, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, ) -> Result<()> { trace!( "fd_fdstat_set_rights(fd={:?}, fs_rights_base={}, fs_rights_inheriting={})", fd, fs_rights_base, fs_rights_inheriting ); let mut entry = self.get_entry_mut(fd)?; if entry.rights_base & fs_rights_base != fs_rights_base || entry.rights_inheriting & fs_rights_inheriting != fs_rights_inheriting { return Err(Errno::Notcapable); } entry.rights_base = fs_rights_base; entry.rights_inheriting = fs_rights_inheriting; Ok(()) } fn fd_filestat_get(&self, fd: types::Fd) -> Result { trace!("fd_filestat_get(fd={:?})", fd); let entry = self.get_entry(fd)?; let fd = entry .as_descriptor(types::Rights::FD_FILESTAT_GET, types::Rights::empty())? .as_file()?; let host_filestat = match fd { Descriptor::OsHandle(fd) => fd::filestat_get(&fd)?, Descriptor::VirtualFile(virt) => virt.filestat_get()?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; trace!(" | *filestat_ptr={:?}", host_filestat); Ok(host_filestat) } fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> { trace!("fd_filestat_set_size(fd={:?}, size={})", fd, size); let entry = self.get_entry(fd)?; let file = entry .as_descriptor(types::Rights::FD_FILESTAT_SET_SIZE, types::Rights::empty())? .as_file()?; // This check will be unnecessary when rust-lang/rust#63326 is fixed if size > i64::max_value() as u64 { return Err(Errno::TooBig); } match file { Descriptor::OsHandle(fd) => fd.set_len(size)?, Descriptor::VirtualFile(virt) => virt.filestat_set_size(size)?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; Ok(()) } fn fd_filestat_set_times( &self, fd: types::Fd, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<()> { trace!( "fd_filestat_set_times(fd={:?}, atim={}, mtim={}, fst_flags={})", fd, atim, mtim, fst_flags ); let entry = self.get_entry(fd)?; let fd = entry .as_descriptor(types::Rights::FD_FILESTAT_SET_TIMES, types::Rights::empty())? .as_file()?; fd::filestat_set_times_impl(&fd, atim, mtim, fst_flags) } fn fd_pread( &self, fd: types::Fd, iovs: &types::IovecArray<'_>, offset: types::Filesize, ) -> Result { trace!("fd_pread(fd={:?}, iovs={:?}, offset={})", fd, iovs, offset,); let mut buf = Vec::new(); let mut bc = GuestBorrows::new(); bc.borrow_slice(iovs)?; for iov_ptr in iovs.iter() { let iov_ptr = iov_ptr?; let iov: types::Iovec = iov_ptr.read()?; let slice = unsafe { let buf = iov.buf.as_array(iov.buf_len); let raw = buf.as_raw(&mut bc)?; &mut *raw }; buf.push(io::IoSliceMut::new(slice)); } let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut( types::Rights::FD_READ | types::Rights::FD_SEEK, types::Rights::empty(), )? .as_file_mut()?; if offset > i64::max_value() as u64 { return Err(Errno::Io); } let host_nread = match file { Descriptor::OsHandle(fd) => { let cur_pos = fd.seek(SeekFrom::Current(0))?; fd.seek(SeekFrom::Start(offset))?; let nread = fd.read_vectored(&mut buf)?; fd.seek(SeekFrom::Start(cur_pos))?; nread } Descriptor::VirtualFile(virt) => virt.preadv(&mut buf, offset)?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; let host_nread = host_nread.try_into()?; trace!(" | *nread={:?}", host_nread); Ok(host_nread) } fn fd_prestat_get(&self, fd: types::Fd) -> Result { trace!("fd_prestat_get(fd={:?})", fd); // TODO: should we validate any rights here? let fe = self.get_entry(fd)?; let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; if fe.file_type != types::Filetype::Directory { return Err(Errno::Notdir); } let path = path::from_host(po_path.as_os_str())?; let prestat = types::PrestatDir { pr_name_len: path.len().try_into()?, }; Ok(types::Prestat::Dir(prestat)) } fn fd_prestat_dir_name( &self, fd: types::Fd, path: &GuestPtr, path_len: types::Size, ) -> Result<()> { trace!( "fd_prestat_dir_name(fd={:?}, path={:?}, path_len={})", fd, path, path_len ); // TODO: should we validate any rights here? let fe = self.get_entry(fd)?; let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; if fe.file_type != types::Filetype::Directory { return Err(Errno::Notdir); } let host_path = path::from_host(po_path.as_os_str())?; let host_path_len = host_path.len().try_into()?; if host_path_len > path_len { return Err(Errno::Nametoolong); } trace!(" | (path_ptr,path_len)='{}'", host_path); path.as_array(host_path_len) .copy_from_slice(host_path.as_bytes())?; Ok(()) } fn fd_pwrite( &self, fd: types::Fd, ciovs: &types::CiovecArray<'_>, offset: types::Filesize, ) -> Result { trace!( "fd_pwrite(fd={:?}, ciovs={:?}, offset={})", fd, ciovs, offset, ); let mut buf = Vec::new(); let mut bc = GuestBorrows::new(); bc.borrow_slice(ciovs)?; for ciov_ptr in ciovs.iter() { let ciov_ptr = ciov_ptr?; let ciov: types::Ciovec = ciov_ptr.read()?; let slice = unsafe { let buf = ciov.buf.as_array(ciov.buf_len); let raw = buf.as_raw(&mut bc)?; &*raw }; buf.push(io::IoSlice::new(slice)); } let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut( types::Rights::FD_WRITE | types::Rights::FD_SEEK, types::Rights::empty(), )? .as_file_mut()?; if offset > i64::max_value() as u64 { return Err(Errno::Io); } let host_nwritten = match file { Descriptor::OsHandle(fd) => { let cur_pos = fd.seek(SeekFrom::Current(0))?; fd.seek(SeekFrom::Start(offset))?; let nwritten = fd.write_vectored(&buf)?; fd.seek(SeekFrom::Start(cur_pos))?; nwritten } Descriptor::VirtualFile(virt) => virt.pwritev(&buf, offset)?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; trace!(" | *nwritten={:?}", host_nwritten); let host_nwritten = host_nwritten.try_into()?; Ok(host_nwritten) } fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result { trace!("fd_read(fd={:?}, iovs={:?})", fd, iovs); let mut bc = GuestBorrows::new(); let mut slices = Vec::new(); bc.borrow_slice(&iovs)?; for iov_ptr in iovs.iter() { let iov_ptr = iov_ptr?; let iov: types::Iovec = iov_ptr.read()?; let slice = unsafe { let buf = iov.buf.as_array(iov.buf_len); let raw = buf.as_raw(&mut bc)?; &mut *raw }; slices.push(io::IoSliceMut::new(slice)); } let mut entry = self.get_entry_mut(fd)?; let host_nread = match entry.as_descriptor_mut(types::Rights::FD_READ, types::Rights::empty())? { Descriptor::OsHandle(file) => file.read_vectored(&mut slices)?, Descriptor::VirtualFile(virt) => virt.read_vectored(&mut slices)?, Descriptor::Stdin => io::stdin().read_vectored(&mut slices)?, _ => return Err(Errno::Badf), }; let host_nread = host_nread.try_into()?; trace!(" | *nread={:?}", host_nread); Ok(host_nread) } fn fd_readdir( &self, fd: types::Fd, buf: &GuestPtr, buf_len: types::Size, cookie: types::Dircookie, ) -> Result { trace!( "fd_readdir(fd={:?}, buf={:?}, buf_len={}, cookie={:?})", fd, buf, buf_len, cookie, ); let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut(types::Rights::FD_READDIR, types::Rights::empty())? .as_file_mut()?; fn copy_entities>>( iter: T, buf: &GuestPtr, buf_len: types::Size, ) -> Result { let mut bufused = 0; let mut buf = buf.clone(); for pair in iter { let (dirent, name) = pair?; let dirent_raw = dirent.as_bytes()?; let dirent_len: types::Size = dirent_raw.len().try_into()?; let name_raw = name.as_bytes(); let name_len = name_raw.len().try_into()?; let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?; if (buf_len - bufused) < offset { break; } else { buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?; buf = buf.add(dirent_len)?; buf.as_array(name_len).copy_from_slice(name_raw)?; buf = buf.add(name_len)?; bufused += offset; } } Ok(bufused) } let bufused = match file { Descriptor::OsHandle(file) => copy_entities(fd::readdir(file, cookie)?, buf, buf_len)?, Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, buf, buf_len)?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; trace!(" | *buf_used={:?}", bufused); Ok(bufused) } fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> { trace!("fd_renumber(from={:?}, to={:?})", from, to); if !self.contains_entry(from) { return Err(Errno::Badf); } // Don't allow renumbering over a pre-opened resource. // TODO: Eventually, we do want to permit this, once libpreopen in // userspace is capable of removing entries from its tables as well. if let Ok(from_fe) = self.get_entry(from) { if from_fe.preopen_path.is_some() { return Err(Errno::Notsup); } } if let Ok(to_fe) = self.get_entry(to) { if to_fe.preopen_path.is_some() { return Err(Errno::Notsup); } } let fe = self.remove_entry(from)?; self.insert_entry_at(to, fe); Ok(()) } fn fd_seek( &self, fd: types::Fd, offset: types::Filedelta, whence: types::Whence, ) -> Result { trace!( "fd_seek(fd={:?}, offset={:?}, whence={:?})", fd, offset, whence, ); let rights = if offset == 0 && whence == types::Whence::Cur { types::Rights::FD_TELL } else { types::Rights::FD_SEEK | types::Rights::FD_TELL }; let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut(rights, types::Rights::empty())? .as_file_mut()?; let pos = match whence { types::Whence::Cur => SeekFrom::Current(offset), types::Whence::End => SeekFrom::End(offset), types::Whence::Set => SeekFrom::Start(offset as u64), }; let host_newoffset = match file { Descriptor::OsHandle(fd) => fd.seek(pos)?, Descriptor::VirtualFile(virt) => virt.seek(pos)?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; trace!(" | *newoffset={:?}", host_newoffset); Ok(host_newoffset) } fn fd_sync(&self, fd: types::Fd) -> Result<()> { trace!("fd_sync(fd={:?})", fd); let entry = self.get_entry(fd)?; let file = entry .as_descriptor(types::Rights::FD_SYNC, types::Rights::empty())? .as_file()?; match file { Descriptor::OsHandle(fd) => fd.sync_all()?, Descriptor::VirtualFile(virt) => virt.sync()?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; Ok(()) } fn fd_tell(&self, fd: types::Fd) -> Result { trace!("fd_tell(fd={:?})", fd); let mut entry = self.get_entry_mut(fd)?; let file = entry .as_descriptor_mut(types::Rights::FD_TELL, types::Rights::empty())? .as_file_mut()?; let host_offset = match file { Descriptor::OsHandle(fd) => fd.seek(SeekFrom::Current(0))?, Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?, _ => { unreachable!( "implementation error: fd should have been checked to not be a stream already" ); } }; trace!(" | *newoffset={:?}", host_offset); Ok(host_offset) } fn fd_write(&self, fd: types::Fd, ciovs: &types::CiovecArray<'_>) -> Result { trace!("fd_write(fd={:?}, ciovs={:#x?})", fd, ciovs); let mut bc = GuestBorrows::new(); let mut slices = Vec::new(); bc.borrow_slice(&ciovs)?; for ciov_ptr in ciovs.iter() { let ciov_ptr = ciov_ptr?; let ciov: types::Ciovec = ciov_ptr.read()?; let slice = unsafe { let buf = ciov.buf.as_array(ciov.buf_len); let raw = buf.as_raw(&mut bc)?; &*raw }; slices.push(io::IoSlice::new(slice)); } // perform unbuffered writes let mut entry = self.get_entry_mut(fd)?; let isatty = entry.isatty(); let desc = entry.as_descriptor_mut(types::Rights::FD_WRITE, types::Rights::empty())?; let host_nwritten = match desc { Descriptor::OsHandle(file) => { if isatty { SandboxedTTYWriter::new(file.deref_mut()).write_vectored(&slices)? } else { file.write_vectored(&slices)? } } Descriptor::VirtualFile(virt) => { if isatty { unimplemented!("writes to virtual tty"); } else { virt.write_vectored(&slices)? } } Descriptor::Stdin => return Err(Errno::Badf), Descriptor::Stdout => { // lock for the duration of the scope let stdout = io::stdout(); let mut stdout = stdout.lock(); let nwritten = if isatty { SandboxedTTYWriter::new(&mut stdout).write_vectored(&slices)? } else { stdout.write_vectored(&slices)? }; stdout.flush()?; nwritten } // Always sanitize stderr, even if it's not directly connected to a tty, // because stderr is meant for diagnostics rather than binary output, // and may be redirected to a file which could end up being displayed // on a tty later. Descriptor::Stderr => { SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&slices)? } }; trace!(" | *nwritten={:?}", host_nwritten); Ok(host_nwritten.try_into()?) } fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { trace!("path_create_directory(dirfd={:?}, path={:?})", dirfd, path); let rights = types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY; let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, rights, types::Rights::empty(), types::Lookupflags::empty(), path, false, )?; resolved.create_directory() } fn path_filestat_get( &self, dirfd: types::Fd, flags: types::Lookupflags, path: &GuestPtr<'_, str>, ) -> Result { trace!( "path_filestat_get(dirfd={:?}, flags={:?}, path={:?})", dirfd, flags, path, ); let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, types::Rights::PATH_FILESTAT_GET, types::Rights::empty(), flags, path, false, )?; let host_filestat = match resolved.dirfd() { Descriptor::VirtualFile(virt) => virt .openat( std::path::Path::new(resolved.path()), false, false, types::Oflags::empty(), types::Fdflags::empty(), )? .filestat_get()?, _ => path::filestat_get(resolved, flags)?, }; trace!(" | *filestat_ptr={:?}", host_filestat); Ok(host_filestat) } fn path_filestat_set_times( &self, dirfd: types::Fd, flags: types::Lookupflags, path: &GuestPtr<'_, str>, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<()> { trace!( "path_filestat_set_times(dirfd={:?}, flags={:?}, path={:?}, atim={}, mtim={}, fst_flags={})", dirfd, flags, path, atim, mtim, fst_flags, ); let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, types::Rights::PATH_FILESTAT_SET_TIMES, types::Rights::empty(), flags, path, false, )?; match resolved.dirfd() { Descriptor::VirtualFile(_virt) => { unimplemented!("virtual filestat_set_times"); } _ => path::filestat_set_times(resolved, flags, atim, mtim, fst_flags), } } fn path_link( &self, old_fd: types::Fd, old_flags: types::Lookupflags, old_path: &GuestPtr<'_, str>, new_fd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { trace!( "path_link(old_fd={:?}, old_flags={:?}, old_path={:?}, new_fd={:?}, new_path={:?})", old_fd, old_flags, old_path, new_fd, new_path, ); let old_entry = self.get_entry(old_fd)?; let resolved_old = path::get( &old_entry, types::Rights::PATH_LINK_SOURCE, types::Rights::empty(), types::Lookupflags::empty(), old_path, false, )?; let new_entry = self.get_entry(new_fd)?; let resolved_new = path::get( &new_entry, types::Rights::PATH_LINK_TARGET, types::Rights::empty(), types::Lookupflags::empty(), new_path, false, )?; path::link( resolved_old, resolved_new, old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW), ) } fn path_open( &self, dirfd: types::Fd, dirflags: types::Lookupflags, path: &GuestPtr<'_, str>, oflags: types::Oflags, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, fdflags: types::Fdflags, ) -> Result { trace!( "path_open(dirfd={:?}, dirflags={}, path={:?}, oflags={}, fs_rights_base={}, fs_rights_inheriting={}, fdflags={}", dirfd, dirflags, path, oflags, fs_rights_base, fs_rights_inheriting, fdflags, ); let (needed_base, needed_inheriting) = path::open_rights(fs_rights_base, fs_rights_inheriting, oflags, fdflags); trace!( " | needed_base = {}, needed_inheriting = {}", needed_base, needed_inheriting ); let resolved = { let entry = self.get_entry(dirfd)?; path::get( &entry, needed_base, needed_inheriting, dirflags, path, oflags & types::Oflags::CREAT != types::Oflags::empty(), )? }; // which open mode do we need? let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR) != types::Rights::empty(); let write = fs_rights_base & (types::Rights::FD_DATASYNC | types::Rights::FD_WRITE | types::Rights::FD_ALLOCATE | types::Rights::FD_FILESTAT_SET_SIZE) != types::Rights::empty(); trace!( " | calling path_open impl: read={}, write={}", read, write ); let fd = resolved.open_with(read, write, oflags, fdflags)?; let mut fe = Entry::from(fd)?; // We need to manually deny the rights which are not explicitly requested // because Entry::from will assign maximal consistent rights. fe.rights_base &= fs_rights_base; fe.rights_inheriting &= fs_rights_inheriting; let guest_fd = self.insert_entry(fe)?; trace!(" | *fd={:?}", guest_fd); Ok(guest_fd) } fn path_readlink( &self, dirfd: types::Fd, path: &GuestPtr<'_, str>, buf: &GuestPtr, buf_len: types::Size, ) -> Result { trace!( "path_readlink(dirfd={:?}, path={:?}, buf={:?}, buf_len={})", dirfd, path, buf, buf_len, ); let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, types::Rights::PATH_READLINK, types::Rights::empty(), types::Lookupflags::empty(), path, false, )?; let slice = unsafe { let mut bc = GuestBorrows::new(); let buf = buf.as_array(buf_len); let raw = buf.as_raw(&mut bc)?; &mut *raw }; let host_bufused = match resolved.dirfd() { Descriptor::VirtualFile(_virt) => { unimplemented!("virtual readlink"); } _ => path::readlink(resolved, slice)?, }; let host_bufused = host_bufused.try_into()?; trace!(" | (buf_ptr,*buf_used)={:?}", slice); trace!(" | *buf_used={:?}", host_bufused); Ok(host_bufused) } fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { trace!("path_remove_directory(dirfd={:?}, path={:?})", dirfd, path); let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, types::Rights::PATH_REMOVE_DIRECTORY, types::Rights::empty(), types::Lookupflags::empty(), path, true, )?; debug!("path_remove_directory resolved={:?}", resolved); match resolved.dirfd() { Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()), _ => path::remove_directory(resolved), } } fn path_rename( &self, old_fd: types::Fd, old_path: &GuestPtr<'_, str>, new_fd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { trace!( "path_rename(old_fd={:?}, old_path={:?}, new_fd={:?}, new_path={:?})", old_fd, old_path, new_fd, new_path, ); let entry = self.get_entry(old_fd)?; let resolved_old = path::get( &entry, types::Rights::PATH_RENAME_SOURCE, types::Rights::empty(), types::Lookupflags::empty(), old_path, true, )?; let entry = self.get_entry(new_fd)?; let resolved_new = path::get( &entry, types::Rights::PATH_RENAME_TARGET, types::Rights::empty(), types::Lookupflags::empty(), new_path, true, )?; log::debug!("path_rename resolved_old={:?}", resolved_old); log::debug!("path_rename resolved_new={:?}", resolved_new); if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) = (resolved_old.dirfd(), resolved_new.dirfd()) { path::rename(resolved_old, resolved_new) } else { // Virtual files do not support rename, at the moment, and streams don't have paths to // rename, so any combination of Descriptor that gets here is an error in the making. panic!("path_rename with one or more non-OS files"); } } fn path_symlink( &self, old_path: &GuestPtr<'_, str>, dirfd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { trace!( "path_symlink(old_path={:?}, dirfd={:?}, new_path={:?})", old_path, dirfd, new_path, ); let entry = self.get_entry(dirfd)?; let resolved_new = path::get( &entry, types::Rights::PATH_SYMLINK, types::Rights::empty(), types::Lookupflags::empty(), new_path, true, )?; let old_path = unsafe { let mut bc = GuestBorrows::new(); let raw = old_path.as_raw(&mut bc)?; &*raw }; trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); match resolved_new.dirfd() { Descriptor::VirtualFile(_virt) => { unimplemented!("virtual path_symlink"); } _non_virtual => path::symlink(old_path, resolved_new), } } fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { trace!("path_unlink_file(dirfd={:?}, path={:?})", dirfd, path); let entry = self.get_entry(dirfd)?; let resolved = path::get( &entry, types::Rights::PATH_UNLINK_FILE, types::Rights::empty(), types::Lookupflags::empty(), path, false, )?; match resolved.dirfd() { Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()), _ => path::unlink_file(resolved), } } fn poll_oneoff( &self, in_: &GuestPtr, out: &GuestPtr, nsubscriptions: types::Size, ) -> Result { trace!( "poll_oneoff(in_={:?}, out={:?}, nsubscriptions={})", in_, out, nsubscriptions, ); if u64::from(nsubscriptions) > types::Filesize::max_value() { return Err(Errno::Inval); } let mut subscriptions = Vec::new(); let mut bc = GuestBorrows::new(); let subs = in_.as_array(nsubscriptions); bc.borrow_slice(&subs)?; for sub_ptr in subs.iter() { let sub_ptr = sub_ptr?; let sub: types::Subscription = sub_ptr.read()?; subscriptions.push(sub); } let mut events = Vec::new(); let mut timeout: Option = None; let mut fd_events = Vec::new(); // As mandated by the WASI spec: // > If `nsubscriptions` is 0, returns `errno::inval`. if subscriptions.is_empty() { return Err(Errno::Inval); } for subscription in subscriptions { match subscription.u { types::SubscriptionU::Clock(clock) => { let delay = clock::to_relative_ns_delay(clock)?; log::debug!("poll_oneoff event.u.clock = {:?}", clock); log::debug!("poll_oneoff delay = {:?}ns", delay); let current = poll::ClockEventData { delay, userdata: subscription.userdata, }; let timeout = timeout.get_or_insert(current); if current.delay < timeout.delay { *timeout = current; } } types::SubscriptionU::FdRead(fd_read) => { let fd = fd_read.file_descriptor; let rights = types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE; let entry = match self.get_entry(fd) { Ok(entry) => entry, Err(error) => { events.push(types::Event { userdata: subscription.userdata, error, type_: types::Eventtype::FdRead, fd_readwrite: types::EventFdReadwrite { nbytes: 0, flags: types::Eventrwflags::empty(), }, }); continue; } }; // TODO Can this be simplified? // Validate rights on the entry before converting into host descriptor. entry.validate_rights(rights, types::Rights::empty())?; let descriptor = Ref::map(entry, |entry| { entry.as_descriptor(rights, types::Rights::empty()).unwrap() }); fd_events.push(poll::FdEventData { descriptor, r#type: types::Eventtype::FdRead, userdata: subscription.userdata, }); } types::SubscriptionU::FdWrite(fd_write) => { let fd = fd_write.file_descriptor; let rights = types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE; let entry = match self.get_entry(fd) { Ok(entry) => entry, Err(error) => { events.push(types::Event { userdata: subscription.userdata, error, type_: types::Eventtype::FdWrite, fd_readwrite: types::EventFdReadwrite { nbytes: 0, flags: types::Eventrwflags::empty(), }, }); continue; } }; // TODO Can this be simplified? // Validate rights on the entry before converting into host descriptor. entry.validate_rights(rights, types::Rights::empty())?; let descriptor = Ref::map(entry, |entry| { entry.as_descriptor(rights, types::Rights::empty()).unwrap() }); fd_events.push(poll::FdEventData { descriptor, r#type: types::Eventtype::FdWrite, userdata: subscription.userdata, }); } } } log::debug!("poll_oneoff events = {:?}", events); log::debug!("poll_oneoff timeout = {:?}", timeout); log::debug!("poll_oneoff fd_events = {:?}", fd_events); // The underlying implementation should successfully and immediately return // if no events have been passed. Such situation may occur if all provided // events have been filtered out as errors in the code above. poll::oneoff(timeout, fd_events, &mut events)?; let nevents = events.len().try_into()?; let out_events = out.as_array(nevents); bc.borrow_slice(&out_events)?; for (event, event_ptr) in events.into_iter().zip(out_events.iter()) { let event_ptr = event_ptr?; event_ptr.write(event)?; } trace!(" | *nevents={:?}", nevents); Ok(nevents) } // This is just a temporary to ignore the warning which becomes a hard error // in the CI. Once we figure out non-returns in `wiggle`, this should be gone. #[allow(unreachable_code)] fn proc_exit(&self, rval: types::Exitcode) -> std::result::Result<(), ()> { trace!("proc_exit(rval={:?})", rval); // TODO: Rather than call std::process::exit here, we should trigger a // stack unwind similar to a trap. std::process::exit(rval as i32); Ok(()) } fn proc_raise(&self, _sig: types::Signal) -> Result<()> { unimplemented!("proc_raise") } fn sched_yield(&self) -> Result<()> { trace!("sched_yield()"); std::thread::yield_now(); Ok(()) } fn random_get(&self, buf: &GuestPtr, buf_len: types::Size) -> Result<()> { trace!("random_get(buf={:?}, buf_len={:?})", buf, buf_len); let slice = unsafe { let mut bc = GuestBorrows::new(); let buf = buf.as_array(buf_len); let raw = buf.as_raw(&mut bc)?; &mut *raw }; getrandom::getrandom(slice).map_err(|err| { error!("getrandom failure: {:?}", err); Errno::Io }) } fn sock_recv( &self, _fd: types::Fd, _ri_data: &types::IovecArray<'_>, _ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags)> { unimplemented!("sock_recv") } fn sock_send( &self, _fd: types::Fd, _si_data: &types::CiovecArray<'_>, _si_flags: types::Siflags, ) -> Result { unimplemented!("sock_send") } fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> { unimplemented!("sock_shutdown") } }