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

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy