diff --git a/Cargo.toml b/Cargo.toml index 3c756d3..c261a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ rand = "0.6" cfg-if = "0.1.9" log = "0.4" filetime = "0.2.7" +lazy_static = "1.4.0" [target.'cfg(unix)'.dependencies] nix = "0.15" diff --git a/src/fs/ctx.rs b/src/fs/ctx.rs new file mode 100644 index 0000000..fcbdd02 --- /dev/null +++ b/src/fs/ctx.rs @@ -0,0 +1,7 @@ +use crate::WasiCtx; + +lazy_static! { + // TODO: Should we allow the context to be passed alternate arguments? + pub(crate) static ref CONTEXT: WasiCtx = + WasiCtx::new(std::env::args()).expect("initializing WASI state"); +} diff --git a/src/fs/dir.rs b/src/fs/dir.rs new file mode 100644 index 0000000..c0078f7 --- /dev/null +++ b/src/fs/dir.rs @@ -0,0 +1,66 @@ +use crate::fs::{ctx, error::wasi_errno_to_io_error, File}; +use crate::{host, hostcalls}; +use std::os::unix::ffi::OsStrExt; +use std::{io, path::Path}; + +/// A reference to an open directory on the filesystem. +pub struct Dir { + fd: host::__wasi_fd_t, +} + +impl Dir { + /// Constructs a new instance of Self from the given raw WASI file descriptor. + pub unsafe fn from_raw_wasi_fd(fd: host::__wasi_fd_t) -> Self { + Self { fd } + } + + /// Attempts to open a file in read-only mode. + fn open_file>(&mut self, path: P) -> io::Result { + let path = path.as_ref(); + let mut fd = 0; + wasi_errno_to_io_error(hostcalls::path_open( + &mut ctx::CONTEXT, + self.fd, + host::__WASI_LOOKUP_SYMLINK_FOLLOW, + path.as_os_str().as_bytes(), + path.as_os_str().len(), + 0, + !0, + !0, + 0, + &mut fd, + ))?; + + Ok(File::from_raw_wasi_fd(fd)) + } + + /// Attempts to open a directory. + fn open_dir>(&mut self, path: P) -> io::Result { + let path = path.as_ref(); + let mut fd = 0; + wasi_errno_to_io_error(hostcalls::path_open( + &mut ctx::CONTEXT, + self.fd, + host::__WASI_LOOKUP_SYMLINK_FOLLOW, + path.as_os_str().as_bytes(), + host::__WASI_O_DIRECTORY, + !0, + !0, + 0, + &mut fd, + ))?; + + Ok(Self::from_raw_wasi_fd(fd)) + } +} + +impl Drop for Dir { + fn drop(&mut self) { + // Note that errors are ignored when closing a file descriptor. The + // reason for this is that if an error occurs we don't actually know if + // the file descriptor was closed or not, and if we retried (for + // something like EINTR), we might close another valid file descriptor + // opened after we closed ours. + let _ = hostcalls::fd_close(&mut ctx::CONTEXT, self.fd); + } +} diff --git a/src/fs/error.rs b/src/fs/error.rs new file mode 100644 index 0000000..3e09a3a --- /dev/null +++ b/src/fs/error.rs @@ -0,0 +1,173 @@ +use crate::host; +use std::io; + +pub(crate) fn wasi_errno_to_io_error(errno: host::__wasi_errno_t) -> io::Result<()> { + #[cfg(unix)] + let raw_os_error = match errno { + host::__WASI_ESUCCESS => return Ok(()), + host::__WASI_EINVAL => libc::EINVAL, + host::__WASI_EPIPE => libc::EPIPE, + host::__WASI_ENOTCONN => libc::ENOTCONN, + host::__WASI_E2BIG => libc::E2BIG, + host::__WASI_EACCES => libc::EACCES, + host::__WASI_EADDRINUSE => libc::EADDRINUSE, + host::__WASI_EADDRNOTAVAIL => libc::EADDRNOTAVAIL, + host::__WASI_EAFNOSUPPORT => libc::EAFNOSUPPORT, + host::__WASI_EAGAIN => libc::EAGAIN, + host::__WASI_EALREADY => libc::EALREADY, + host::__WASI_EBADF => libc::EBADF, + host::__WASI_EBADMSG => libc::EBADMSG, + host::__WASI_EBUSY => libc::EBUSY, + host::__WASI_ECANCELED => libc::ECANCELED, + host::__WASI_ECHILD => libc::ECHILD, + host::__WASI_ECONNABORTED => libc::ECONNABORTED, + host::__WASI_ECONNREFUSED => libc::ECONNREFUSED, + host::__WASI_ECONNRESET => libc::ECONNRESET, + host::__WASI_EDEADLK => libc::EDEADLK, + host::__WASI_EDESTADDRREQ => libc::EDESTADDRREQ, + host::__WASI_EDOM => libc::EDOM, + host::__WASI_EDQUOT => libc::EDQUOT, + host::__WASI_EEXIST => libc::EEXIST, + host::__WASI_EFAULT => libc::EFAULT, + host::__WASI_EFBIG => libc::EFBIG, + host::__WASI_EHOSTUNREACH => libc::EHOSTUNREACH, + host::__WASI_EIDRM => libc::EIDRM, + host::__WASI_EILSEQ => libc::EILSEQ, + host::__WASI_EINPROGRESS => libc::EINPROGRESS, + host::__WASI_EINTR => libc::EINTR, + host::__WASI_EISCONN => libc::EISCONN, + host::__WASI_EISDIR => libc::EISDIR, + host::__WASI_ELOOP => libc::ELOOP, + host::__WASI_EMFILE => libc::EMFILE, + host::__WASI_EMLINK => libc::EMLINK, + host::__WASI_EMSGSIZE => libc::EMSGSIZE, + host::__WASI_EMULTIHOP => libc::EMULTIHOP, + host::__WASI_ENAMETOOLONG => libc::ENAMETOOLONG, + host::__WASI_ENETDOWN => libc::ENETDOWN, + host::__WASI_ENETRESET => libc::ENETRESET, + host::__WASI_ENETUNREACH => libc::ENETUNREACH, + host::__WASI_ENFILE => libc::ENFILE, + host::__WASI_ENOBUFS => libc::ENOBUFS, + host::__WASI_ENODEV => libc::ENODEV, + host::__WASI_ENOENT => libc::ENOENT, + host::__WASI_ENOEXEC => libc::ENOEXEC, + host::__WASI_ENOLCK => libc::ENOLCK, + host::__WASI_ENOLINK => libc::ENOLINK, + host::__WASI_ENOMEM => libc::ENOMEM, + host::__WASI_ENOMSG => libc::ENOMSG, + host::__WASI_ENOPROTOOPT => libc::ENOPROTOOPT, + host::__WASI_ENOSPC => libc::ENOSPC, + host::__WASI_ENOSYS => libc::ENOSYS, + host::__WASI_ENOTDIR => libc::ENOTDIR, + host::__WASI_ENOTEMPTY => libc::ENOTEMPTY, + host::__WASI_ENOTRECOVERABLE => libc::ENOTRECOVERABLE, + host::__WASI_ENOTSOCK => libc::ENOTSOCK, + host::__WASI_ENOTSUP => libc::ENOTSUP, + host::__WASI_ENOTTY => libc::ENOTTY, + host::__WASI_ENXIO => libc::ENXIO, + host::__WASI_EOVERFLOW => libc::EOVERFLOW, + host::__WASI_EOWNERDEAD => libc::EOWNERDEAD, + host::__WASI_EPROTO => libc::EPROTO, + host::__WASI_EPROTONOSUPPORT => libc::EPROTONOSUPPORT, + host::__WASI_EPROTOTYPE => libc::EPROTOTYPE, + host::__WASI_ERANGE => libc::ERANGE, + host::__WASI_EROFS => libc::EROFS, + host::__WASI_ESPIPE => libc::ESPIPE, + host::__WASI_ESRCH => libc::ESRCH, + host::__WASI_ESTALE => libc::ESTALE, + host::__WASI_ETIMEDOUT => libc::ETIMEDOUT, + host::__WASI_ETXTBSY => libc::ETXTBSY, + host::__WASI_EXDEV => libc::EXDEV, + #[cfg(target_os = "wasi")] + host::__WASI_ENOTCAPABLE => libc::ENOTCAPABLE, + #[cfg(not(target_os = "wasi"))] + host::__WASI_ENOTCAPABLE => libc::EIO, + }; + + #[cfg(windows)] + use winapi::shared::winerror::*; + + #[cfg(windows)] + let raw_os_error = match errno { + host::__WASI_ESUCCESS => return Ok(()), + host::__WASI_EINVAL => WSAEINVAL, + host::__WASI_EPIPE => ERROR_BROKEN_PIPE, + host::__WASI_ENOTCONN => WSAENOTCONN, + host::__WASI_EACCES => ERROR_ACCESS_DENIED, + host::__WASI_EADDRINUSE => WSAEADDRINUSE, + host::__WASI_EADDRNOTAVAIL => WSAEADDRNOTAVAIL, + host::__WASI_EAGAIN => WSAEWOULDBLOCK, + host::__WASI_ECONNABORTED => WSAECONNABORTED, + host::__WASI_ECONNREFUSED => WSAECONNREFUSED, + host::__WASI_ECONNRESET => WSAECONNRESET, + host::__WASI_EEXIST => ERROR_ALREADY_EXISTS, + host::__WASI_ENOENT => ERROR_FILE_NOT_FOUND, + host::__WASI_ETIMEDOUT => WSAETIMEDOUT, + host::__WASI_E2BIG => WSAE2BIG, + host::__WASI_EAFNOSUPPORT => WSAEAFNOSUPPORT, + host::__WASI_EALREADY => WSAEALREADY, + host::__WASI_EBADF => WSAEBADF, + host::__WASI_EBADMSG => WSAEBADMSG, + host::__WASI_EBUSY => WSAEBUSY, + host::__WASI_ECANCELED => WSAECANCELED, + host::__WASI_ECHILD => WSAECHILD, + host::__WASI_EDEADLK => WSAEDEADLK, + host::__WASI_EDESTADDRREQ => WSAEDESTADDRREQ, + host::__WASI_EDOM => WSAEDOM, + host::__WASI_EDQUOT => WSAEDQUOT, + host::__WASI_EFAULT => WSAEFAULT, + host::__WASI_EFBIG => WSAEFBIG, + host::__WASI_EHOSTUNREACH => WSAEHOSTUNREACH, + host::__WASI_EIDRM => WSAEIDRM, + host::__WASI_EILSEQ => WSAEILSEQ, + host::__WASI_EINPROGRESS => WSAEINPROGRESS, + host::__WASI_EINTR => WSAEINTR, + host::__WASI_EISCONN => WSAEISCONN, + host::__WASI_EISDIR => WSAEISDIR, + host::__WASI_ELOOP => WSAELOOP, + host::__WASI_EMFILE => WSAEMFILE, + host::__WASI_EMLINK => WSAEMLINK, + host::__WASI_EMSGSIZE => WSAEMSGSIZE, + host::__WASI_EMULTIHOP => WSAEMULTIHOP, + host::__WASI_ENAMETOOLONG => WSAENAMETOOLONG, + host::__WASI_ENETDOWN => WSAENETDOWN, + host::__WASI_ENETRESET => WSAENETRESET, + host::__WASI_ENETUNREACH => WSAENETUNREACH, + host::__WASI_ENFILE => WSAENFILE, + host::__WASI_ENOBUFS => WSAENOBUFS, + host::__WASI_ENODEV => WSAENODEV, + host::__WASI_ENOEXEC => WSAENOEXEC, + host::__WASI_ENOLCK => WSAENOLCK, + host::__WASI_ENOLINK => WSAENOLINK, + host::__WASI_ENOMEM => WSAENOMEM, + host::__WASI_ENOMSG => WSAENOMSG, + host::__WASI_ENOPROTOOPT => WSAENOPROTOOPT, + host::__WASI_ENOSPC => WSAENOSPC, + host::__WASI_ENOSYS => WSAENOSYS, + host::__WASI_ENOTDIR => WSAENOTDIR, + host::__WASI_ENOTEMPTY => WSAENOTEMPTY, + host::__WASI_ENOTRECOVERABLE => WSAENOTRECOVERABLE, + host::__WASI_ENOTSOCK => WSAENOTSOCK, + host::__WASI_ENOTSUP => WSAENOTSUP, + host::__WASI_ENOTTY => WSAENOTTY, + host::__WASI_ENXIO => WSAENXIO, + host::__WASI_EOVERFLOW => WSAEOVERFLOW, + host::__WASI_EOWNERDEAD => WSAEOWNERDEAD, + host::__WASI_EPROTO => WSAEPROTO, + host::__WASI_EPROTONOSUPPORT => WSAEPROTONOSUPPORT, + host::__WASI_EPROTOTYPE => WSAEPROTOTYPE, + host::__WASI_ERANGE => WSAERANGE, + host::__WASI_EROFS => WSAEROFS, + host::__WASI_ESPIPE => WSAESPIPE, + host::__WASI_ESRCH => WSAESRCH, + host::__WASI_ESTALE => WSAESTALE, + host::__WASI_ETXTBSY => WSAETXTBSY, + host::__WASI_EXDEV => WSAEXDEV, + #[cfg(target_os = "wasi")] + host::__WASI_ENOTCAPABLE => WSAENOTCAPABLE, + #[cfg(not(target_os = "wasi"))] + host::__WASI_ENOTCAPABLE => WSAEIO, + }; + + Err(io::Error::from_raw_os_error(raw_os_error)) +} diff --git a/src/fs/file.rs b/src/fs/file.rs new file mode 100644 index 0000000..1f0edbe --- /dev/null +++ b/src/fs/file.rs @@ -0,0 +1,50 @@ +use crate::fs::{ctx, error::wasi_errno_to_io_error}; +use crate::{host, hostcalls}; +use std::io; + +/// A reference to an open file on the filesystem. +pub struct File { + fd: host::__wasi_fd_t, +} + +impl File { + /// Constructs a new instance of Self from the given raw WASI file descriptor. + pub unsafe fn from_raw_wasi_fd(fd: host::__wasi_fd_t) -> Self { + Self { fd } + } + + // TODO: functions to implement: sync_all, sync_data, set_len, metadata +} + +impl Drop for File { + fn drop(&mut self) { + // Note that errors are ignored when closing a file descriptor. The + // reason for this is that if an error occurs we don't actually know if + // the file descriptor was closed or not, and if we retried (for + // something like EINTR), we might close another valid file descriptor + // opened after we closed ours. + let _ = hostcalls::fd_close(&mut ctx::CONTEXT, self.fd); + } +} + +impl io::Read for File { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let iov = [host::__wasi_iovec_t { + buf: buf.as_mut_ptr() as *mut core::ffi::c_void, + buf_len: buf.len(), + }]; + let mut nread = 0; + + wasi_errno_to_io_error(hostcalls::fd_read( + &mut ctx::CONTEXT, + self.fd, + &iov, + 1, + &mut nread, + ))?; + + Ok(nread) + } +} + +// TODO: traits to implement: Write, Seek, FileExt diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..68ebbba --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,9 @@ +mod ctx; +mod dir; +mod error; +mod file; + +pub use dir::*; +pub use file::*; + +// TODO: Implement more things from std::fs. diff --git a/src/lib.rs b/src/lib.rs index 3e773ba..67b5fce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,9 @@ ) )] +#[macro_use] +extern crate lazy_static; + mod ctx; mod error; mod fdentry; @@ -29,6 +32,7 @@ mod sys; #[macro_use] mod macros; +pub mod fs; pub mod host; pub mod hostcalls; pub mod memory;