1 //! WASI-specific extensions to primitives in the `std::fs` module.
3 #![deny(unsafe_op_in_unsafe_fn)]
4 #![unstable(feature = "wasi_ext", issue = "none")]
6 use crate::fs::{self, File, Metadata, OpenOptions};
7 use crate::io::{self, IoSlice, IoSliceMut};
8 use crate::path::{Path, PathBuf};
9 use crate::sys::fs::osstr2str;
10 use crate::sys_common::{AsInner, AsInnerMut, FromInner};
12 /// WASI-specific extensions to [`File`].
14 /// Reads a number of bytes starting from a given offset.
16 /// Returns the number of bytes read.
18 /// The offset is relative to the start of the file and thus independent
19 /// from the current cursor.
21 /// The current file cursor is not affected by this function.
23 /// Note that similar to [`File::read`], it is not an error to return with a
25 fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
26 let bufs = &mut [IoSliceMut::new(buf)];
27 self.read_vectored_at(bufs, offset)
30 /// Reads a number of bytes starting from a given offset.
32 /// Returns the number of bytes read.
34 /// The offset is relative to the start of the file and thus independent
35 /// from the current cursor.
37 /// The current file cursor is not affected by this function.
39 /// Note that similar to [`File::read_vectored`], it is not an error to
40 /// return with a short read.
41 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
43 /// Reads the exact number of byte required to fill `buf` from the given offset.
45 /// The offset is relative to the start of the file and thus independent
46 /// from the current cursor.
48 /// The current file cursor is not affected by this function.
50 /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
52 /// [`read_at`]: FileExt::read_at
56 /// If this function encounters an error of the kind
57 /// [`ErrorKind::Interrupted`] then the error is ignored and the operation
60 /// If this function encounters an "end of file" before completely filling
61 /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`].
62 /// The contents of `buf` are unspecified in this case.
64 /// If any other read error is encountered then this function immediately
65 /// returns. The contents of `buf` are unspecified in this case.
67 /// If this function returns an error, it is unspecified how many bytes it
68 /// has read, but it will never read more than would be necessary to
69 /// completely fill the buffer.
70 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
71 fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
72 while !buf.is_empty() {
73 match self.read_at(buf, offset) {
80 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
81 Err(e) => return Err(e),
85 Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
91 /// Writes a number of bytes starting from a given offset.
93 /// Returns the number of bytes written.
95 /// The offset is relative to the start of the file and thus independent
96 /// from the current cursor.
98 /// The current file cursor is not affected by this function.
100 /// When writing beyond the end of the file, the file is appropriately
101 /// extended and the intermediate bytes are initialized with the value 0.
103 /// Note that similar to [`File::write`], it is not an error to return a
105 fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
106 let bufs = &[IoSlice::new(buf)];
107 self.write_vectored_at(bufs, offset)
110 /// Writes a number of bytes starting from a given offset.
112 /// Returns the number of bytes written.
114 /// The offset is relative to the start of the file and thus independent
115 /// from the current cursor.
117 /// The current file cursor is not affected by this function.
119 /// When writing beyond the end of the file, the file is appropriately
120 /// extended and the intermediate bytes are initialized with the value 0.
122 /// Note that similar to [`File::write_vectored`], it is not an error to return a
124 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
126 /// Attempts to write an entire buffer starting from a given offset.
128 /// The offset is relative to the start of the file and thus independent
129 /// from the current cursor.
131 /// The current file cursor is not affected by this function.
133 /// This method will continuously call [`write_at`] until there is no more data
134 /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is
135 /// returned. This method will not return until the entire buffer has been
136 /// successfully written or such an error occurs. The first error that is
137 /// not of [`ErrorKind::Interrupted`] kind generated from this method will be
142 /// This function will return the first error of
143 /// non-[`ErrorKind::Interrupted`] kind that [`write_at`] returns.
145 /// [`write_at`]: FileExt::write_at
146 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
147 fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
148 while !buf.is_empty() {
149 match self.write_at(buf, offset) {
151 return Err(io::Error::new(
152 io::ErrorKind::WriteZero,
153 "failed to write whole buffer",
160 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
161 Err(e) => return Err(e),
167 /// Returns the current position within the file.
169 /// This corresponds to the `fd_tell` syscall and is similar to
170 /// `seek` where you offset 0 bytes from the current position.
171 fn tell(&self) -> io::Result<u64>;
173 /// Adjust the flags associated with this file.
175 /// This corresponds to the `fd_fdstat_set_flags` syscall.
176 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
178 /// Adjust the rights associated with this file.
180 /// This corresponds to the `fd_fdstat_set_rights` syscall.
181 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
183 /// Provide file advisory information on a file descriptor.
185 /// This corresponds to the `fd_advise` syscall.
186 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
188 /// Force the allocation of space in a file.
190 /// This corresponds to the `fd_allocate` syscall.
191 fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
193 /// Create a directory.
195 /// This corresponds to the `path_create_directory` syscall.
196 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
198 /// Read the contents of a symbolic link.
200 /// This corresponds to the `path_readlink` syscall.
201 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
203 /// Return the attributes of a file or directory.
205 /// This corresponds to the `path_filestat_get` syscall.
206 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
210 /// This corresponds to the `path_unlink_file` syscall.
211 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
213 /// Remove a directory.
215 /// This corresponds to the `path_remove_directory` syscall.
216 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
219 // FIXME: bind fd_fdstat_get - need to define a custom return type
220 // FIXME: bind fd_readdir - can't return `ReadDir` since we only have entry name
221 // FIXME: bind fd_filestat_set_times maybe? - on crates.io for unix
222 // FIXME: bind path_filestat_set_times maybe? - on crates.io for unix
223 // FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle
224 // FIXME: bind random_get maybe? - on crates.io for unix
226 impl FileExt for fs::File {
227 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
228 self.as_inner().fd().pread(bufs, offset)
231 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
232 self.as_inner().fd().pwrite(bufs, offset)
235 fn tell(&self) -> io::Result<u64> {
236 self.as_inner().fd().tell()
239 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
240 self.as_inner().fd().set_flags(flags)
243 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
244 self.as_inner().fd().set_rights(rights, inheriting)
247 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
248 self.as_inner().fd().advise(offset, len, advice)
251 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
252 self.as_inner().fd().allocate(offset, len)
255 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
256 self.as_inner().fd().create_directory(osstr2str(dir.as_ref().as_ref())?)
259 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
260 self.as_inner().read_link(path.as_ref())
263 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
264 let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
265 Ok(FromInner::from_inner(m))
268 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
269 self.as_inner().fd().unlink_file(osstr2str(path.as_ref().as_ref())?)
272 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
273 self.as_inner().fd().remove_directory(osstr2str(path.as_ref().as_ref())?)
277 /// WASI-specific extensions to [`fs::OpenOptions`].
278 pub trait OpenOptionsExt {
279 /// Pass custom `dirflags` argument to `path_open`.
281 /// This option configures the `dirflags` argument to the
282 /// `path_open` syscall which `OpenOptions` will eventually call. The
283 /// `dirflags` argument configures how the file is looked up, currently
284 /// primarily affecting whether symlinks are followed or not.
286 /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
287 /// followed. You can call this method with 0 to disable following symlinks
288 fn lookup_flags(&mut self, flags: u32) -> &mut Self;
290 /// Indicates whether `OpenOptions` must open a directory or not.
292 /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
293 /// passed when opening a file. When passed it will require that the opened
294 /// path is a directory.
296 /// This option is by default `false`
297 fn directory(&mut self, dir: bool) -> &mut Self;
299 /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
300 /// field of `path_open`.
302 /// This option is by default `false`
303 fn dsync(&mut self, dsync: bool) -> &mut Self;
305 /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
306 /// field of `path_open`.
308 /// This option is by default `false`
309 fn nonblock(&mut self, nonblock: bool) -> &mut Self;
311 /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
312 /// field of `path_open`.
314 /// This option is by default `false`
315 fn rsync(&mut self, rsync: bool) -> &mut Self;
317 /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
318 /// field of `path_open`.
320 /// This option is by default `false`
321 fn sync(&mut self, sync: bool) -> &mut Self;
323 /// Indicates the value that should be passed in for the `fs_rights_base`
324 /// parameter of `path_open`.
326 /// This option defaults based on the `read` and `write` configuration of
327 /// this `OpenOptions` builder. If this method is called, however, the
328 /// exact mask passed in will be used instead.
329 fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
331 /// Indicates the value that should be passed in for the
332 /// `fs_rights_inheriting` parameter of `path_open`.
334 /// The default for this option is the same value as what will be passed
335 /// for the `fs_rights_base` parameter but if this method is called then
336 /// the specified value will be used instead.
337 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
339 /// Open a file or directory.
341 /// This corresponds to the `path_open` syscall.
342 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
345 impl OpenOptionsExt for OpenOptions {
346 fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
347 self.as_inner_mut().lookup_flags(flags);
351 fn directory(&mut self, dir: bool) -> &mut OpenOptions {
352 self.as_inner_mut().directory(dir);
356 fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
357 self.as_inner_mut().dsync(enabled);
361 fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
362 self.as_inner_mut().nonblock(enabled);
366 fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
367 self.as_inner_mut().rsync(enabled);
371 fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
372 self.as_inner_mut().sync(enabled);
376 fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
377 self.as_inner_mut().fs_rights_base(rights);
381 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
382 self.as_inner_mut().fs_rights_inheriting(rights);
386 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
387 let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
388 Ok(File::from_inner(inner))
392 /// WASI-specific extensions to [`fs::Metadata`].
393 pub trait MetadataExt {
394 /// Returns the `st_dev` field of the internal `filestat_t`
395 fn dev(&self) -> u64;
396 /// Returns the `st_ino` field of the internal `filestat_t`
397 fn ino(&self) -> u64;
398 /// Returns the `st_nlink` field of the internal `filestat_t`
399 fn nlink(&self) -> u64;
400 /// Returns the `st_atim` field of the internal `filestat_t`
401 fn atim(&self) -> u64;
402 /// Returns the `st_mtim` field of the internal `filestat_t`
403 fn mtim(&self) -> u64;
404 /// Returns the `st_ctim` field of the internal `filestat_t`
405 fn ctim(&self) -> u64;
408 impl MetadataExt for fs::Metadata {
409 fn dev(&self) -> u64 {
410 self.as_inner().as_wasi().dev
412 fn ino(&self) -> u64 {
413 self.as_inner().as_wasi().ino
415 fn nlink(&self) -> u64 {
416 self.as_inner().as_wasi().nlink
418 fn atim(&self) -> u64 {
419 self.as_inner().as_wasi().atim
421 fn mtim(&self) -> u64 {
422 self.as_inner().as_wasi().mtim
424 fn ctim(&self) -> u64 {
425 self.as_inner().as_wasi().ctim
429 /// WASI-specific extensions for [`FileType`].
431 /// Adds support for special WASI file types such as block/character devices,
432 /// pipes, and sockets.
433 pub trait FileTypeExt {
434 /// Returns `true` if this file type is a block device.
435 fn is_block_device(&self) -> bool;
436 /// Returns `true` if this file type is a character device.
437 fn is_character_device(&self) -> bool;
438 /// Returns `true` if this file type is a socket datagram.
439 fn is_socket_dgram(&self) -> bool;
440 /// Returns `true` if this file type is a socket stream.
441 fn is_socket_stream(&self) -> bool;
444 impl FileTypeExt for fs::FileType {
445 fn is_block_device(&self) -> bool {
446 self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
448 fn is_character_device(&self) -> bool {
449 self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
451 fn is_socket_dgram(&self) -> bool {
452 self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
454 fn is_socket_stream(&self) -> bool {
455 self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
459 /// WASI-specific extension methods for [`fs::DirEntry`].
460 pub trait DirEntryExt {
461 /// Returns the underlying `d_ino` field of the `dirent_t`
462 fn ino(&self) -> u64;
465 impl DirEntryExt for fs::DirEntry {
466 fn ino(&self) -> u64 {
467 self.as_inner().ino()
471 /// Create a hard link.
473 /// This corresponds to the `path_link` syscall.
474 pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
480 ) -> io::Result<()> {
481 old_fd.as_inner().fd().link(
483 osstr2str(old_path.as_ref().as_ref())?,
484 new_fd.as_inner().fd(),
485 osstr2str(new_path.as_ref().as_ref())?,
489 /// Rename a file or directory.
491 /// This corresponds to the `path_rename` syscall.
492 pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
497 ) -> io::Result<()> {
498 old_fd.as_inner().fd().rename(
499 osstr2str(old_path.as_ref().as_ref())?,
500 new_fd.as_inner().fd(),
501 osstr2str(new_path.as_ref().as_ref())?,
505 /// Create a symbolic link.
507 /// This corresponds to the `path_symlink` syscall.
508 pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
512 ) -> io::Result<()> {
515 .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)