std/os/wasi/
fs.rs

1//! WASIp1-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
11#[cfg(target_env = "p1")]
12use crate::ffi::OsStr;
13use crate::fs::{self, File, OpenOptions};
14use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
15#[cfg(target_env = "p1")]
16use crate::os::fd::AsRawFd;
17use crate::path::Path;
18#[cfg(target_env = "p1")]
19use crate::sys::err2io;
20use crate::sys_common::{AsInner, AsInnerMut};
21
22/// WASI-specific extensions to [`File`].
23pub trait FileExt {
24    /// Reads a number of bytes starting from a given offset.
25    ///
26    /// Returns the number of bytes read.
27    ///
28    /// The offset is relative to the start of the file and thus independent
29    /// from the current cursor.
30    ///
31    /// The current file cursor is not affected by this function.
32    ///
33    /// Note that similar to [`File::read`], it is not an error to return with a
34    /// short read.
35    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
36
37    /// Reads a number of bytes starting from a given offset.
38    ///
39    /// Returns the number of bytes read.
40    ///
41    /// The offset is relative to the start of the file and thus independent
42    /// from the current cursor.
43    ///
44    /// The current file cursor is not affected by this function.
45    ///
46    /// Note that similar to [`File::read_vectored`], it is not an error to
47    /// return with a short read.
48    fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
49
50    /// Reads some bytes starting from a given offset into the buffer.
51    ///
52    /// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a
53    /// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new
54    /// data will be appended to any existing contents of `buf`.
55    fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()>;
56
57    /// Reads the exact number of byte required to fill `buf` from the given offset.
58    ///
59    /// The offset is relative to the start of the file and thus independent
60    /// from the current cursor.
61    ///
62    /// The current file cursor is not affected by this function.
63    ///
64    /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
65    ///
66    /// [`read_at`]: FileExt::read_at
67    ///
68    /// # Errors
69    ///
70    /// If this function encounters an error of the kind
71    /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation
72    /// will continue.
73    ///
74    /// If this function encounters an "end of file" before completely filling
75    /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`].
76    /// The contents of `buf` are unspecified in this case.
77    ///
78    /// If any other read error is encountered then this function immediately
79    /// returns. The contents of `buf` are unspecified in this case.
80    ///
81    /// If this function returns an error, it is unspecified how many bytes it
82    /// has read, but it will never read more than would be necessary to
83    /// completely fill the buffer.
84    fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
85        while !buf.is_empty() {
86            match self.read_at(buf, offset) {
87                Ok(0) => break,
88                Ok(n) => {
89                    let tmp = buf;
90                    buf = &mut tmp[n..];
91                    offset += n as u64;
92                }
93                Err(ref e) if e.is_interrupted() => {}
94                Err(e) => return Err(e),
95            }
96        }
97        if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) }
98    }
99
100    /// Writes a number of bytes starting from a given offset.
101    ///
102    /// Returns the number of bytes written.
103    ///
104    /// The offset is relative to the start of the file and thus independent
105    /// from the current cursor.
106    ///
107    /// The current file cursor is not affected by this function.
108    ///
109    /// When writing beyond the end of the file, the file is appropriately
110    /// extended and the intermediate bytes are initialized with the value 0.
111    ///
112    /// Note that similar to [`File::write`], it is not an error to return a
113    /// short write.
114    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
115
116    /// Writes a number of bytes starting from a given offset.
117    ///
118    /// Returns the number of bytes written.
119    ///
120    /// The offset is relative to the start of the file and thus independent
121    /// from the current cursor.
122    ///
123    /// The current file cursor is not affected by this function.
124    ///
125    /// When writing beyond the end of the file, the file is appropriately
126    /// extended and the intermediate bytes are initialized with the value 0.
127    ///
128    /// Note that similar to [`File::write_vectored`], it is not an error to return a
129    /// short write.
130    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
131
132    /// Attempts to write an entire buffer starting from a given offset.
133    ///
134    /// The offset is relative to the start of the file and thus independent
135    /// from the current cursor.
136    ///
137    /// The current file cursor is not affected by this function.
138    ///
139    /// This method will continuously call [`write_at`] until there is no more data
140    /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is
141    /// returned. This method will not return until the entire buffer has been
142    /// successfully written or such an error occurs. The first error that is
143    /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be
144    /// returned.
145    ///
146    /// # Errors
147    ///
148    /// This function will return the first error of
149    /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns.
150    ///
151    /// [`write_at`]: FileExt::write_at
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) {
155                Ok(0) => {
156                    return Err(io::Error::WRITE_ALL_EOF);
157                }
158                Ok(n) => {
159                    buf = &buf[n..];
160                    offset += n as u64
161                }
162                Err(ref e) if e.is_interrupted() => {}
163                Err(e) => return Err(e),
164            }
165        }
166        Ok(())
167    }
168
169    /// Adjusts the flags associated with this file.
170    ///
171    /// This corresponds to the `fd_fdstat_set_flags` syscall.
172    #[doc(alias = "fd_fdstat_set_flags")]
173    #[cfg(target_env = "p1")]
174    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
175
176    /// Adjusts the rights associated with this file.
177    ///
178    /// This corresponds to the `fd_fdstat_set_rights` syscall.
179    #[doc(alias = "fd_fdstat_set_rights")]
180    #[cfg(target_env = "p1")]
181    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
182
183    /// Provides file advisory information on a file descriptor.
184    ///
185    /// This corresponds to the `fd_advise` syscall.
186    #[doc(alias = "fd_advise")]
187    #[cfg(target_env = "p1")]
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    #[cfg(target_env = "p1")]
195    fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
196
197    /// Creates a directory.
198    ///
199    /// This corresponds to the `path_create_directory` syscall.
200    #[doc(alias = "path_create_directory")]
201    #[cfg(target_env = "p1")]
202    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
203
204    /// Unlinks a file.
205    ///
206    /// This corresponds to the `path_unlink_file` syscall.
207    #[doc(alias = "path_unlink_file")]
208    #[cfg(target_env = "p1")]
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    #[cfg(target_env = "p1")]
216    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
217}
218
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
225
226impl FileExt for File {
227    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
228        self.as_inner().read_at(buf, offset)
229    }
230
231    fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
232        self.as_inner().read_buf_at(buf, offset)
233    }
234
235    fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
236        self.as_inner().read_vectored_at(bufs, offset)
237    }
238
239    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
240        self.as_inner().write_at(buf, offset)
241    }
242
243    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
244        self.as_inner().write_vectored_at(bufs, offset)
245    }
246
247    #[cfg(target_env = "p1")]
248    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
249        unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) }
250    }
251
252    #[cfg(target_env = "p1")]
253    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
254        unsafe {
255            wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, rights, inheriting)
256                .map_err(err2io)
257        }
258    }
259
260    #[cfg(target_env = "p1")]
261    fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
262        let advice = match advice {
263            a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL,
264            a if a == wasi::ADVICE_SEQUENTIAL.raw() => wasi::ADVICE_SEQUENTIAL,
265            a if a == wasi::ADVICE_RANDOM.raw() => wasi::ADVICE_RANDOM,
266            a if a == wasi::ADVICE_WILLNEED.raw() => wasi::ADVICE_WILLNEED,
267            a if a == wasi::ADVICE_DONTNEED.raw() => wasi::ADVICE_DONTNEED,
268            a if a == wasi::ADVICE_NOREUSE.raw() => wasi::ADVICE_NOREUSE,
269            _ => {
270                return Err(io::const_error!(
271                    io::ErrorKind::InvalidInput,
272                    "invalid parameter 'advice'",
273                ));
274            }
275        };
276
277        unsafe {
278            wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io)
279        }
280    }
281
282    #[cfg(target_env = "p1")]
283    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
284        unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) }
285    }
286
287    #[cfg(target_env = "p1")]
288    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
289        let path = osstr2str(dir.as_ref().as_ref())?;
290        unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
291    }
292
293    #[cfg(target_env = "p1")]
294    fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
295        let path = osstr2str(path.as_ref().as_ref())?;
296        unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
297    }
298
299    #[cfg(target_env = "p1")]
300    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
301        let path = osstr2str(path.as_ref().as_ref())?;
302        unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
303    }
304}
305
306/// WASI-specific extensions to [`OpenOptions`].
307pub trait OpenOptionsExt {
308    /// Pass custom flags to the `flags` argument of `open`.
309    fn custom_flags(&mut self, flags: i32) -> &mut Self;
310}
311
312impl OpenOptionsExt for OpenOptions {
313    fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
314        self.as_inner_mut().custom_flags(flags);
315        self
316    }
317}
318
319/// WASI-specific extensions to [`fs::Metadata`].
320pub trait MetadataExt {
321    /// Returns the `st_dev` field of the internal `filestat_t`
322    fn dev(&self) -> u64;
323    /// Returns the `st_ino` field of the internal `filestat_t`
324    fn ino(&self) -> u64;
325    /// Returns the `st_nlink` field of the internal `filestat_t`
326    fn nlink(&self) -> u64;
327}
328
329impl MetadataExt for fs::Metadata {
330    fn dev(&self) -> u64 {
331        self.as_inner().as_inner().st_dev
332    }
333    fn ino(&self) -> u64 {
334        self.as_inner().as_inner().st_ino
335    }
336    fn nlink(&self) -> u64 {
337        self.as_inner().as_inner().st_nlink
338    }
339}
340
341/// WASI-specific extensions for [`fs::FileType`].
342///
343/// Adds support for special WASI file types such as block/character devices,
344/// pipes, and sockets.
345pub trait FileTypeExt {
346    /// Returns `true` if this file type is a block device.
347    fn is_block_device(&self) -> bool;
348    /// Returns `true` if this file type is a character device.
349    fn is_char_device(&self) -> bool;
350    /// Returns `true` if this file type is any type of socket.
351    fn is_socket(&self) -> bool;
352}
353
354impl FileTypeExt for fs::FileType {
355    fn is_block_device(&self) -> bool {
356        self.as_inner().is(libc::S_IFBLK)
357    }
358    fn is_char_device(&self) -> bool {
359        self.as_inner().is(libc::S_IFCHR)
360    }
361    fn is_socket(&self) -> bool {
362        self.as_inner().is(libc::S_IFSOCK)
363    }
364}
365
366/// WASI-specific extension methods for [`fs::DirEntry`].
367pub trait DirEntryExt {
368    /// Returns the underlying `d_ino` field of the `dirent_t`
369    fn ino(&self) -> u64;
370}
371
372impl DirEntryExt for fs::DirEntry {
373    fn ino(&self) -> u64 {
374        self.as_inner().ino()
375    }
376}
377
378/// Creates a hard link.
379///
380/// This corresponds to the `path_link` syscall.
381#[doc(alias = "path_link")]
382#[cfg(target_env = "p1")]
383pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
384    old_fd: &File,
385    old_flags: u32,
386    old_path: P,
387    new_fd: &File,
388    new_path: U,
389) -> io::Result<()> {
390    unsafe {
391        wasi::path_link(
392            old_fd.as_raw_fd() as wasi::Fd,
393            old_flags,
394            osstr2str(old_path.as_ref().as_ref())?,
395            new_fd.as_raw_fd() as wasi::Fd,
396            osstr2str(new_path.as_ref().as_ref())?,
397        )
398        .map_err(err2io)
399    }
400}
401
402/// Renames a file or directory.
403///
404/// This corresponds to the `path_rename` syscall.
405#[doc(alias = "path_rename")]
406#[cfg(target_env = "p1")]
407pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
408    old_fd: &File,
409    old_path: P,
410    new_fd: &File,
411    new_path: U,
412) -> io::Result<()> {
413    unsafe {
414        wasi::path_rename(
415            old_fd.as_raw_fd() as wasi::Fd,
416            osstr2str(old_path.as_ref().as_ref())?,
417            new_fd.as_raw_fd() as wasi::Fd,
418            osstr2str(new_path.as_ref().as_ref())?,
419        )
420        .map_err(err2io)
421    }
422}
423
424/// Creates a symbolic link.
425///
426/// This corresponds to the `path_symlink` syscall.
427#[doc(alias = "path_symlink")]
428#[cfg(target_env = "p1")]
429pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
430    old_path: P,
431    fd: &File,
432    new_path: U,
433) -> io::Result<()> {
434    unsafe {
435        wasi::path_symlink(
436            osstr2str(old_path.as_ref().as_ref())?,
437            fd.as_raw_fd() as wasi::Fd,
438            osstr2str(new_path.as_ref().as_ref())?,
439        )
440        .map_err(err2io)
441    }
442}
443
444/// Creates a symbolic link.
445///
446/// This is a convenience API similar to `std::os::unix::fs::symlink` and
447/// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`.
448pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> {
449    crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref())
450}
451
452#[cfg(target_env = "p1")]
453fn osstr2str(f: &OsStr) -> io::Result<&str> {
454    f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
455}