diff --git a/src/fs/dir.rs b/src/fs/dir.rs new file mode 100644 index 0000000000..806628fbe7 --- /dev/null +++ b/src/fs/dir.rs @@ -0,0 +1,216 @@ +use crate::fs::{error::wasi_errno_to_io_error, File, OpenOptions, ReadDir}; +use crate::{host, hostcalls, WasiCtx}; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +use std::{io, path::Path}; + +/// A reference to an open directory on the filesystem. +/// +/// TODO: Implement `Dir`-using versions of `std::fs`'s free functions: +/// `copy`, `create_dir`, `create_dir_all`, `hard_link`, `metadata`, +/// `read_link`, `read_to_string`, `remove_dir`, `remove_dir_all`, +/// `remove_file`, `rename`, `set_permissions`, `symlink_metadata`, and +/// `write`. +/// +/// Unlike `std::fs`, this API has no `canonicalize`, because absolute paths +/// don't interoperate well with the capability-oriented security model. +pub struct Dir<'ctx> { + ctx: &'ctx mut WasiCtx, + fd: host::__wasi_fd_t, +} + +impl<'ctx> Dir<'ctx> { + /// Constructs a new instance of `Self` from the given raw WASI file descriptor. + pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: host::__wasi_fd_t) -> Self { + Self { ctx, fd } + } + + /// Attempts to open a file in read-only mode. + /// + /// This corresponds to [`std::fs::File::open`], but only accesses paths + /// relative to and within `self`. + /// + /// TODO: Not yet implemented. Refactor the hostcalls functions to split out the + /// encoding/decoding parts from the underlying functionality, so that we can call + /// into the underlying functionality directly. + /// + /// [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open + pub fn open_file>(&mut self, path: P) -> io::Result { + let path = path.as_ref(); + let mut fd = 0; + + // TODO: Refactor the hostcalls functions to split out the encoding/decoding + // parts from the underlying functionality, so that we can call into the + // underlying functionality directly. + // + // TODO: Set the requested rights to be readonly. + // + // TODO: Handle paths for non-Unix platforms which don't have `as_bytes()` + // on `OsStrExt`. + unimplemented!("Dir::open_file"); + /* + wasi_errno_to_io_error(hostcalls::path_open( + self.ctx, + self.fd, + host::__WASI_LOOKUP_SYMLINK_FOLLOW, + path.as_os_str().as_bytes(), + path.as_os_str().len(), + 0, + !0, + !0, + 0, + &mut fd, + ))?; + */ + + let ctx = self.ctx; + Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) }) + } + + /// Opens a file at `path` with the options specified by `self`. + /// + /// This corresponds to [`std::fs::OpenOptions::open`]. + /// + /// Instead of being a method on `OpenOptions`, this is a method on `Dir`, + /// and it only accesses functions relative to and within `self`. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::OpenOptions::open`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open + pub fn open_file_with>( + &mut self, + path: P, + options: &OpenOptions, + ) -> io::Result { + unimplemented!("Dir::open_file_with"); + } + + /// Attempts to open a directory. + /// + /// TODO: Not yet implemented. See the comment in `open_file`. + pub fn open_dir>(&mut self, path: P) -> io::Result { + let path = path.as_ref(); + let mut fd = 0; + + // TODO: See the comment in `open_file`. + unimplemented!("Dir::open_dir"); + /* + wasi_errno_to_io_error(hostcalls::path_open( + self.ctx, + self.fd, + host::__WASI_LOOKUP_SYMLINK_FOLLOW, + path.as_os_str().as_bytes(), + host::__WASI_O_DIRECTORY, + !0, + !0, + 0, + &mut fd, + ))?; + */ + + let ctx = self.ctx; + Ok(unsafe { Dir::from_raw_wasi_fd(ctx, fd) }) + } + + /// Opens a file in write-only mode. + /// + /// This corresponds to [`std::fs::File::create`], but only accesses paths + /// relative to and within `self`. + /// + /// TODO: Not yet implemented. See the comment in `open_file`. + /// + /// [`std::fs::File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create + pub fn create_file>(&mut self, path: P) -> io::Result { + let path = path.as_ref(); + let mut fd = 0; + + // TODO: See the comments in `open_file`. + // + // TODO: Set the requested rights to be read+write. + unimplemented!("Dir::create_file"); + /* + wasi_errno_to_io_error(hostcalls::path_open( + self.ctx, + self.fd, + host::__WASI_LOOKUP_SYMLINK_FOLLOW, + path.as_os_str().as_bytes(), + path.as_os_str().len(), + host::__WASI_O_CREAT | host::__WASI_O_TRUNC, + !0, + !0, + 0, + &mut fd, + ))?; + */ + + let ctx = self.ctx; + Ok(unsafe { File::from_raw_wasi_fd(ctx, fd) }) + } + + /// Returns an iterator over the entries within a directory. + /// + /// This corresponds to [`std::fs::read_dir`], but reads the directory + /// represented by `self`. + /// + /// TODO: Not yet implemented. We may need to wait until we have the ability + /// to duplicate file descriptors before we can implement read safely. For + /// now, use `into_read` instead. + /// + /// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html + pub fn read(&mut self) -> io::Result { + unimplemented!("Dir::read") + } + + /// Consumes self and returns an iterator over the entries within a directory + /// in the manner of `read`. + pub fn into_read(self) -> ReadDir { + unsafe { ReadDir::from_raw_wasi_fd(self.fd) } + } + + /// Read the entire contents of a file into a bytes vector. + /// + /// This corresponds to [`std::fs::read`], but only accesses paths + /// relative to and within `self`. + /// + /// [`std::fs::read`]: https://doc.rust-lang.org/std/fs/fn.read.html + pub fn read_file>(&mut self, path: P) -> io::Result> { + use io::Read; + let mut file = self.open_file(path)?; + let mut bytes = Vec::with_capacity(initial_buffer_size(&file)); + file.read_to_end(&mut bytes)?; + Ok(bytes) + } + + /// Returns an iterator over the entries within a directory. + /// + /// This corresponds to [`std::fs::read_dir`], but only accesses paths + /// relative to and within `self`. + /// + /// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html + pub fn read_dir>(&mut self, path: P) -> io::Result { + self.open_dir(path)?.read() + } +} + +impl<'ctx> Drop for Dir<'ctx> { + 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 _ = unsafe { hostcalls::fd_close(self.ctx, self.fd) }; + } +} + +/// Indicates how large a buffer to pre-allocate before reading the entire file. +/// +/// Derived from the function of the same name in libstd. +fn initial_buffer_size(file: &File) -> usize { + // Allocate one extra byte so the buffer doesn't need to grow before the + // final `read` call at the end of the file. Don't worry about `usize` + // overflow because reading will fail regardless in that case. + file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0) +} + +// TODO: impl Debug for Dir diff --git a/src/fs/dir_builder.rs b/src/fs/dir_builder.rs new file mode 100644 index 0000000000..aaf44b7345 --- /dev/null +++ b/src/fs/dir_builder.rs @@ -0,0 +1,49 @@ +use std::{io, path::Path}; + +/// A builder used to create directories in various manners. +/// +/// This corresponds to [`std::fs::DirBuilder`]. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::DirBuilder`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html +pub struct DirBuilder {} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all platforms and also non-recursive. + /// + /// This corresponds to [`std::fs::DirBuilder::new`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirBuilder::new`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.new + pub fn new() -> Self { + unimplemented!("DirBuilder::new"); + } + + /// Indicates that directories should be created recursively, creating all parent directories. + /// + /// This corresponds to [`std::fs::DirBuilder::recursive`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirBuilder::recursive`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.recursive + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + unimplemented!("DirBuilder::recursive"); + } + + /// Creates the specified directory with the options configured in this builder. + /// + /// This corresponds to [`std::fs::DirBuilder::create`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirBuilder::create`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html#method.create + pub fn create>(&self, path: P) -> io::Result<()> { + unimplemented!("DirBuilder::create"); + } +} + +// TODO: functions from DirBuilderExt? + +// TODO: impl Debug for DirBuilder diff --git a/src/fs/dir_entry.rs b/src/fs/dir_entry.rs new file mode 100644 index 0000000000..ff926f1c4c --- /dev/null +++ b/src/fs/dir_entry.rs @@ -0,0 +1,53 @@ +use crate::fs::{FileType, Metadata}; +use std::{ffi, io}; + +/// Entries returned by the ReadDir iterator. +/// +/// This corresponds to [`std::fs::DirEntry`]. +/// +/// Unlike `std::fs::DirEntry`, this API has no `DirEntry::path`, because +/// absolute paths don't interoperate well with the capability-oriented +/// security model. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::DirEntry`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html +pub struct DirEntry {} + +impl DirEntry { + /// Returns the metadata for the file that this entry points at. + /// + /// This corresponds to [`std::fs::DirEntry::metadata`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirEntry::metadata`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.metadata + pub fn metadata(&self) -> io::Result { + unimplemented!("DirEntry::metadata"); + } + + /// Returns the file type for the file that this entry points at. + /// + /// This to [`std::fs::DirEntry::file_type`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirEntry::file_type`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_type + pub fn file_type(&self) -> io::Result { + unimplemented!("DirEntry::file_type"); + } + + /// Returns the bare file name of this directory entry without any other leading path component. + /// + /// This corresponds to [`std::fs::DirEntry::file_name`], though it returns + /// `String` rather than `OsString`. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::DirEntry::file_name`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_name + pub fn file_name(&self) -> String { + unimplemented!("DirEntry::file_name"); + } +} + +// TODO: impl Debug for DirEntry diff --git a/src/fs/error.rs b/src/fs/error.rs new file mode 100644 index 0000000000..db243b76e6 --- /dev/null +++ b/src/fs/error.rs @@ -0,0 +1,265 @@ +use crate::host; +use std::io; + +/// Translate a WASI errno code into an `io::Result<()>`. +/// +/// TODO: Would it be better to have our own version of `io::Error` (and +/// `io::Result`), rather than trying to shoehorn WASI errors into the +/// libstd version? +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_EIO => libc::EIO, + host::__WASI_EPERM => libc::EPERM, + 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, + _ => panic!("unexpected wasi errno value"), + }; + + #[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_EPERM | 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_EAFNOSUPPORT => WSAEAFNOSUPPORT, + host::__WASI_EALREADY => WSAEALREADY, + host::__WASI_EBADF => WSAEBADF, + host::__WASI_EDESTADDRREQ => WSAEDESTADDRREQ, + host::__WASI_EDQUOT => WSAEDQUOT, + host::__WASI_EFAULT => WSAEFAULT, + host::__WASI_EHOSTUNREACH => WSAEHOSTUNREACH, + host::__WASI_EINPROGRESS => WSAEINPROGRESS, + host::__WASI_EINTR => WSAEINTR, + host::__WASI_EISCONN => WSAEISCONN, + host::__WASI_ELOOP => WSAELOOP, + host::__WASI_EMFILE => WSAEMFILE, + host::__WASI_EMSGSIZE => WSAEMSGSIZE, + host::__WASI_ENAMETOOLONG => WSAENAMETOOLONG, + host::__WASI_ENETDOWN => WSAENETDOWN, + host::__WASI_ENETRESET => WSAENETRESET, + host::__WASI_ENETUNREACH => WSAENETUNREACH, + host::__WASI_ENOBUFS => WSAENOBUFS, + host::__WASI_ENOPROTOOPT => WSAENOPROTOOPT, + host::__WASI_ENOTEMPTY => WSAENOTEMPTY, + host::__WASI_ENOTSOCK => WSAENOTSOCK, + host::__WASI_EPROTONOSUPPORT => WSAEPROTONOSUPPORT, + host::__WASI_EPROTOTYPE => WSAEPROTOTYPE, + host::__WASI_ESTALE => WSAESTALE, + host::__WASI_EIO + | host::__WASI_EISDIR + | host::__WASI_E2BIG + | host::__WASI_EBADMSG + | host::__WASI_EBUSY + | host::__WASI_ECANCELED + | host::__WASI_ECHILD + | host::__WASI_EDEADLK + | host::__WASI_EDOM + | host::__WASI_EFBIG + | host::__WASI_EIDRM + | host::__WASI_EILSEQ + | host::__WASI_EMLINK + | host::__WASI_EMULTIHOP + | host::__WASI_ENFILE + | host::__WASI_ENODEV + | host::__WASI_ENOEXEC + | host::__WASI_ENOLCK + | host::__WASI_ENOLINK + | host::__WASI_ENOMEM + | host::__WASI_ENOMSG + | host::__WASI_ENOSPC + | host::__WASI_ENOSYS + | host::__WASI_ENOTDIR + | host::__WASI_ENOTRECOVERABLE + | host::__WASI_ENOTSUP + | host::__WASI_ENOTTY + | host::__WASI_ENXIO + | host::__WASI_EOVERFLOW + | host::__WASI_EOWNERDEAD + | host::__WASI_EPROTO + | host::__WASI_ERANGE + | host::__WASI_EROFS + | host::__WASI_ESPIPE + | host::__WASI_ESRCH + | host::__WASI_ETXTBSY + | host::__WASI_EXDEV + | host::__WASI_ENOTCAPABLE => { + return Err(io::Error::new(io::ErrorKind::Other, error_str(errno))) + } + _ => panic!("unrecognized WASI errno value"), + } as i32; + + Err(io::Error::from_raw_os_error(raw_os_error)) +} + +#[cfg(windows)] +fn error_str(errno: host::__wasi_errno_t) -> &'static str { + match errno { + host::__WASI_E2BIG => "Argument list too long", + host::__WASI_EACCES => "Permission denied", + host::__WASI_EADDRINUSE => "Address in use", + host::__WASI_EADDRNOTAVAIL => "Address not available", + host::__WASI_EAFNOSUPPORT => "Address family not supported by protocol", + host::__WASI_EAGAIN => "Resource temporarily unavailable", + host::__WASI_EALREADY => "Operation already in progress", + host::__WASI_EBADF => "Bad file descriptor", + host::__WASI_EBADMSG => "Bad message", + host::__WASI_EBUSY => "Resource busy", + host::__WASI_ECANCELED => "Operation canceled", + host::__WASI_ECHILD => "No child process", + host::__WASI_ECONNABORTED => "Connection aborted", + host::__WASI_ECONNREFUSED => "Connection refused", + host::__WASI_ECONNRESET => "Connection reset by peer", + host::__WASI_EDEADLK => "Resource deadlock would occur", + host::__WASI_EDESTADDRREQ => "Destination address required", + host::__WASI_EDOM => "Domain error", + host::__WASI_EDQUOT => "Quota exceeded", + host::__WASI_EEXIST => "File exists", + host::__WASI_EFAULT => "Bad address", + host::__WASI_EFBIG => "File too large", + host::__WASI_EHOSTUNREACH => "Host is unreachable", + host::__WASI_EIDRM => "Identifier removed", + host::__WASI_EILSEQ => "Illegal byte sequence", + host::__WASI_EINPROGRESS => "Operation in progress", + host::__WASI_EINTR => "Interrupted system call", + host::__WASI_EINVAL => "Invalid argument", + host::__WASI_EIO => "Remote I/O error", + host::__WASI_EISCONN => "Socket is connected", + host::__WASI_EISDIR => "Is a directory", + host::__WASI_ELOOP => "Symbolic link loop", + host::__WASI_EMFILE => "No file descriptors available", + host::__WASI_EMLINK => "Too many links", + host::__WASI_EMSGSIZE => "Message too large", + host::__WASI_EMULTIHOP => "Multihop attempted", + host::__WASI_ENAMETOOLONG => "Filename too long", + host::__WASI_ENETDOWN => "Network is down", + host::__WASI_ENETRESET => "Connection reset by network", + host::__WASI_ENETUNREACH => "Network unreachable", + host::__WASI_ENFILE => "Too many open files in system", + host::__WASI_ENOBUFS => "No buffer space available", + host::__WASI_ENODEV => "No such device", + host::__WASI_ENOENT => "No such file or directory", + host::__WASI_ENOEXEC => "Exec format error", + host::__WASI_ENOLCK => "No locks available", + host::__WASI_ENOLINK => "Link has been severed", + host::__WASI_ENOMEM => "Out of memory", + host::__WASI_ENOMSG => "No message of desired type", + host::__WASI_ENOPROTOOPT => "Protocol not available", + host::__WASI_ENOSPC => "No space left on device", + host::__WASI_ENOSYS => "Function not implemented", + host::__WASI_ENOTCONN => "Socket not connected", + host::__WASI_ENOTDIR => "Not a directory", + host::__WASI_ENOTEMPTY => "Directory not empty", + host::__WASI_ENOTRECOVERABLE => "State not recoverable", + host::__WASI_ENOTSOCK => "Not a socket", + host::__WASI_ENOTSUP => "Not supported", + host::__WASI_ENOTTY => "Not a tty", + host::__WASI_ENXIO => "No such device or address", + host::__WASI_EOVERFLOW => "Value too large for data type", + host::__WASI_EOWNERDEAD => "Previous owner died", + host::__WASI_EPERM => "Operation not permitted", + host::__WASI_EPIPE => "Broken pipe", + host::__WASI_EPROTO => "Protocol error", + host::__WASI_EPROTONOSUPPORT => "Protocol not supported", + host::__WASI_EPROTOTYPE => "Protocol wrong type for socket", + host::__WASI_ERANGE => "Result not representable", + host::__WASI_EROFS => "Read-only file system", + host::__WASI_ESPIPE => "Invalid seek", + host::__WASI_ESRCH => "No such process", + host::__WASI_ESTALE => "Stale file handle", + host::__WASI_ETIMEDOUT => "Operation timed out", + host::__WASI_ETXTBSY => "Text file busy", + host::__WASI_EXDEV => "Cross-device link", + host::__WASI_ENOTCAPABLE => "Capabilities insufficient", + _ => panic!("unrecognized WASI errno value"), + } +} diff --git a/src/fs/file.rs b/src/fs/file.rs new file mode 100644 index 0000000000..1eae46e9c5 --- /dev/null +++ b/src/fs/file.rs @@ -0,0 +1,104 @@ +use crate::fs::{error::wasi_errno_to_io_error, Metadata}; +use crate::{host, hostcalls, WasiCtx}; +use std::io; + +/// A reference to an open file on the filesystem. +/// +/// This corresponds to [`std::fs::File`]. +/// +/// Note that this `File` has no `open` or `create` methods. To open or create +/// a file, you must first obtain a `Dir` containing the file, and then call +/// `Dir::open_file` or `Dir::create_file`. +/// +/// [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html +pub struct File<'ctx> { + ctx: &'ctx mut WasiCtx, + fd: host::__wasi_fd_t, +} + +impl<'ctx> File<'ctx> { + /// Constructs a new instance of `Self` from the given raw WASI file descriptor. + /// + /// This corresponds to [`std::fs::File::from_raw_fd`]. + /// + /// [`std::fs::File::from_raw_fd`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.from_raw_fd + pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: host::__wasi_fd_t) -> Self { + Self { ctx, fd } + } + + /// Attempts to sync all OS-internal metadata to disk. + /// + /// This corresponds to [`std::fs::File::sync_all`]. + /// + /// [`std::fs::File::sync_all`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all + pub fn sync_all(&self) -> io::Result<()> { + wasi_errno_to_io_error(unsafe { hostcalls::fd_sync(self.ctx, self.fd) }) + } + + /// This function is similar to `sync_all`, except that it may not synchronize + /// file metadata to the filesystem. + /// + /// This corresponds to [`std::fs::File::sync_data`]. + /// + /// [`std::fs::File::sync_data`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data + pub fn sync_data(&self) -> io::Result<()> { + wasi_errno_to_io_error(unsafe { hostcalls::fd_datasync(self.ctx, self.fd) }) + } + + /// Truncates or extends the underlying file, updating the size of this file + /// to become size. + /// + /// This corresponds to [`std::fs::File::set_len`]. + /// + /// [`std::fs::File::set_len`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len + pub fn set_len(&self, size: u64) -> io::Result<()> { + wasi_errno_to_io_error(unsafe { hostcalls::fd_filestat_set_size(self.ctx, self.fd, size) }) + } + + /// Queries metadata about the underlying file. + /// + /// This corresponds to [`std::fs::File::metadata`]. + /// + /// [`std::fs::File::metadata`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata + pub fn metadata(&self) -> io::Result { + Ok(Metadata {}) + } +} + +impl<'ctx> Drop for File<'ctx> { + 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 _ = unsafe { hostcalls::fd_close(self.ctx, self.fd) }; + } +} + +impl<'ctx> io::Read for File<'ctx> { + /// TODO: Not yet implemented. See the comment in `Dir::open_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; + + // TODO: See the comment in `Dir::open_file`. + unimplemented!("File::read"); + /* + wasi_errno_to_io_error(unsafe { + hostcalls::fd_read(self.ctx, self.fd, &iov, 1, &mut nread) + })?; + */ + + Ok(nread) + } +} + +// TODO: traits to implement: Write, Seek + +// TODO: functions from FileExt? + +// TODO: impl Debug for File diff --git a/src/fs/file_type.rs b/src/fs/file_type.rs new file mode 100644 index 0000000000..a97f53a8e1 --- /dev/null +++ b/src/fs/file_type.rs @@ -0,0 +1,49 @@ +/// A structure representing a type of file with accessors for each file type. +/// It is returned by `Metadata::file_type` method. +/// +/// This corresponds to [`std::fs::FileType`]. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct FileType {} + +impl FileType { + /// Tests whether this file type represents a directory. + /// + /// This corresponds to [`std::fs::FileType::is_dir`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::FileType::is_dir`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_dir + pub fn is_dir(&self) -> bool { + unimplemented!("FileType::is_dir"); + } + + /// Tests whether this file type represents a regular file. + /// + /// This corresponds to [`std::fs::FileType::is_file`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::FileType::is_file`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_file + pub fn is_file(&self) -> bool { + unimplemented!("FileType::is_file"); + } + + /// Tests whether this file type represents a symbolic link. + /// + /// This corresponds to [`std::fs::FileType::is_symlink`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::FileType::is_symlink`]: https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_symlink + pub fn is_symlink(&self) -> bool { + unimplemented!("FileType::is_symlink"); + } +} + +// TODO: functions from FileTypeExt? + +// TODO: impl Debug for FileType diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs new file mode 100644 index 0000000000..e55afb73a8 --- /dev/null +++ b/src/fs/metadata.rs @@ -0,0 +1,106 @@ +use crate::fs::{FileType, Permissions}; +use std::{io, time::SystemTime}; + +/// Metadata information about a file. +/// +/// This corresponds to [`std::fs::Metadata`]. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html +#[derive(Clone)] +pub struct Metadata {} + +impl Metadata { + /// Returns the file type for this metadata. + /// + /// This corresponds to [`std::fs::Metadata::file_type`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::file_type`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.file_type + pub fn file_type(&self) -> FileType { + unimplemented!("Metadata::file_type"); + } + + /// Returns true if this metadata is for a directory. + /// + /// This corresponds to [`std::fs::Metadata::is_dir`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::is_dir`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_dir + pub fn is_dir(&self) -> bool { + unimplemented!("Metadata::is_dir"); + } + + /// Returns true if this metadata is for a regular file. + /// + /// This corresponds to [`std::fs::Metadata::is_file`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::is_file`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.is_file + pub fn is_file(&self) -> bool { + unimplemented!("Metadata::is_file"); + } + + /// Returns the size of the file, in bytes, this metadata is for. + /// + /// This corresponds to [`std::fs::Metadata::len`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::len`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.len + pub fn len(&self) -> u64 { + unimplemented!("Metadata::len"); + } + + /// Returns the permissions of the file this metadata is for. + /// + /// This corresponds to [`std::fs::Metadata::permissions`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::permissions`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.permissions + pub fn permissions(&self) -> Permissions { + unimplemented!("Metadata::permissions"); + } + + /// Returns the last modification time listed in this metadata. + /// + /// This corresponds to [`std::fs::Metadata::modified`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::modified`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.modified + pub fn modified(&self) -> io::Result { + unimplemented!("Metadata::modified"); + } + + /// Returns the last access time of this metadata. + /// + /// This corresponds to [`std::fs::Metadata::accessed`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::accessed`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.accessed + pub fn accessed(&self) -> io::Result { + unimplemented!("Metadata::accessed"); + } + + /// Returns the creation time listed in this metadata. + /// + /// This corresponds to [`std::fs::Metadata::created`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Metadata::created`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html#method.created + pub fn created(&self) -> io::Result { + unimplemented!("Metadata::created"); + } +} + +// TODO: Functions from MetadataExt? + +// TODO: impl Debug for Metadata diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000000..d3ab1089f3 --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,50 @@ +//! A very experimental module modeled providing a high-level and safe +//! filesystem interface, modeled after `std::fs`, implemented on top of +//! WASI functions. +//! +//! Most functions in this API are not yet implemented! +//! +//! This corresponds to [`std::fs`]. +//! +//! Instead of [`std::fs`'s free functions] which operate on paths, this +//! crate has methods on `Dir` which operate on paths which must be +//! relative to and within the directory. +//! +//! Since all functions which expose raw file descriptors are `unsafe`, +//! I/O handles in this API are unforgeable (unsafe code notwithstanding). +//! This combined with WASI's lack of absolute paths provides a natural +//! capability-oriented interface. +//! +//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html +//! [`std::fs`'s free functions]: https://doc.rust-lang.org/std/fs/index.html#functions + +// TODO: When more things are implemented, remove these. +#![allow( + unused_imports, + unreachable_code, + unused_variables, + unused_mut, + unused_unsafe, + dead_code +)] + +mod dir; +mod dir_builder; +mod dir_entry; +mod error; +mod file; +mod file_type; +mod metadata; +mod open_options; +mod permissions; +mod readdir; + +pub use dir::*; +pub use dir_builder::*; +pub use dir_entry::*; +pub use file::*; +pub use file_type::*; +pub use metadata::*; +pub use open_options::*; +pub use permissions::*; +pub use readdir::*; diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs new file mode 100644 index 0000000000..c0c5c419c8 --- /dev/null +++ b/src/fs/open_options.rs @@ -0,0 +1,96 @@ +/// Options and flags which can be used to configure how a file is opened. +/// +/// This corresponds to [`std::fs::OpenOptions`]. +/// +/// Note that this `OpenOptions` has no `open` method. To open a file with +/// an `OptionOptions`, pass it to `Dir::open_file_with`. +/// +/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html +pub struct OpenOptions { + pub(crate) read: bool, + pub(crate) write: bool, + pub(crate) append: bool, + pub(crate) truncate: bool, + pub(crate) create: bool, + pub(crate) create_new: bool, +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + /// + /// This corresponds to [`std::fs::OpenOptions::new`]. + /// + /// [`std::fs::OpenOptions::new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.new + pub fn new() -> Self { + Self { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + } + } + + /// Sets the option for read access. + /// + /// This corresponds to [`std::fs::OpenOptions::read`]. + /// + /// [`std::fs::OpenOptions::read`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read + pub fn read(&mut self, read: bool) -> &mut Self { + self.read = read; + self + } + + /// Sets the option for write access. + /// + /// This corresponds to [`std::fs::OpenOptions::write`]. + /// + /// [`std::fs::OpenOptions::write`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write + pub fn write(&mut self, write: bool) -> &mut Self { + self.write = write; + self + } + + /// Sets the option for the append mode. + /// + /// This corresponds to [`std::fs::OpenOptions::append`]. + /// + /// [`std::fs::OpenOptions::append`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append + pub fn append(&mut self, append: bool) -> &mut Self { + self.append = append; + self + } + + /// Sets the option for truncating a previous file. + /// + /// This corresponds to [`std::fs::OpenOptions::truncate`]. + /// + /// [`std::fs::OpenOptions::truncate`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.truncate = truncate; + self + } + + /// Sets the option to create a new file. + /// + /// This corresponds to [`std::fs::OpenOptions::create`]. + /// + /// [`std::fs::OpenOptions::create`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create + pub fn create(&mut self, create: bool) -> &mut Self { + self.create = create; + self + } + + /// Sets the option to always create a new file. + /// + /// This corresponds to [`std::fs::OpenOptions::create_new`]. + /// + /// [`std::fs::OpenOptions::create_new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.create_new = create_new; + self + } +} + +// TODO: Functions from OpenOptionsExt? diff --git a/src/fs/permissions.rs b/src/fs/permissions.rs new file mode 100644 index 0000000000..498a1898ff --- /dev/null +++ b/src/fs/permissions.rs @@ -0,0 +1,37 @@ +/// Representation of the various permissions on a file. +/// +/// This corresponds to [`std::fs::Permissions`]. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html +#[derive(Eq, PartialEq, Clone)] +pub struct Permissions {} + +impl Permissions { + /// Returns true if these permissions describe a readonly (unwritable) file. + /// + /// This corresponds to [`std::fs::Permissions::readonly`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Permissions::readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.readonly + pub fn readonly(&self) -> bool { + unimplemented!("Permissions::readonly"); + } + + /// Modifies the readonly flag for this set of permissions. + /// + /// This corresponds to [`std::fs::Permissions::set_readonly`]. + /// + /// TODO: Not yet implemented. + /// + /// [`std::fs::Permissions::set_readonly`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly + pub fn set_readonly(&mut self, readonly: bool) { + unimplemented!("Permissions::set_readonly"); + } +} + +// TODO: functions from PermissionsExt? + +// TODO: impl Debug for Permissions diff --git a/src/fs/readdir.rs b/src/fs/readdir.rs new file mode 100644 index 0000000000..21f7887692 --- /dev/null +++ b/src/fs/readdir.rs @@ -0,0 +1,32 @@ +use crate::fs::DirEntry; +use crate::{host, hostcalls}; + +/// Iterator over the entries in a directory. +/// +/// This corresponds to [`std::fs::ReadDir`]. +/// +/// TODO: Not yet implemented. +/// +/// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html +pub struct ReadDir { + fd: host::__wasi_fd_t, +} + +impl ReadDir { + /// 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: Not yet implemented. +impl Iterator for ReadDir { + type Item = DirEntry; + + /// TODO: Not yet implemented. + fn next(&mut self) -> Option { + unimplemented!("ReadDir::next"); + } +} + +// TODO: impl Debug for ReadDir diff --git a/src/lib.rs b/src/lib.rs index 99c8305adf..864e909000 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ mod hostcalls_impl; mod sys; #[macro_use] mod macros; +pub mod fs; mod host; pub mod hostcalls; mod memory;