std/os/wasi/
fs.rs

1//! WASI-specific extensions to primitives in the [`std::fs`] module.
2//!
3//! [`std::fs`]: crate::fs
4
5#![unstable(feature = "wasi_ext", issue = "71213")]
6
7// Used for `File::read` on intra-doc links
8#[allow(unused_imports)]
9use io::{Read, Write};
10
11use crate::ffi::OsStr;
12use crate::fs::{self, File, Metadata, OpenOptions};
13use crate::io::{self, IoSlice, IoSliceMut};
14use crate::path::{Path, PathBuf};
15use crate::sys_common::{AsInner, AsInnerMut, FromInner};
16
17/// WASI-specific extensions to [`File`].
18pub trait FileExt {
19    /// Reads a number of bytes starting from a given offset.
20    ///
21    /// Returns the number of bytes read.
22    ///
23    /// The offset is relative to the start of the file and thus independent
24    /// from the current cursor.
25    ///
26    /// The current file cursor is not affected by this function.
27    ///
28    /// Note that similar to [`File::read`], it is not an error to return with a
29    /// short read.
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)
33    }
34
35    /// Reads a number of bytes starting from a given offset.
36    ///
37    /// Returns the number of bytes read.
38    ///
39    /// The offset is relative to the start of the file and thus independent
40    /// from the current cursor.
41    ///
42    /// The current file cursor is not affected by this function.
43    ///
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>;
47
48    /// Reads the exact number of byte required to fill `buf` from the given offset.
49    ///
50    /// The offset is relative to the start of the file and thus independent
51    /// from the current cursor.
52    ///
53    /// The current file cursor is not affected by this function.
54    ///
55    /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
56    ///
57    /// [`read_at`]: FileExt::read_at
58    ///
59    /// # Errors
60    ///
61    /// If this function encounters an error of the kind
62    /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation
63    /// will continue.
64    ///
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.
68    ///
69    /// If any other read error is encountered then this function immediately
70    /// returns. The contents of `buf` are unspecified in this case.
71    ///
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) {
79                Ok(0) => break,
80                Ok(n) => {
81                    let tmp = buf;
82                    buf = &mut tmp[n..];
83                    offset += n as u64;
84                }
85                Err(ref e) if e.is_interrupted() => {}
86                Err(e) => return Err(e),
87            }
88        }
89        if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) }
90    }
91
92    /// Writes a number of bytes starting from a given offset.
93    ///
94    /// Returns the number of bytes written.
95    ///
96    /// The offset is relative to the start of the file and thus independent
97    /// from the current cursor.
98    ///
99    /// The current file cursor is not affected by this function.
100    ///
101    /// When writing beyond the end of the file, the file is appropriately
102    /// extended and the intermediate bytes are initialized with the value 0.
103    ///
104    /// Note that similar to [`File::write`], it is not an error to return a
105    /// short write.
106    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
107        let bufs = &[IoSlice::new(buf)];
108        self.write_vectored_at(bufs, offset)
109    }
110
111    /// Writes a number of bytes starting from a given offset.
112    ///
113    /// Returns the number of bytes written.
114    ///
115    /// The offset is relative to the start of the file and thus independent
116    /// from the current cursor.
117    ///
118    /// The current file cursor is not affected by this function.
119    ///
120    /// When writing beyond the end of the file, the file is appropriately
121    /// extended and the intermediate bytes are initialized with the value 0.
122    ///
123    /// Note that similar to [`File::write_vectored`], it is not an error to return a
124    /// short write.
125    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
126
127    /// Attempts to write an entire buffer starting from a given offset.
128    ///
129    /// The offset is relative to the start of the file and thus independent
130    /// from the current cursor.
131    ///
132    /// The current file cursor is not affected by this function.
133    ///
134    /// This method will continuously call [`write_at`] until there is no more data
135    /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is
136    /// returned. This method will not return until the entire buffer has been
137    /// successfully written or such an error occurs. The first error that is
138    /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be
139    /// returned.
140    ///
141    /// # Errors
142    ///
143    /// This function will return the first error of
144    /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns.
145    ///
146    /// [`write_at`]: FileExt::write_at
147    #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
148    fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
149        while !buf.is_empty() {
150            match self.write_at(buf, offset) {
151                Ok(0) => {
152                    return Err(io::Error::WRITE_ALL_EOF);
153                }
154                Ok(n) => {
155                    buf = &buf[n..];
156                    offset += n as u64
157                }
158                Err(ref e) if e.is_interrupted() => {}
159                Err(e) => return Err(e),
160            }
161        }
162        Ok(())
163    }
164
165    /// Returns the current position within the file.
166    ///
167    /// This corresponds to the `fd_tell` syscall and is similar to
168    /// `seek` where you offset 0 bytes from the current position.
169    #[doc(alias = "fd_tell")]
170    fn tell(&self) -> io::Result<u64>;
171
172    /// Adjusts the flags associated with this file.
173    ///
174    /// This corresponds to the `fd_fdstat_set_flags` syscall.
175    #[doc(alias = "fd_fdstat_set_flags")]
176    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
177
178    /// Adjusts the rights associated with this file.
179    ///
180    /// This corresponds to the `fd_fdstat_set_rights` syscall.
181    #[doc(alias = "fd_fdstat_set_rights")]
182    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
183
184    /// Provides file advisory information on a file descriptor.
185    ///
186    /// This corresponds to the `fd_advise` syscall.
187    #[doc(alias = "fd_advise")]
188    fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
189
190    /// Forces the allocation of space in a file.
191    ///
192    /// This corresponds to the `fd_allocate` syscall.
193    #[doc(alias = "fd_allocate")]
194    fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
195
196    /// Creates a directory.
197    ///
198    /// This corresponds to the `path_create_directory` syscall.
199    #[doc(alias = "path_create_directory")]
200    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
201
202    /// Reads the contents of a symbolic link.
203    ///
204    /// This corresponds to the `path_readlink` syscall.
205    #[doc(alias = "path_readlink")]
206    fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
207
208    /// Returns the attributes of a file or directory.
209    ///
210    /// This corresponds to the `path_filestat_get` syscall.
211    #[doc(alias = "path_filestat_get")]
212    fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
213
214    /// Unlinks a file.
215    ///
216    /// This corresponds to the `path_unlink_file` syscall.
217    #[doc(alias = "path_unlink_file")]
218    fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
219
220    /// Removes a directory.
221    ///
222    /// This corresponds to the `path_remove_directory` syscall.
223    #[doc(alias = "path_remove_directory")]
224    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
225}
226
227// FIXME: bind fd_fdstat_get - need to define a custom return type
228// FIXME: bind fd_readdir - can't return `ReadDir` since we only have entry name
229// FIXME: bind fd_filestat_set_times maybe? - on crates.io for unix
230// FIXME: bind path_filestat_set_times maybe? - on crates.io for unix
231// FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle
232// FIXME: bind random_get maybe? - on crates.io for unix
233
234impl FileExt for fs::File {
235    fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
236        self.as_inner().as_inner().pread(bufs, offset)
237    }
238
239    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
240        self.as_inner().as_inner().pwrite(bufs, offset)
241    }
242
243    fn tell(&self) -> io::Result<u64> {
244        self.as_inner().as_inner().tell()
245    }
246
247    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
248        self.as_inner().as_inner().set_flags(flags)
249    }
250
251    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
252        self.as_inner().as_inner().set_rights(rights, inheriting)
253    }
254
255    fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
256        let advice = match advice {
257            a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL,
258            a if a == wasi::ADVICE_SEQUENTIAL.raw() => wasi::ADVICE_SEQUENTIAL,
259            a if a == wasi::ADVICE_RANDOM.raw() => wasi::ADVICE_RANDOM,
260            a if a == wasi::ADVICE_WILLNEED.raw() => wasi::ADVICE_WILLNEED,
261            a if a == wasi::ADVICE_DONTNEED.raw() => wasi::ADVICE_DONTNEED,
262            a if a == wasi::ADVICE_NOREUSE.raw() => wasi::ADVICE_NOREUSE,
263            _ => {
264                return Err(io::const_error!(
265                    io::ErrorKind::InvalidInput,
266                    "invalid parameter 'advice'",
267                ));
268            }
269        };
270
271        self.as_inner().as_inner().advise(offset, len, advice)
272    }
273
274    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
275        self.as_inner().as_inner().allocate(offset, len)
276    }
277
278    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
279        self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?)
280    }
281
282    fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
283        self.as_inner().read_link(path.as_ref())
284    }
285
286    fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
287        let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
288        Ok(FromInner::from_inner(m))
289    }
290
291    fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
292        self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?)
293    }
294
295    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
296        self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?)
297    }
298}
299
300/// WASI-specific extensions to [`fs::OpenOptions`].
301pub trait OpenOptionsExt {
302    /// Pass custom `dirflags` argument to `path_open`.
303    ///
304    /// This option configures the `dirflags` argument to the
305    /// `path_open` syscall which `OpenOptions` will eventually call. The
306    /// `dirflags` argument configures how the file is looked up, currently
307    /// primarily affecting whether symlinks are followed or not.
308    ///
309    /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
310    /// followed. You can call this method with 0 to disable following symlinks
311    fn lookup_flags(&mut self, flags: u32) -> &mut Self;
312
313    /// Indicates whether `OpenOptions` must open a directory or not.
314    ///
315    /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
316    /// passed when opening a file. When passed it will require that the opened
317    /// path is a directory.
318    ///
319    /// This option is by default `false`
320    fn directory(&mut self, dir: bool) -> &mut Self;
321
322    /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
323    /// field of `path_open`.
324    ///
325    /// This option is by default `false`
326    fn dsync(&mut self, dsync: bool) -> &mut Self;
327
328    /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
329    /// field of `path_open`.
330    ///
331    /// This option is by default `false`
332    fn nonblock(&mut self, nonblock: bool) -> &mut Self;
333
334    /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
335    /// field of `path_open`.
336    ///
337    /// This option is by default `false`
338    fn rsync(&mut self, rsync: bool) -> &mut Self;
339
340    /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
341    /// field of `path_open`.
342    ///
343    /// This option is by default `false`
344    fn sync(&mut self, sync: bool) -> &mut Self;
345
346    /// Indicates the value that should be passed in for the `fs_rights_base`
347    /// parameter of `path_open`.
348    ///
349    /// This option defaults based on the `read` and `write` configuration of
350    /// this `OpenOptions` builder. If this method is called, however, the
351    /// exact mask passed in will be used instead.
352    fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
353
354    /// Indicates the value that should be passed in for the
355    /// `fs_rights_inheriting` parameter of `path_open`.
356    ///
357    /// The default for this option is the same value as what will be passed
358    /// for the `fs_rights_base` parameter but if this method is called then
359    /// the specified value will be used instead.
360    fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
361
362    /// Open a file or directory.
363    ///
364    /// This corresponds to the `path_open` syscall.
365    #[doc(alias = "path_open")]
366    fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
367}
368
369impl OpenOptionsExt for OpenOptions {
370    fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
371        self.as_inner_mut().lookup_flags(flags);
372        self
373    }
374
375    fn directory(&mut self, dir: bool) -> &mut OpenOptions {
376        self.as_inner_mut().directory(dir);
377        self
378    }
379
380    fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
381        self.as_inner_mut().dsync(enabled);
382        self
383    }
384
385    fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
386        self.as_inner_mut().nonblock(enabled);
387        self
388    }
389
390    fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
391        self.as_inner_mut().rsync(enabled);
392        self
393    }
394
395    fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
396        self.as_inner_mut().sync(enabled);
397        self
398    }
399
400    fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
401        self.as_inner_mut().fs_rights_base(rights);
402        self
403    }
404
405    fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
406        self.as_inner_mut().fs_rights_inheriting(rights);
407        self
408    }
409
410    fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
411        let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
412        Ok(File::from_inner(inner))
413    }
414}
415
416/// WASI-specific extensions to [`fs::Metadata`].
417pub trait MetadataExt {
418    /// Returns the `st_dev` field of the internal `filestat_t`
419    fn dev(&self) -> u64;
420    /// Returns the `st_ino` field of the internal `filestat_t`
421    fn ino(&self) -> u64;
422    /// Returns the `st_nlink` field of the internal `filestat_t`
423    fn nlink(&self) -> u64;
424    /// Returns the `st_size` field of the internal `filestat_t`
425    fn size(&self) -> u64;
426    /// Returns the `st_atim` field of the internal `filestat_t`
427    fn atim(&self) -> u64;
428    /// Returns the `st_mtim` field of the internal `filestat_t`
429    fn mtim(&self) -> u64;
430    /// Returns the `st_ctim` field of the internal `filestat_t`
431    fn ctim(&self) -> u64;
432}
433
434impl MetadataExt for fs::Metadata {
435    fn dev(&self) -> u64 {
436        self.as_inner().as_wasi().dev
437    }
438    fn ino(&self) -> u64 {
439        self.as_inner().as_wasi().ino
440    }
441    fn nlink(&self) -> u64 {
442        self.as_inner().as_wasi().nlink
443    }
444    fn size(&self) -> u64 {
445        self.as_inner().as_wasi().size
446    }
447    fn atim(&self) -> u64 {
448        self.as_inner().as_wasi().atim
449    }
450    fn mtim(&self) -> u64 {
451        self.as_inner().as_wasi().mtim
452    }
453    fn ctim(&self) -> u64 {
454        self.as_inner().as_wasi().ctim
455    }
456}
457
458/// WASI-specific extensions for [`fs::FileType`].
459///
460/// Adds support for special WASI file types such as block/character devices,
461/// pipes, and sockets.
462pub trait FileTypeExt {
463    /// Returns `true` if this file type is a block device.
464    fn is_block_device(&self) -> bool;
465    /// Returns `true` if this file type is a character device.
466    fn is_char_device(&self) -> bool;
467    /// Returns `true` if this file type is a socket datagram.
468    fn is_socket_dgram(&self) -> bool;
469    /// Returns `true` if this file type is a socket stream.
470    fn is_socket_stream(&self) -> bool;
471    /// Returns `true` if this file type is any type of socket.
472    fn is_socket(&self) -> bool {
473        self.is_socket_stream() || self.is_socket_dgram()
474    }
475}
476
477impl FileTypeExt for fs::FileType {
478    fn is_block_device(&self) -> bool {
479        self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
480    }
481    fn is_char_device(&self) -> bool {
482        self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
483    }
484    fn is_socket_dgram(&self) -> bool {
485        self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
486    }
487    fn is_socket_stream(&self) -> bool {
488        self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
489    }
490}
491
492/// WASI-specific extension methods for [`fs::DirEntry`].
493pub trait DirEntryExt {
494    /// Returns the underlying `d_ino` field of the `dirent_t`
495    fn ino(&self) -> u64;
496}
497
498impl DirEntryExt for fs::DirEntry {
499    fn ino(&self) -> u64 {
500        self.as_inner().ino()
501    }
502}
503
504/// Creates a hard link.
505///
506/// This corresponds to the `path_link` syscall.
507#[doc(alias = "path_link")]
508pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
509    old_fd: &File,
510    old_flags: u32,
511    old_path: P,
512    new_fd: &File,
513    new_path: U,
514) -> io::Result<()> {
515    old_fd.as_inner().as_inner().link(
516        old_flags,
517        osstr2str(old_path.as_ref().as_ref())?,
518        new_fd.as_inner().as_inner(),
519        osstr2str(new_path.as_ref().as_ref())?,
520    )
521}
522
523/// Renames a file or directory.
524///
525/// This corresponds to the `path_rename` syscall.
526#[doc(alias = "path_rename")]
527pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
528    old_fd: &File,
529    old_path: P,
530    new_fd: &File,
531    new_path: U,
532) -> io::Result<()> {
533    old_fd.as_inner().as_inner().rename(
534        osstr2str(old_path.as_ref().as_ref())?,
535        new_fd.as_inner().as_inner(),
536        osstr2str(new_path.as_ref().as_ref())?,
537    )
538}
539
540/// Creates a symbolic link.
541///
542/// This corresponds to the `path_symlink` syscall.
543#[doc(alias = "path_symlink")]
544pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
545    old_path: P,
546    fd: &File,
547    new_path: U,
548) -> io::Result<()> {
549    fd.as_inner()
550        .as_inner()
551        .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)
552}
553
554/// Creates a symbolic link.
555///
556/// This is a convenience API similar to `std::os::unix::fs::symlink` and
557/// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`.
558pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> {
559    crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref())
560}
561
562fn osstr2str(f: &OsStr) -> io::Result<&str> {
563    f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
564}