summaryrefslogtreecommitdiffstats
path: root/library/std/src/os/wasi/fs.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/std/src/os/wasi/fs.rs558
1 files changed, 558 insertions, 0 deletions
diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs
new file mode 100644
index 000000000..160c8f1ec
--- /dev/null
+++ b/library/std/src/os/wasi/fs.rs
@@ -0,0 +1,558 @@
+//! WASI-specific extensions to primitives in the [`std::fs`] module.
+//!
+//! [`std::fs`]: crate::fs
+
+#![deny(unsafe_op_in_unsafe_fn)]
+#![unstable(feature = "wasi_ext", issue = "71213")]
+
+use crate::ffi::OsStr;
+use crate::fs::{self, File, Metadata, OpenOptions};
+use crate::io::{self, IoSlice, IoSliceMut};
+use crate::path::{Path, PathBuf};
+use crate::sys_common::{AsInner, AsInnerMut, FromInner};
+// Used for `File::read` on intra-doc links
+#[allow(unused_imports)]
+use io::{Read, Write};
+
+/// 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> {
+ let bufs = &mut [IoSliceMut::new(buf)];
+ self.read_vectored_at(bufs, offset)
+ }
+
+ /// 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 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.
+ #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
+ 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.kind() == io::ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ if !buf.is_empty() {
+ Err(io::const_io_error!(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
+ } 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> {
+ let bufs = &[IoSlice::new(buf)];
+ self.write_vectored_at(bufs, offset)
+ }
+
+ /// 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
+ #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
+ 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::const_io_error!(
+ io::ErrorKind::WriteZero,
+ "failed to write whole buffer",
+ ));
+ }
+ Ok(n) => {
+ buf = &buf[n..];
+ offset += n as u64
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(())
+ }
+
+ /// Returns the current position within the file.
+ ///
+ /// This corresponds to the `fd_tell` syscall and is similar to
+ /// `seek` where you offset 0 bytes from the current position.
+ fn tell(&self) -> io::Result<u64>;
+
+ /// Adjust the flags associated with this file.
+ ///
+ /// This corresponds to the `fd_fdstat_set_flags` syscall.
+ fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
+
+ /// Adjust the rights associated with this file.
+ ///
+ /// This corresponds to the `fd_fdstat_set_rights` syscall.
+ fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
+
+ /// Provide file advisory information on a file descriptor.
+ ///
+ /// This corresponds to the `fd_advise` syscall.
+ fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
+
+ /// Force the allocation of space in a file.
+ ///
+ /// This corresponds to the `fd_allocate` syscall.
+ fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
+
+ /// Create a directory.
+ ///
+ /// This corresponds to the `path_create_directory` syscall.
+ fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
+
+ /// Read the contents of a symbolic link.
+ ///
+ /// This corresponds to the `path_readlink` syscall.
+ fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
+
+ /// Return the attributes of a file or directory.
+ ///
+ /// This corresponds to the `path_filestat_get` syscall.
+ fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
+
+ /// Unlink a file.
+ ///
+ /// This corresponds to the `path_unlink_file` syscall.
+ fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
+
+ /// Remove a directory.
+ ///
+ /// This corresponds to the `path_remove_directory` syscall.
+ 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 fs::File {
+ fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+ self.as_inner().as_inner().pread(bufs, offset)
+ }
+
+ fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+ self.as_inner().as_inner().pwrite(bufs, offset)
+ }
+
+ fn tell(&self) -> io::Result<u64> {
+ self.as_inner().as_inner().tell()
+ }
+
+ fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
+ self.as_inner().as_inner().set_flags(flags)
+ }
+
+ fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
+ self.as_inner().as_inner().set_rights(rights, inheriting)
+ }
+
+ 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_io_error!(
+ io::ErrorKind::InvalidInput,
+ "invalid parameter 'advice'",
+ ));
+ }
+ };
+
+ self.as_inner().as_inner().advise(offset, len, advice)
+ }
+
+ fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
+ self.as_inner().as_inner().allocate(offset, len)
+ }
+
+ fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
+ self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?)
+ }
+
+ fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
+ self.as_inner().read_link(path.as_ref())
+ }
+
+ fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
+ let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
+ Ok(FromInner::from_inner(m))
+ }
+
+ fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
+ self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?)
+ }
+
+ fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
+ self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?)
+ }
+}
+
+/// WASI-specific extensions to [`fs::OpenOptions`].
+pub trait OpenOptionsExt {
+ /// Pass custom `dirflags` argument to `path_open`.
+ ///
+ /// This option configures the `dirflags` argument to the
+ /// `path_open` syscall which `OpenOptions` will eventually call. The
+ /// `dirflags` argument configures how the file is looked up, currently
+ /// primarily affecting whether symlinks are followed or not.
+ ///
+ /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
+ /// followed. You can call this method with 0 to disable following symlinks
+ fn lookup_flags(&mut self, flags: u32) -> &mut Self;
+
+ /// Indicates whether `OpenOptions` must open a directory or not.
+ ///
+ /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
+ /// passed when opening a file. When passed it will require that the opened
+ /// path is a directory.
+ ///
+ /// This option is by default `false`
+ fn directory(&mut self, dir: bool) -> &mut Self;
+
+ /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
+ /// field of `path_open`.
+ ///
+ /// This option is by default `false`
+ fn dsync(&mut self, dsync: bool) -> &mut Self;
+
+ /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
+ /// field of `path_open`.
+ ///
+ /// This option is by default `false`
+ fn nonblock(&mut self, nonblock: bool) -> &mut Self;
+
+ /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
+ /// field of `path_open`.
+ ///
+ /// This option is by default `false`
+ fn rsync(&mut self, rsync: bool) -> &mut Self;
+
+ /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
+ /// field of `path_open`.
+ ///
+ /// This option is by default `false`
+ fn sync(&mut self, sync: bool) -> &mut Self;
+
+ /// Indicates the value that should be passed in for the `fs_rights_base`
+ /// parameter of `path_open`.
+ ///
+ /// This option defaults based on the `read` and `write` configuration of
+ /// this `OpenOptions` builder. If this method is called, however, the
+ /// exact mask passed in will be used instead.
+ fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
+
+ /// Indicates the value that should be passed in for the
+ /// `fs_rights_inheriting` parameter of `path_open`.
+ ///
+ /// The default for this option is the same value as what will be passed
+ /// for the `fs_rights_base` parameter but if this method is called then
+ /// the specified value will be used instead.
+ fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
+
+ /// Open a file or directory.
+ ///
+ /// This corresponds to the `path_open` syscall.
+ fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
+}
+
+impl OpenOptionsExt for OpenOptions {
+ fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
+ self.as_inner_mut().lookup_flags(flags);
+ self
+ }
+
+ fn directory(&mut self, dir: bool) -> &mut OpenOptions {
+ self.as_inner_mut().directory(dir);
+ self
+ }
+
+ fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
+ self.as_inner_mut().dsync(enabled);
+ self
+ }
+
+ fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
+ self.as_inner_mut().nonblock(enabled);
+ self
+ }
+
+ fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
+ self.as_inner_mut().rsync(enabled);
+ self
+ }
+
+ fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
+ self.as_inner_mut().sync(enabled);
+ self
+ }
+
+ fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
+ self.as_inner_mut().fs_rights_base(rights);
+ self
+ }
+
+ fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
+ self.as_inner_mut().fs_rights_inheriting(rights);
+ self
+ }
+
+ fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
+ let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
+ Ok(File::from_inner(inner))
+ }
+}
+
+/// 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;
+ /// Returns the `st_size` field of the internal `filestat_t`
+ fn size(&self) -> u64;
+ /// Returns the `st_atim` field of the internal `filestat_t`
+ fn atim(&self) -> u64;
+ /// Returns the `st_mtim` field of the internal `filestat_t`
+ fn mtim(&self) -> u64;
+ /// Returns the `st_ctim` field of the internal `filestat_t`
+ fn ctim(&self) -> u64;
+}
+
+impl MetadataExt for fs::Metadata {
+ fn dev(&self) -> u64 {
+ self.as_inner().as_wasi().dev
+ }
+ fn ino(&self) -> u64 {
+ self.as_inner().as_wasi().ino
+ }
+ fn nlink(&self) -> u64 {
+ self.as_inner().as_wasi().nlink
+ }
+ fn size(&self) -> u64 {
+ self.as_inner().as_wasi().size
+ }
+ fn atim(&self) -> u64 {
+ self.as_inner().as_wasi().atim
+ }
+ fn mtim(&self) -> u64 {
+ self.as_inner().as_wasi().mtim
+ }
+ fn ctim(&self) -> u64 {
+ self.as_inner().as_wasi().ctim
+ }
+}
+
+/// 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 a socket datagram.
+ fn is_socket_dgram(&self) -> bool;
+ /// Returns `true` if this file type is a socket stream.
+ fn is_socket_stream(&self) -> bool;
+ /// Returns `true` if this file type is any type of socket.
+ fn is_socket(&self) -> bool {
+ self.is_socket_stream() || self.is_socket_dgram()
+ }
+}
+
+impl FileTypeExt for fs::FileType {
+ fn is_block_device(&self) -> bool {
+ self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
+ }
+ fn is_char_device(&self) -> bool {
+ self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
+ }
+ fn is_socket_dgram(&self) -> bool {
+ self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
+ }
+ fn is_socket_stream(&self) -> bool {
+ self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
+ }
+}
+
+/// 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()
+ }
+}
+
+/// Create a hard link.
+///
+/// This corresponds to the `path_link` syscall.
+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<()> {
+ old_fd.as_inner().as_inner().link(
+ old_flags,
+ osstr2str(old_path.as_ref().as_ref())?,
+ new_fd.as_inner().as_inner(),
+ osstr2str(new_path.as_ref().as_ref())?,
+ )
+}
+
+/// Rename a file or directory.
+///
+/// This corresponds to the `path_rename` syscall.
+pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
+ old_fd: &File,
+ old_path: P,
+ new_fd: &File,
+ new_path: U,
+) -> io::Result<()> {
+ old_fd.as_inner().as_inner().rename(
+ osstr2str(old_path.as_ref().as_ref())?,
+ new_fd.as_inner().as_inner(),
+ osstr2str(new_path.as_ref().as_ref())?,
+ )
+}
+
+/// Create a symbolic link.
+///
+/// This corresponds to the `path_symlink` syscall.
+pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
+ old_path: P,
+ fd: &File,
+ new_path: U,
+) -> io::Result<()> {
+ fd.as_inner()
+ .as_inner()
+ .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)
+}
+
+/// Create 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())
+}
+
+fn osstr2str(f: &OsStr) -> io::Result<&str> {
+ f.to_str()
+ .ok_or_else(|| io::const_io_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
+}