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