1 //! WASI-specific extensions to primitives in the `std::fs` module.
3 #![unstable(feature = "wasi_ext", issue = "none")]
5 use crate::fs::{self, File, Metadata, OpenOptions};
6 use crate::io::{self, IoSlice, IoSliceMut};
7 use crate::path::{Path, PathBuf};
8 use crate::sys::fs::osstr2str;
9 use crate::sys_common::{AsInner, AsInnerMut, FromInner};
11 /// WASI-specific extensions to [`File`].
13 /// Reads a number of bytes starting from a given offset.
15 /// Returns the number of bytes read.
17 /// The offset is relative to the start of the file and thus independent
18 /// from the current cursor.
20 /// The current file cursor is not affected by this function.
22 /// Note that similar to [`File::read`], it is not an error to return with a
24 fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
25 let bufs = &mut [IoSliceMut::new(buf)];
26 self.read_vectored_at(bufs, offset)
29 /// Reads a number of bytes starting from a given offset.
31 /// Returns the number of bytes read.
33 /// The offset is relative to the start of the file and thus independent
34 /// from the current cursor.
36 /// The current file cursor is not affected by this function.
38 /// Note that similar to [`File::read_vectored`], it is not an error to
39 /// return with a short read.
40 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
42 /// Reads the exact number of byte required to fill `buf` from the given offset.
44 /// The offset is relative to the start of the file and thus independent
45 /// from the current cursor.
47 /// The current file cursor is not affected by this function.
49 /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
51 /// [`read_at`]: FileExt::read_at
55 /// If this function encounters an error of the kind
56 /// [`ErrorKind::Interrupted`] then the error is ignored and the operation
59 /// If this function encounters an "end of file" before completely filling
60 /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`].
61 /// The contents of `buf` are unspecified in this case.
63 /// If any other read error is encountered then this function immediately
64 /// returns. The contents of `buf` are unspecified in this case.
66 /// If this function returns an error, it is unspecified how many bytes it
67 /// has read, but it will never read more than would be necessary to
68 /// completely fill the buffer.
69 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
70 fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
71 while !buf.is_empty() {
72 match self.read_at(buf, offset) {
79 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
80 Err(e) => return Err(e),
84 Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
90 /// Writes a number of bytes starting from a given offset.
92 /// Returns the number of bytes written.
94 /// The offset is relative to the start of the file and thus independent
95 /// from the current cursor.
97 /// The current file cursor is not affected by this function.
99 /// When writing beyond the end of the file, the file is appropriately
100 /// extended and the intermediate bytes are initialized with the value 0.
102 /// Note that similar to [`File::write`], it is not an error to return a
104 fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
105 let bufs = &[IoSlice::new(buf)];
106 self.write_vectored_at(bufs, offset)
109 /// Writes a number of bytes starting from a given offset.
111 /// Returns the number of bytes written.
113 /// The offset is relative to the start of the file and thus independent
114 /// from the current cursor.
116 /// The current file cursor is not affected by this function.
118 /// When writing beyond the end of the file, the file is appropriately
119 /// extended and the intermediate bytes are initialized with the value 0.
121 /// Note that similar to [`File::write_vectored`], it is not an error to return a
123 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
125 /// Attempts to write an entire buffer starting from a given offset.
127 /// The offset is relative to the start of the file and thus independent
128 /// from the current cursor.
130 /// The current file cursor is not affected by this function.
132 /// This method will continuously call [`write_at`] until there is no more data
133 /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is
134 /// returned. This method will not return until the entire buffer has been
135 /// successfully written or such an error occurs. The first error that is
136 /// not of [`ErrorKind::Interrupted`] kind generated from this method will be
141 /// This function will return the first error of
142 /// non-[`ErrorKind::Interrupted`] kind that [`write_at`] returns.
144 /// [`write_at`]: FileExt::write_at
145 #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
146 fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
147 while !buf.is_empty() {
148 match self.write_at(buf, offset) {
150 return Err(io::Error::new(
151 io::ErrorKind::WriteZero,
152 "failed to write whole buffer",
159 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
160 Err(e) => return Err(e),
166 /// Returns the current position within the file.
168 /// This corresponds to the `fd_tell` syscall and is similar to
169 /// `seek` where you offset 0 bytes from the current position.
170 fn tell(&self) -> io::Result<u64>;
172 /// Adjust the flags associated with this file.
174 /// This corresponds to the `fd_fdstat_set_flags` syscall.
175 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
177 /// Adjust the rights associated with this file.
179 /// This corresponds to the `fd_fdstat_set_rights` syscall.
180 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
182 /// Provide file advisory information on a file descriptor.
184 /// This corresponds to the `fd_advise` syscall.
185 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
187 /// Force the allocation of space in a file.
189 /// This corresponds to the `fd_allocate` syscall.
190 fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
192 /// Create a directory.
194 /// This corresponds to the `path_create_directory` syscall.
195 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
197 /// Read the contents of a symbolic link.
199 /// This corresponds to the `path_readlink` syscall.
200 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
202 /// Return the attributes of a file or directory.
204 /// This corresponds to the `path_filestat_get` syscall.
205 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
209 /// This corresponds to the `path_unlink_file` syscall.
210 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
212 /// Remove a directory.
214 /// This corresponds to the `path_remove_directory` syscall.
215 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
218 // FIXME: bind fd_fdstat_get - need to define a custom return type
219 // FIXME: bind fd_readdir - can't return `ReadDir` since we only have entry name
220 // FIXME: bind fd_filestat_set_times maybe? - on crates.io for unix
221 // FIXME: bind path_filestat_set_times maybe? - on crates.io for unix
222 // FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle
223 // FIXME: bind random_get maybe? - on crates.io for unix
225 impl FileExt for fs::File {
226 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
227 self.as_inner().fd().pread(bufs, offset)
230 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
231 self.as_inner().fd().pwrite(bufs, offset)
234 fn tell(&self) -> io::Result<u64> {
235 self.as_inner().fd().tell()
238 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
239 self.as_inner().fd().set_flags(flags)
242 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
243 self.as_inner().fd().set_rights(rights, inheriting)
246 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
247 self.as_inner().fd().advise(offset, len, advice)
250 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
251 self.as_inner().fd().allocate(offset, len)
254 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
255 self.as_inner().fd().create_directory(osstr2str(dir.as_ref().as_ref())?)
258 fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
259 self.as_inner().read_link(path.as_ref())
262 fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
263 let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
264 Ok(FromInner::from_inner(m))
267 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
268 self.as_inner().fd().unlink_file(osstr2str(path.as_ref().as_ref())?)
271 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
272 self.as_inner().fd().remove_directory(osstr2str(path.as_ref().as_ref())?)
276 /// WASI-specific extensions to [`fs::OpenOptions`].
277 pub trait OpenOptionsExt {
278 /// Pass custom `dirflags` argument to `path_open`.
280 /// This option configures the `dirflags` argument to the
281 /// `path_open` syscall which `OpenOptions` will eventually call. The
282 /// `dirflags` argument configures how the file is looked up, currently
283 /// primarily affecting whether symlinks are followed or not.
285 /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
286 /// followed. You can call this method with 0 to disable following symlinks
287 fn lookup_flags(&mut self, flags: u32) -> &mut Self;
289 /// Indicates whether `OpenOptions` must open a directory or not.
291 /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
292 /// passed when opening a file. When passed it will require that the opened
293 /// path is a directory.
295 /// This option is by default `false`
296 fn directory(&mut self, dir: bool) -> &mut Self;
298 /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
299 /// field of `path_open`.
301 /// This option is by default `false`
302 fn dsync(&mut self, dsync: bool) -> &mut Self;
304 /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
305 /// field of `path_open`.
307 /// This option is by default `false`
308 fn nonblock(&mut self, nonblock: bool) -> &mut Self;
310 /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
311 /// field of `path_open`.
313 /// This option is by default `false`
314 fn rsync(&mut self, rsync: bool) -> &mut Self;
316 /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
317 /// field of `path_open`.
319 /// This option is by default `false`
320 fn sync(&mut self, sync: bool) -> &mut Self;
322 /// Indicates the value that should be passed in for the `fs_rights_base`
323 /// parameter of `path_open`.
325 /// This option defaults based on the `read` and `write` configuration of
326 /// this `OpenOptions` builder. If this method is called, however, the
327 /// exact mask passed in will be used instead.
328 fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
330 /// Indicates the value that should be passed in for the
331 /// `fs_rights_inheriting` parameter of `path_open`.
333 /// The default for this option is the same value as what will be passed
334 /// for the `fs_rights_base` parameter but if this method is called then
335 /// the specified value will be used instead.
336 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
338 /// Open a file or directory.
340 /// This corresponds to the `path_open` syscall.
341 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
344 impl OpenOptionsExt for OpenOptions {
345 fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
346 self.as_inner_mut().lookup_flags(flags);
350 fn directory(&mut self, dir: bool) -> &mut OpenOptions {
351 self.as_inner_mut().directory(dir);
355 fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
356 self.as_inner_mut().dsync(enabled);
360 fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
361 self.as_inner_mut().nonblock(enabled);
365 fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
366 self.as_inner_mut().rsync(enabled);
370 fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
371 self.as_inner_mut().sync(enabled);
375 fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
376 self.as_inner_mut().fs_rights_base(rights);
380 fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
381 self.as_inner_mut().fs_rights_inheriting(rights);
385 fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
386 let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
387 Ok(File::from_inner(inner))
391 /// WASI-specific extensions to [`fs::Metadata`].
392 pub trait MetadataExt {
393 /// Returns the `st_dev` field of the internal `filestat_t`
394 fn dev(&self) -> u64;
395 /// Returns the `st_ino` field of the internal `filestat_t`
396 fn ino(&self) -> u64;
397 /// Returns the `st_nlink` field of the internal `filestat_t`
398 fn nlink(&self) -> u64;
399 /// Returns the `st_atim` field of the internal `filestat_t`
400 fn atim(&self) -> u64;
401 /// Returns the `st_mtim` field of the internal `filestat_t`
402 fn mtim(&self) -> u64;
403 /// Returns the `st_ctim` field of the internal `filestat_t`
404 fn ctim(&self) -> u64;
407 impl MetadataExt for fs::Metadata {
408 fn dev(&self) -> u64 {
409 self.as_inner().as_wasi().dev
411 fn ino(&self) -> u64 {
412 self.as_inner().as_wasi().ino
414 fn nlink(&self) -> u64 {
415 self.as_inner().as_wasi().nlink
417 fn atim(&self) -> u64 {
418 self.as_inner().as_wasi().atim
420 fn mtim(&self) -> u64 {
421 self.as_inner().as_wasi().mtim
423 fn ctim(&self) -> u64 {
424 self.as_inner().as_wasi().ctim
428 /// WASI-specific extensions for [`FileType`].
430 /// Adds support for special WASI file types such as block/character devices,
431 /// pipes, and sockets.
432 pub trait FileTypeExt {
433 /// Returns `true` if this file type is a block device.
434 fn is_block_device(&self) -> bool;
435 /// Returns `true` if this file type is a character device.
436 fn is_character_device(&self) -> bool;
437 /// Returns `true` if this file type is a socket datagram.
438 fn is_socket_dgram(&self) -> bool;
439 /// Returns `true` if this file type is a socket stream.
440 fn is_socket_stream(&self) -> bool;
443 impl FileTypeExt for fs::FileType {
444 fn is_block_device(&self) -> bool {
445 self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
447 fn is_character_device(&self) -> bool {
448 self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
450 fn is_socket_dgram(&self) -> bool {
451 self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
453 fn is_socket_stream(&self) -> bool {
454 self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
458 /// WASI-specific extension methods for [`fs::DirEntry`].
459 pub trait DirEntryExt {
460 /// Returns the underlying `d_ino` field of the `dirent_t`
461 fn ino(&self) -> u64;
464 impl DirEntryExt for fs::DirEntry {
465 fn ino(&self) -> u64 {
466 self.as_inner().ino()
470 /// Create a hard link.
472 /// This corresponds to the `path_link` syscall.
473 pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
479 ) -> io::Result<()> {
480 old_fd.as_inner().fd().link(
482 osstr2str(old_path.as_ref().as_ref())?,
483 new_fd.as_inner().fd(),
484 osstr2str(new_path.as_ref().as_ref())?,
488 /// Rename a file or directory.
490 /// This corresponds to the `path_rename` syscall.
491 pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
496 ) -> io::Result<()> {
497 old_fd.as_inner().fd().rename(
498 osstr2str(old_path.as_ref().as_ref())?,
499 new_fd.as_inner().fd(),
500 osstr2str(new_path.as_ref().as_ref())?,
504 /// Create a symbolic link.
506 /// This corresponds to the `path_symlink` syscall.
507 pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
511 ) -> io::Result<()> {
514 .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)