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