| //! WASIp1-specific extensions to primitives in the [`std::fs`] module. |
| //! |
| //! [`std::fs`]: crate::fs |
| |
| #![unstable(feature = "wasi_ext", issue = "71213")] |
| |
| // Used for `File::read` on intra-doc links |
| #[allow(unused_imports)] |
| use io::{Read, Write}; |
| |
| #[cfg(target_env = "p1")] |
| use crate::ffi::OsStr; |
| use crate::fs::{self, File, OpenOptions}; |
| use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; |
| #[cfg(target_env = "p1")] |
| use crate::os::fd::AsRawFd; |
| use crate::path::Path; |
| #[cfg(target_env = "p1")] |
| use crate::sys::err2io; |
| use crate::sys::{AsInner, AsInnerMut}; |
| |
| /// WASI-specific extensions to [`File`]. |
| pub trait FileExt { |
| /// Reads a number of bytes starting from a given offset. |
| /// |
| /// Returns the number of bytes read. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// Note that similar to [`File::read`], it is not an error to return with a |
| /// short read. |
| fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>; |
| |
| /// Reads a number of bytes starting from a given offset. |
| /// |
| /// Returns the number of bytes read. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// Note that similar to [`File::read_vectored`], it is not an error to |
| /// return with a short read. |
| fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>; |
| |
| /// Reads some bytes starting from a given offset into the buffer. |
| /// |
| /// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a |
| /// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new |
| /// data will be appended to any existing contents of `buf`. |
| fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()>; |
| |
| /// Reads the exact number of byte required to fill `buf` from the given offset. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`. |
| /// |
| /// [`read_at`]: FileExt::read_at |
| /// |
| /// # Errors |
| /// |
| /// If this function encounters an error of the kind |
| /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation |
| /// will continue. |
| /// |
| /// If this function encounters an "end of file" before completely filling |
| /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`]. |
| /// The contents of `buf` are unspecified in this case. |
| /// |
| /// If any other read error is encountered then this function immediately |
| /// returns. The contents of `buf` are unspecified in this case. |
| /// |
| /// If this function returns an error, it is unspecified how many bytes it |
| /// has read, but it will never read more than would be necessary to |
| /// completely fill the buffer. |
| fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { |
| while !buf.is_empty() { |
| match self.read_at(buf, offset) { |
| Ok(0) => break, |
| Ok(n) => { |
| let tmp = buf; |
| buf = &mut tmp[n..]; |
| offset += n as u64; |
| } |
| Err(ref e) if e.is_interrupted() => {} |
| Err(e) => return Err(e), |
| } |
| } |
| if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) } |
| } |
| |
| /// Writes a number of bytes starting from a given offset. |
| /// |
| /// Returns the number of bytes written. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// When writing beyond the end of the file, the file is appropriately |
| /// extended and the intermediate bytes are initialized with the value 0. |
| /// |
| /// Note that similar to [`File::write`], it is not an error to return a |
| /// short write. |
| fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>; |
| |
| /// Writes a number of bytes starting from a given offset. |
| /// |
| /// Returns the number of bytes written. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// When writing beyond the end of the file, the file is appropriately |
| /// extended and the intermediate bytes are initialized with the value 0. |
| /// |
| /// Note that similar to [`File::write_vectored`], it is not an error to return a |
| /// short write. |
| fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>; |
| |
| /// Attempts to write an entire buffer starting from a given offset. |
| /// |
| /// The offset is relative to the start of the file and thus independent |
| /// from the current cursor. |
| /// |
| /// The current file cursor is not affected by this function. |
| /// |
| /// This method will continuously call [`write_at`] until there is no more data |
| /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is |
| /// returned. This method will not return until the entire buffer has been |
| /// successfully written or such an error occurs. The first error that is |
| /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be |
| /// returned. |
| /// |
| /// # Errors |
| /// |
| /// This function will return the first error of |
| /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns. |
| /// |
| /// [`write_at`]: FileExt::write_at |
| fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { |
| while !buf.is_empty() { |
| match self.write_at(buf, offset) { |
| Ok(0) => { |
| return Err(io::Error::WRITE_ALL_EOF); |
| } |
| Ok(n) => { |
| buf = &buf[n..]; |
| offset += n as u64 |
| } |
| Err(ref e) if e.is_interrupted() => {} |
| Err(e) => return Err(e), |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Adjusts the flags associated with this file. |
| /// |
| /// This corresponds to the `fd_fdstat_set_flags` syscall. |
| #[doc(alias = "fd_fdstat_set_flags")] |
| #[cfg(target_env = "p1")] |
| fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; |
| |
| /// Adjusts the rights associated with this file. |
| /// |
| /// This corresponds to the `fd_fdstat_set_rights` syscall. |
| #[doc(alias = "fd_fdstat_set_rights")] |
| #[cfg(target_env = "p1")] |
| fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; |
| |
| /// Provides file advisory information on a file descriptor. |
| /// |
| /// This corresponds to the `fd_advise` syscall. |
| #[doc(alias = "fd_advise")] |
| #[cfg(target_env = "p1")] |
| fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; |
| |
| /// Forces the allocation of space in a file. |
| /// |
| /// This corresponds to the `fd_allocate` syscall. |
| #[doc(alias = "fd_allocate")] |
| #[cfg(target_env = "p1")] |
| fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; |
| |
| /// Creates a directory. |
| /// |
| /// This corresponds to the `path_create_directory` syscall. |
| #[doc(alias = "path_create_directory")] |
| #[cfg(target_env = "p1")] |
| fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>; |
| |
| /// Unlinks a file. |
| /// |
| /// This corresponds to the `path_unlink_file` syscall. |
| #[doc(alias = "path_unlink_file")] |
| #[cfg(target_env = "p1")] |
| fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>; |
| |
| /// Removes a directory. |
| /// |
| /// This corresponds to the `path_remove_directory` syscall. |
| #[doc(alias = "path_remove_directory")] |
| #[cfg(target_env = "p1")] |
| fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>; |
| } |
| |
| // FIXME: bind fd_fdstat_get - need to define a custom return type |
| // FIXME: bind fd_readdir - can't return `ReadDir` since we only have entry name |
| // FIXME: bind fd_filestat_set_times maybe? - on crates.io for unix |
| // FIXME: bind path_filestat_set_times maybe? - on crates.io for unix |
| // FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle |
| // FIXME: bind random_get maybe? - on crates.io for unix |
| |
| impl FileExt for File { |
| fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> { |
| self.as_inner().read_at(buf, offset) |
| } |
| |
| fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { |
| self.as_inner().read_buf_at(buf, offset) |
| } |
| |
| fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
| self.as_inner().read_vectored_at(bufs, offset) |
| } |
| |
| fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> { |
| self.as_inner().write_at(buf, offset) |
| } |
| |
| fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
| self.as_inner().write_vectored_at(bufs, offset) |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> { |
| unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> { |
| unsafe { |
| wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, rights, inheriting) |
| .map_err(err2io) |
| } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> { |
| let advice = match advice { |
| a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL, |
| a if a == wasi::ADVICE_SEQUENTIAL.raw() => wasi::ADVICE_SEQUENTIAL, |
| a if a == wasi::ADVICE_RANDOM.raw() => wasi::ADVICE_RANDOM, |
| a if a == wasi::ADVICE_WILLNEED.raw() => wasi::ADVICE_WILLNEED, |
| a if a == wasi::ADVICE_DONTNEED.raw() => wasi::ADVICE_DONTNEED, |
| a if a == wasi::ADVICE_NOREUSE.raw() => wasi::ADVICE_NOREUSE, |
| _ => { |
| return Err(io::const_error!( |
| io::ErrorKind::InvalidInput, |
| "invalid parameter 'advice'", |
| )); |
| } |
| }; |
| |
| unsafe { |
| wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) |
| } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { |
| unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> { |
| let path = osstr2str(dir.as_ref().as_ref())?; |
| unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { |
| let path = osstr2str(path.as_ref().as_ref())?; |
| unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { |
| let path = osstr2str(path.as_ref().as_ref())?; |
| unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } |
| } |
| } |
| |
| /// WASI-specific extensions to [`OpenOptions`]. |
| pub trait OpenOptionsExt { |
| /// Pass custom flags to the `flags` argument of `open`. |
| fn custom_flags(&mut self, flags: i32) -> &mut Self; |
| } |
| |
| impl OpenOptionsExt for OpenOptions { |
| fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions { |
| self.as_inner_mut().custom_flags(flags); |
| self |
| } |
| } |
| |
| /// WASI-specific extensions to [`fs::Metadata`]. |
| pub trait MetadataExt { |
| /// Returns the `st_dev` field of the internal `filestat_t` |
| fn dev(&self) -> u64; |
| /// Returns the `st_ino` field of the internal `filestat_t` |
| fn ino(&self) -> u64; |
| /// Returns the `st_nlink` field of the internal `filestat_t` |
| fn nlink(&self) -> u64; |
| } |
| |
| impl MetadataExt for fs::Metadata { |
| fn dev(&self) -> u64 { |
| self.as_inner().as_inner().st_dev |
| } |
| fn ino(&self) -> u64 { |
| self.as_inner().as_inner().st_ino |
| } |
| fn nlink(&self) -> u64 { |
| self.as_inner().as_inner().st_nlink |
| } |
| } |
| |
| /// WASI-specific extensions for [`fs::FileType`]. |
| /// |
| /// Adds support for special WASI file types such as block/character devices, |
| /// pipes, and sockets. |
| pub trait FileTypeExt { |
| /// Returns `true` if this file type is a block device. |
| fn is_block_device(&self) -> bool; |
| /// Returns `true` if this file type is a character device. |
| fn is_char_device(&self) -> bool; |
| /// Returns `true` if this file type is any type of socket. |
| fn is_socket(&self) -> bool; |
| } |
| |
| impl FileTypeExt for fs::FileType { |
| fn is_block_device(&self) -> bool { |
| self.as_inner().is(libc::S_IFBLK) |
| } |
| fn is_char_device(&self) -> bool { |
| self.as_inner().is(libc::S_IFCHR) |
| } |
| fn is_socket(&self) -> bool { |
| self.as_inner().is(libc::S_IFSOCK) |
| } |
| } |
| |
| /// WASI-specific extension methods for [`fs::DirEntry`]. |
| pub trait DirEntryExt { |
| /// Returns the underlying `d_ino` field of the `dirent_t` |
| fn ino(&self) -> u64; |
| } |
| |
| impl DirEntryExt for fs::DirEntry { |
| fn ino(&self) -> u64 { |
| self.as_inner().ino() |
| } |
| } |
| |
| /// Creates a hard link. |
| /// |
| /// This corresponds to the `path_link` syscall. |
| #[doc(alias = "path_link")] |
| #[cfg(target_env = "p1")] |
| pub fn link<P: AsRef<Path>, U: AsRef<Path>>( |
| old_fd: &File, |
| old_flags: u32, |
| old_path: P, |
| new_fd: &File, |
| new_path: U, |
| ) -> io::Result<()> { |
| unsafe { |
| wasi::path_link( |
| old_fd.as_raw_fd() as wasi::Fd, |
| old_flags, |
| osstr2str(old_path.as_ref().as_ref())?, |
| new_fd.as_raw_fd() as wasi::Fd, |
| osstr2str(new_path.as_ref().as_ref())?, |
| ) |
| .map_err(err2io) |
| } |
| } |
| |
| /// Renames a file or directory. |
| /// |
| /// This corresponds to the `path_rename` syscall. |
| #[doc(alias = "path_rename")] |
| #[cfg(target_env = "p1")] |
| pub fn rename<P: AsRef<Path>, U: AsRef<Path>>( |
| old_fd: &File, |
| old_path: P, |
| new_fd: &File, |
| new_path: U, |
| ) -> io::Result<()> { |
| unsafe { |
| wasi::path_rename( |
| old_fd.as_raw_fd() as wasi::Fd, |
| osstr2str(old_path.as_ref().as_ref())?, |
| new_fd.as_raw_fd() as wasi::Fd, |
| osstr2str(new_path.as_ref().as_ref())?, |
| ) |
| .map_err(err2io) |
| } |
| } |
| |
| /// Creates a symbolic link. |
| /// |
| /// This corresponds to the `path_symlink` syscall. |
| #[doc(alias = "path_symlink")] |
| #[cfg(target_env = "p1")] |
| pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>( |
| old_path: P, |
| fd: &File, |
| new_path: U, |
| ) -> io::Result<()> { |
| unsafe { |
| wasi::path_symlink( |
| osstr2str(old_path.as_ref().as_ref())?, |
| fd.as_raw_fd() as wasi::Fd, |
| osstr2str(new_path.as_ref().as_ref())?, |
| ) |
| .map_err(err2io) |
| } |
| } |
| |
| /// Creates a symbolic link. |
| /// |
| /// This is a convenience API similar to `std::os::unix::fs::symlink` and |
| /// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`. |
| pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> { |
| crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref()) |
| } |
| |
| #[cfg(target_env = "p1")] |
| fn osstr2str(f: &OsStr) -> io::Result<&str> { |
| f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) |
| } |