1 //! WASI-specific extensions to primitives in the [`std::fs`] module.
3 //! [`std::fs`]: crate::fs
5 #![deny(unsafe_op_in_unsafe_fn)]
6 #![unstable(feature = "wasi_ext", issue = "71213")]
9 use crate::fs::{self, File, Metadata, OpenOptions};
10 use crate::io::{self, IoSlice, IoSliceMut};
11 use crate::path::{Path, PathBuf};
12 use crate::sys_common::{AsInner, AsInnerMut, FromInner};
13 // Used for `File::read` on intra-doc links
14 #[allow(unused_imports)]
15 use io::{Read, Write};
17 /// WASI-specific extensions to [`File`].
19 /// Reads a number of bytes starting from a given offset.
21 /// Returns the number of bytes read.
23 /// The offset is relative to the start of the file and thus independent
24 /// from the current cursor.
26 /// The current file cursor is not affected by this function.
28 /// Note that similar to [`File::read`], it is not an error to return with a
30 fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
31 let bufs = &mut [IoSliceMut::new(buf)];
32 self.read_vectored_at(bufs, offset)
35 /// Reads a number of bytes starting from a given offset.
37 /// Returns the number of bytes read.
39 /// The offset is relative to the start of the file and thus independent
40 /// from the current cursor.
42 /// The current file cursor is not affected by this function.
44 /// Note that similar to [`File::read_vectored`], it is not an error to
45 /// return with a short read.
46 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
48 /// Reads the exact number of byte required to fill `buf` from the given offset.
50 /// The offset is relative to the start of the file and thus independent
51 /// from the current cursor.
53 /// The current file cursor is not affected by this function.
55 /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
57 /// [`read_at`]: FileExt::read_at
61 /// If this function encounters an error of the kind
62 /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation
65 /// If this function encounters an "end of file" before completely filling
66 /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`].
67 /// The contents of `buf` are unspecified in this case.
69 /// If any other read error is encountered then this function immediately
70 /// returns. The contents of `buf` are unspecified in this case.
72 /// If this function returns an error, it is unspecified how many bytes it
73 /// has read, but it will never read more than would be necessary to
74 /// completely fill the buffer.
75 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
76 fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
77 while !buf.is_empty() {
78 match self.read_at(buf, offset) {
85 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
86 Err(e) => return Err(e),
90 Err(io::const_io_error!(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
96 /// Writes a number of bytes starting from a given offset.
98 /// Returns the number of bytes written.
100 /// The offset is relative to the start of the file and thus independent
101 /// from the current cursor.
103 /// The current file cursor is not affected by this function.
105 /// When writing beyond the end of the file, the file is appropriately
106 /// extended and the intermediate bytes are initialized with the value 0.
108 /// Note that similar to [`File::write`], it is not an error to return a
110 fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
111 let bufs = &[IoSlice::new(buf)];
112 self.write_vectored_at(bufs, offset)
115 /// Writes a number of bytes starting from a given offset.
117 /// Returns the number of bytes written.
119 /// The offset is relative to the start of the file and thus independent
120 /// from the current cursor.
122 /// The current file cursor is not affected by this function.
124 /// When writing beyond the end of the file, the file is appropriately
125 /// extended and the intermediate bytes are initialized with the value 0.
127 /// Note that similar to [`File::write_vectored`], it is not an error to return a
129 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
131 /// Attempts to write an entire buffer starting from a given offset.
133 /// The offset is relative to the start of the file and thus independent
134 /// from the current cursor.
136 /// The current file cursor is not affected by this function.
138 /// This method will continuously call [`write_at`] until there is no more data
139 /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is
140 /// returned. This method will not return until the entire buffer has been
141 /// successfully written or such an error occurs. The first error that is
142 /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be
147 /// This function will return the first error of
148 /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns.
150 /// [`write_at`]: FileExt::write_at
151 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
152 fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
153 while !buf.is_empty() {
154 match self.write_at(buf, offset) {
156 return Err(io::const_io_error!(
157 io::ErrorKind::WriteZero,
158 "failed to write whole buffer",
165 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
166 Err(e) => return Err(e),
172 /// Returns the current position within the file.
174 /// This corresponds to the `fd_tell` syscall and is similar to
175 /// `seek` where you offset 0 bytes from the current position.
176 fn tell(&self) -> io::Result<u64>;
178 /// Adjust the flags associated with this file.
180 /// This corresponds to the `fd_fdstat_set_flags` syscall.
181 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
183 /// Adjust the rights associated with this file.
185 /// This corresponds to the `fd_fdstat_set_rights` syscall.
186 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
188 /// Provide file advisory information on a file descriptor.
190 /// This corresponds to the `fd_advise` syscall.
191 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
193 /// Force the allocation of space in a file.
195 /// This corresponds to the `fd_allocate` syscall.
196 fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
198 /// Create a directory.
200 /// This corresponds to the `path_create_directory` syscall.
201 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
203 /// Read the contents of a symbolic link.
205 /// This corresponds to the `path_readlink` syscall.
206 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
208 /// Return the attributes of a file or directory.
210 /// This corresponds to the `path_filestat_get` syscall.
211 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
215 /// This corresponds to the `path_unlink_file` syscall.
216 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
218 /// Remove a directory.
220 /// This corresponds to the `path_remove_directory` syscall.
221 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
224 // FIXME: bind fd_fdstat_get - need to define a custom return type
225 // FIXME: bind fd_readdir - can't return `ReadDir` since we only have entry name
226 // FIXME: bind fd_filestat_set_times maybe? - on crates.io for unix
227 // FIXME: bind path_filestat_set_times maybe? - on crates.io for unix
228 // FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle
229 // FIXME: bind random_get maybe? - on crates.io for unix
231 impl FileExt for fs::File {
232 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
233 self.as_inner().as_inner().pread(bufs, offset)
236 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
237 self.as_inner().as_inner().pwrite(bufs, offset)
240 fn tell(&self) -> io::Result<u64> {
241 self.as_inner().as_inner().tell()
244 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
245 self.as_inner().as_inner().set_flags(flags)
248 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
249 self.as_inner().as_inner().set_rights(rights, inheriting)
252 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
253 let advice = match advice {
254 a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL,
255 a if a == wasi::ADVICE_SEQUENTIAL.raw() => wasi::ADVICE_SEQUENTIAL,
256 a if a == wasi::ADVICE_RANDOM.raw() => wasi::ADVICE_RANDOM,
257 a if a == wasi::ADVICE_WILLNEED.raw() => wasi::ADVICE_WILLNEED,
258 a if a == wasi::ADVICE_DONTNEED.raw() => wasi::ADVICE_DONTNEED,
259 a if a == wasi::ADVICE_NOREUSE.raw() => wasi::ADVICE_NOREUSE,
261 return Err(io::const_io_error!(
262 io::ErrorKind::InvalidInput,
263 "invalid parameter 'advice'",
268 self.as_inner().as_inner().advise(offset, len, advice)
271 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
272 self.as_inner().as_inner().allocate(offset, len)
275 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
276 self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?)
279 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
280 self.as_inner().read_link(path.as_ref())
283 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
284 let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
285 Ok(FromInner::from_inner(m))
288 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
289 self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?)
292 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
293 self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?)
297 /// WASI-specific extensions to [`fs::OpenOptions`].
298 pub trait OpenOptionsExt {
299 /// Pass custom `dirflags` argument to `path_open`.
301 /// This option configures the `dirflags` argument to the
302 /// `path_open` syscall which `OpenOptions` will eventually call. The
303 /// `dirflags` argument configures how the file is looked up, currently
304 /// primarily affecting whether symlinks are followed or not.
306 /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
307 /// followed. You can call this method with 0 to disable following symlinks
308 fn lookup_flags(&mut self, flags: u32) -> &mut Self;
310 /// Indicates whether `OpenOptions` must open a directory or not.
312 /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
313 /// passed when opening a file. When passed it will require that the opened
314 /// path is a directory.
316 /// This option is by default `false`
317 fn directory(&mut self, dir: bool) -> &mut Self;
319 /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
320 /// field of `path_open`.
322 /// This option is by default `false`
323 fn dsync(&mut self, dsync: bool) -> &mut Self;
325 /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
326 /// field of `path_open`.
328 /// This option is by default `false`
329 fn nonblock(&mut self, nonblock: bool) -> &mut Self;
331 /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
332 /// field of `path_open`.
334 /// This option is by default `false`
335 fn rsync(&mut self, rsync: bool) -> &mut Self;
337 /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
338 /// field of `path_open`.
340 /// This option is by default `false`
341 fn sync(&mut self, sync: bool) -> &mut Self;
343 /// Indicates the value that should be passed in for the `fs_rights_base`
344 /// parameter of `path_open`.
346 /// This option defaults based on the `read` and `write` configuration of
347 /// this `OpenOptions` builder. If this method is called, however, the
348 /// exact mask passed in will be used instead.
349 fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
351 /// Indicates the value that should be passed in for the
352 /// `fs_rights_inheriting` parameter of `path_open`.
354 /// The default for this option is the same value as what will be passed
355 /// for the `fs_rights_base` parameter but if this method is called then
356 /// the specified value will be used instead.
357 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
359 /// Open a file or directory.
361 /// This corresponds to the `path_open` syscall.
362 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
365 impl OpenOptionsExt for OpenOptions {
366 fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
367 self.as_inner_mut().lookup_flags(flags);
371 fn directory(&mut self, dir: bool) -> &mut OpenOptions {
372 self.as_inner_mut().directory(dir);
376 fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
377 self.as_inner_mut().dsync(enabled);
381 fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
382 self.as_inner_mut().nonblock(enabled);
386 fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
387 self.as_inner_mut().rsync(enabled);
391 fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
392 self.as_inner_mut().sync(enabled);
396 fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
397 self.as_inner_mut().fs_rights_base(rights);
401 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
402 self.as_inner_mut().fs_rights_inheriting(rights);
406 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
407 let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
408 Ok(File::from_inner(inner))
412 /// WASI-specific extensions to [`fs::Metadata`].
413 pub trait MetadataExt {
414 /// Returns the `st_dev` field of the internal `filestat_t`
415 fn dev(&self) -> u64;
416 /// Returns the `st_ino` field of the internal `filestat_t`
417 fn ino(&self) -> u64;
418 /// Returns the `st_nlink` field of the internal `filestat_t`
419 fn nlink(&self) -> u64;
420 /// Returns the `st_size` field of the internal `filestat_t`
421 fn size(&self) -> u64;
422 /// Returns the `st_atim` field of the internal `filestat_t`
423 fn atim(&self) -> u64;
424 /// Returns the `st_mtim` field of the internal `filestat_t`
425 fn mtim(&self) -> u64;
426 /// Returns the `st_ctim` field of the internal `filestat_t`
427 fn ctim(&self) -> u64;
430 impl MetadataExt for fs::Metadata {
431 fn dev(&self) -> u64 {
432 self.as_inner().as_wasi().dev
434 fn ino(&self) -> u64 {
435 self.as_inner().as_wasi().ino
437 fn nlink(&self) -> u64 {
438 self.as_inner().as_wasi().nlink
440 fn size(&self) -> u64 {
441 self.as_inner().as_wasi().size
443 fn atim(&self) -> u64 {
444 self.as_inner().as_wasi().atim
446 fn mtim(&self) -> u64 {
447 self.as_inner().as_wasi().mtim
449 fn ctim(&self) -> u64 {
450 self.as_inner().as_wasi().ctim
454 /// WASI-specific extensions for [`fs::FileType`].
456 /// Adds support for special WASI file types such as block/character devices,
457 /// pipes, and sockets.
458 pub trait FileTypeExt {
459 /// Returns `true` if this file type is a block device.
460 fn is_block_device(&self) -> bool;
461 /// Returns `true` if this file type is a character device.
462 fn is_char_device(&self) -> bool;
463 /// Returns `true` if this file type is a socket datagram.
464 fn is_socket_dgram(&self) -> bool;
465 /// Returns `true` if this file type is a socket stream.
466 fn is_socket_stream(&self) -> bool;
467 /// Returns `true` if this file type is any type of socket.
468 fn is_socket(&self) -> bool {
469 self.is_socket_stream() || self.is_socket_dgram()
473 impl FileTypeExt for fs::FileType {
474 fn is_block_device(&self) -> bool {
475 self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
477 fn is_char_device(&self) -> bool {
478 self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
480 fn is_socket_dgram(&self) -> bool {
481 self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
483 fn is_socket_stream(&self) -> bool {
484 self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
488 /// WASI-specific extension methods for [`fs::DirEntry`].
489 pub trait DirEntryExt {
490 /// Returns the underlying `d_ino` field of the `dirent_t`
491 fn ino(&self) -> u64;
494 impl DirEntryExt for fs::DirEntry {
495 fn ino(&self) -> u64 {
496 self.as_inner().ino()
500 /// Create a hard link.
502 /// This corresponds to the `path_link` syscall.
503 pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
509 ) -> io::Result<()> {
510 old_fd.as_inner().as_inner().link(
512 osstr2str(old_path.as_ref().as_ref())?,
513 new_fd.as_inner().as_inner(),
514 osstr2str(new_path.as_ref().as_ref())?,
518 /// Rename a file or directory.
520 /// This corresponds to the `path_rename` syscall.
521 pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
526 ) -> io::Result<()> {
527 old_fd.as_inner().as_inner().rename(
528 osstr2str(old_path.as_ref().as_ref())?,
529 new_fd.as_inner().as_inner(),
530 osstr2str(new_path.as_ref().as_ref())?,
534 /// Create a symbolic link.
536 /// This corresponds to the `path_symlink` syscall.
537 pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
541 ) -> io::Result<()> {
544 .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)
547 /// Create a symbolic link.
549 /// This is a convenience API similar to `std::os::unix::fs::symlink` and
550 /// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`.
551 pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> {
552 crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref())
555 fn osstr2str(f: &OsStr) -> io::Result<&str> {
557 .ok_or_else(|| io::const_io_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))