From 89fbde2c3fb42661cd6705dadd3e72b30aa76748 Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Fri, 26 Jul 2019 19:15:29 +0200 Subject: [PATCH] Implement fd_filestat_get for all platforms (#42) * Implement fd_filestat_get for all platforms * Remove an old comment * Remove panics from the syscall wrappers * Return WASI error type * Reuse Metadata if possible to save syscalls. * Refactor the change for two separate fd_filestat_get_impl * Refactor error handling --- src/helpers.rs | 10 +++++ src/hostcalls_impl/fs.rs | 2 +- src/lib.rs | 1 + src/sys/mod.rs | 14 +++++- src/sys/unix/hostcalls_impl/fs.rs | 59 +++++++++++++++++++++--- src/sys/windows/hostcalls_impl/fs.rs | 67 ++++++++++++++++++++++++++-- winx/src/file.rs | 53 ++++++++++++++++++++-- 7 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 src/helpers.rs diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 000000000000..08755b5c15f8 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,10 @@ +use crate::{host, Result}; +use std::convert::TryInto; +use std::time::{SystemTime, UNIX_EPOCH}; +pub(crate) fn systemtime_to_timestamp(st: SystemTime) -> Result { + st.duration_since(UNIX_EPOCH) + .map_err(|_| host::__WASI_EINVAL)? // date earlier than UNIX_EPOCH + .as_nanos() + .try_into() + .map_err(|_| host::__WASI_EOVERFLOW) // u128 doesn't fit into u64 +} diff --git a/src/hostcalls_impl/fs.rs b/src/hostcalls_impl/fs.rs index 3f5a9b153645..e05ceec55feb 100644 --- a/src/hostcalls_impl/fs.rs +++ b/src/hostcalls_impl/fs.rs @@ -730,7 +730,7 @@ pub(crate) fn fd_filestat_get( .get_fd_entry(fd, 0, 0) .and_then(|fe| fe.fd_object.descriptor.as_file())?; - let host_filestat = hostcalls_impl::fd_filestat_get(fd)?; + let host_filestat = hostcalls_impl::fd_filestat_get_impl(fd)?; trace!(" | *filestat_ptr={:?}", host_filestat); diff --git a/src/lib.rs b/src/lib.rs index 690358c87ce4..23f3760465d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ mod ctx; mod fdentry; +mod helpers; mod hostcalls_impl; mod sys; #[macro_use] diff --git a/src/sys/mod.rs b/src/sys/mod.rs index dcad0ea7fc7e..8a60ea664f57 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -6,17 +6,27 @@ cfg_if! { mod unix; pub use self::unix::*; - pub fn errno_from_host(err: i32) -> host::__wasi_errno_t { + pub(crate) fn errno_from_host(err: i32) -> host::__wasi_errno_t { host_impl::errno_from_nix(nix::errno::from_i32(err)) } } else if #[cfg(windows)] { mod windows; pub use self::windows::*; - pub fn errno_from_host(err: i32) -> host::__wasi_errno_t { + pub(crate) fn errno_from_host(err: i32) -> host::__wasi_errno_t { host_impl::errno_from_win(winx::winerror::WinError::from_u32(err as u32)) } } else { compile_error!("wasi-common doesn't compile for this platform yet"); } } + +pub(crate) fn errno_from_ioerror(e: std::io::Error) -> host::__wasi_errno_t { + match e.raw_os_error() { + Some(code) => errno_from_host(code), + None => { + log::debug!("Inconvertible OS error: {}", e); + host::__WASI_EIO + } + } +} diff --git a/src/sys/unix/hostcalls_impl/fs.rs b/src/sys/unix/hostcalls_impl/fs.rs index 5842ceeafd71..13cdbed3b505 100644 --- a/src/sys/unix/hostcalls_impl/fs.rs +++ b/src/sys/unix/hostcalls_impl/fs.rs @@ -3,13 +3,15 @@ use super::fs_helpers::*; use crate::ctx::WasiCtx; use crate::fdentry::FdEntry; -use crate::sys::errno_from_host; +use crate::helpers::systemtime_to_timestamp; use crate::sys::fdentry_impl::determine_type_rights; use crate::sys::host_impl; +use crate::sys::{errno_from_host, errno_from_ioerror}; use crate::{host, wasm32, Result}; use nix::libc::{self, c_long, c_void, off_t}; +use std::convert::TryInto; use std::ffi::CString; -use std::fs::File; +use std::fs::{File, Metadata}; use std::os::unix::fs::FileExt; use std::os::unix::prelude::{AsRawFd, FromRawFd}; @@ -356,11 +358,54 @@ pub(crate) fn path_rename( } } -pub(crate) fn fd_filestat_get(fd: &File) -> Result { - use nix::sys::stat::fstat; - let filestat = - fstat(fd.as_raw_fd()).map_err(|err| host_impl::errno_from_nix(err.as_errno().unwrap()))?; - host_impl::filestat_from_nix(filestat) +pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result { + use std::os::unix::fs::MetadataExt; + + let metadata = file.metadata().map_err(errno_from_ioerror)?; + Ok(host::__wasi_filestat_t { + st_dev: metadata.dev(), + st_ino: metadata.ino(), + st_nlink: metadata + .nlink() + .try_into() + .map_err(|_| host::__WASI_EOVERFLOW)?, // u64 doesn't fit into u32 + st_size: metadata.len(), + st_atim: metadata + .accessed() + .map_err(errno_from_ioerror) + .and_then(systemtime_to_timestamp)?, + st_ctim: metadata + .ctime() + .try_into() + .map_err(|_| host::__WASI_EOVERFLOW)?, // i64 doesn't fit into u64 + st_mtim: metadata + .modified() + .map_err(errno_from_ioerror) + .and_then(systemtime_to_timestamp)?, + st_filetype: filetype(&metadata), + }) +} + +fn filetype(metadata: &Metadata) -> host::__wasi_filetype_t { + use std::os::unix::fs::FileTypeExt; + let ftype = metadata.file_type(); + if ftype.is_file() { + host::__WASI_FILETYPE_REGULAR_FILE + } else if ftype.is_dir() { + host::__WASI_FILETYPE_DIRECTORY + } else if ftype.is_symlink() { + host::__WASI_FILETYPE_SYMBOLIC_LINK + } else if ftype.is_char_device() { + host::__WASI_FILETYPE_CHARACTER_DEVICE + } else if ftype.is_block_device() { + host::__WASI_FILETYPE_BLOCK_DEVICE + } else if ftype.is_socket() || ftype.is_fifo() { + // TODO we should use getsockopt to find out if it's + // SOCKET_STREAM or SOCKET_DGRAM + host::__WASI_FILETYPE_SOCKET_STREAM + } else { + host::__WASI_FILETYPE_UNKNOWN + } } pub(crate) fn fd_filestat_set_times( diff --git a/src/sys/windows/hostcalls_impl/fs.rs b/src/sys/windows/hostcalls_impl/fs.rs index 942086bf8bc8..e1b4736e3b9c 100644 --- a/src/sys/windows/hostcalls_impl/fs.rs +++ b/src/sys/windows/hostcalls_impl/fs.rs @@ -3,11 +3,13 @@ use super::fs_helpers::*; use crate::ctx::WasiCtx; use crate::fdentry::FdEntry; -use crate::sys::errno_from_host; +use crate::helpers::systemtime_to_timestamp; +use crate::sys::{errno_from_ioerror, errno_from_host}; use crate::sys::fdentry_impl::determine_type_rights; use crate::sys::host_impl; use crate::{host, Result}; -use std::fs::File; +use std::convert::TryInto; +use std::fs::{File, Metadata}; use std::io::{self, Seek, SeekFrom}; use std::os::windows::fs::FileExt; use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; @@ -173,8 +175,65 @@ pub(crate) fn path_rename( unimplemented!("path_rename") } -pub(crate) fn fd_filestat_get(fd: &File) -> Result { - unimplemented!("fd_filestat_get") +pub(crate) fn num_hardlinks(file: &File, _metadata: &Metadata) -> io::Result { + Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into()) +} + +pub(crate) fn device_id(file: &File, _metadata: &Metadata) -> io::Result { + Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into()) +} + +pub(crate) fn file_serial_no(file: &File, _metadata: &Metadata) -> io::Result { + let info = winx::file::get_fileinfo(file)?; + let high = info.nFileIndexHigh; + let low = info.nFileIndexLow; + let no = ((high as u64) << 32) | (low as u64); + Ok(no) +} + +pub(crate) fn change_time(file: &File, _metadata: &Metadata) -> io::Result { + winx::file::change_time(file) +} + +pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result { + let metadata = file.metadata().map_err(errno_from_ioerror)?; + Ok(host::__wasi_filestat_t { + st_dev: device_id(file, &metadata).map_err(errno_from_ioerror)?, + st_ino: file_serial_no(file, &metadata).map_err(errno_from_ioerror)?, + st_nlink: num_hardlinks(file, &metadata) + .map_err(errno_from_ioerror)? + .try_into() + .map_err(|_| host::__WASI_EOVERFLOW)?, // u64 doesn't fit into u32 + st_size: metadata.len(), + st_atim: metadata + .accessed() + .map_err(errno_from_ioerror) + .and_then(systemtime_to_timestamp)?, + st_ctim: change_time(file, &metadata) + .map_err(errno_from_ioerror)? + .try_into() + .map_err(|_| host::__WASI_EOVERFLOW)?, // i64 doesn't fit into u64 + st_mtim: metadata + .modified() + .map_err(errno_from_ioerror) + .and_then(systemtime_to_timestamp)?, + st_filetype: filetype(&metadata).map_err(errno_from_ioerror)?, + }) +} + +fn filetype(metadata: &Metadata) -> io::Result { + let ftype = metadata.file_type(); + let ret = if ftype.is_file() { + host::__WASI_FILETYPE_REGULAR_FILE + } else if ftype.is_dir() { + host::__WASI_FILETYPE_DIRECTORY + } else if ftype.is_symlink() { + host::__WASI_FILETYPE_SYMBOLIC_LINK + } else { + host::__WASI_FILETYPE_UNKNOWN + }; + + Ok(ret) } pub(crate) fn fd_filestat_set_times( diff --git a/winx/src/file.rs b/winx/src/file.rs index 30339763d835..de7f7ed00e42 100644 --- a/winx/src/file.rs +++ b/winx/src/file.rs @@ -1,9 +1,11 @@ #![allow(non_camel_case_types)] use crate::{winerror, Result}; -use std::ffi::{OsStr, OsString}; -use std::os::windows::prelude::{OsStrExt, OsStringExt, RawHandle}; +use std::ffi::{c_void, OsStr, OsString}; +use std::fs::File; +use std::io; +use std::os::windows::prelude::{AsRawHandle, OsStrExt, OsStringExt, RawHandle}; use winapi::shared::minwindef::{self, DWORD}; -use winapi::um::{fileapi, fileapi::GetFileType, winbase, winnt}; +use winapi::um::{fileapi, fileapi::GetFileType, minwinbase, winbase, winnt}; /// Maximum total path length for Unicode in Windows. /// [Maximum path length limitation]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation @@ -457,3 +459,48 @@ pub fn openat>( Ok(handle) } } + +// Taken from Rust libstd, file libstd/sys/windows/fs.rs +fn cvt(i: winapi::shared::minwindef::BOOL) -> io::Result<()> { + if i == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn get_fileinfo(file: &File) -> io::Result { + use fileapi::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION}; + use std::mem; + + let handle = file.as_raw_handle(); + let info = unsafe { + let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); + cvt(GetFileInformationByHandle(handle, &mut info))?; + info + }; + + Ok(info) +} + +pub fn change_time(file: &File) -> io::Result { + use fileapi::FILE_BASIC_INFO; + use minwinbase::FileBasicInfo; + use std::mem; + use winbase::GetFileInformationByHandleEx; + + let handle = file.as_raw_handle(); + let tm = unsafe { + let mut info: FILE_BASIC_INFO = mem::zeroed(); + let infosize = mem::size_of_val(&info); + cvt(GetFileInformationByHandleEx( + handle, + FileBasicInfo, + &mut info as *mut FILE_BASIC_INFO as *mut c_void, + infosize as u32, + ))?; + *info.ChangeTime.QuadPart() + }; + + Ok(tm) +}