]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/wasi/ext/fs.rs
Auto merge of #75971 - Amjad50:libstd-deny-unsafe_op_in_unsafe_fn, r=Mark-Simulacrum
[rust.git] / library / std / src / sys / wasi / ext / fs.rs
1 //! WASI-specific extensions to primitives in the `std::fs` module.
2
3 #![deny(unsafe_op_in_unsafe_fn)]
4 #![unstable(feature = "wasi_ext", issue = "none")]
5
6 use crate::fs::{self, File, Metadata, OpenOptions};
7 use crate::io::{self, IoSlice, IoSliceMut};
8 use crate::path::{Path, PathBuf};
9 use crate::sys::fs::osstr2str;
10 use crate::sys_common::{AsInner, AsInnerMut, FromInner};
11
12 /// WASI-specific extensions to [`File`].
13 pub trait FileExt {
14     /// Reads a number of bytes starting from a given offset.
15     ///
16     /// Returns the number of bytes read.
17     ///
18     /// The offset is relative to the start of the file and thus independent
19     /// from the current cursor.
20     ///
21     /// The current file cursor is not affected by this function.
22     ///
23     /// Note that similar to [`File::read`], it is not an error to return with a
24     /// short read.
25     fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
26         let bufs = &mut [IoSliceMut::new(buf)];
27         self.read_vectored_at(bufs, offset)
28     }
29
30     /// Reads a number of bytes starting from a given offset.
31     ///
32     /// Returns the number of bytes read.
33     ///
34     /// The offset is relative to the start of the file and thus independent
35     /// from the current cursor.
36     ///
37     /// The current file cursor is not affected by this function.
38     ///
39     /// Note that similar to [`File::read_vectored`], it is not an error to
40     /// return with a short read.
41     fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
42
43     /// Reads the exact number of byte required to fill `buf` from the given offset.
44     ///
45     /// The offset is relative to the start of the file and thus independent
46     /// from the current cursor.
47     ///
48     /// The current file cursor is not affected by this function.
49     ///
50     /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
51     ///
52     /// [`read_at`]: FileExt::read_at
53     ///
54     /// # Errors
55     ///
56     /// If this function encounters an error of the kind
57     /// [`ErrorKind::Interrupted`] then the error is ignored and the operation
58     /// will continue.
59     ///
60     /// If this function encounters an "end of file" before completely filling
61     /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`].
62     /// The contents of `buf` are unspecified in this case.
63     ///
64     /// If any other read error is encountered then this function immediately
65     /// returns. The contents of `buf` are unspecified in this case.
66     ///
67     /// If this function returns an error, it is unspecified how many bytes it
68     /// has read, but it will never read more than would be necessary to
69     /// completely fill the buffer.
70     #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
71     fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
72         while !buf.is_empty() {
73             match self.read_at(buf, offset) {
74                 Ok(0) => break,
75                 Ok(n) => {
76                     let tmp = buf;
77                     buf = &mut tmp[n..];
78                     offset += n as u64;
79                 }
80                 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
81                 Err(e) => return Err(e),
82             }
83         }
84         if !buf.is_empty() {
85             Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
86         } else {
87             Ok(())
88         }
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-[`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 [`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-[`ErrorKind::Interrupted`] kind that [`write_at`] returns.
144     ///
145     /// [`write_at`]: FileExt::write_at
146     #[stable(feature = "rw_exact_all_at", since = "1.33.0")]
147     fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
148         while !buf.is_empty() {
149             match self.write_at(buf, offset) {
150                 Ok(0) => {
151                     return Err(io::Error::new(
152                         io::ErrorKind::WriteZero,
153                         "failed to write whole buffer",
154                     ));
155                 }
156                 Ok(n) => {
157                     buf = &buf[n..];
158                     offset += n as u64
159                 }
160                 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
161                 Err(e) => return Err(e),
162             }
163         }
164         Ok(())
165     }
166
167     /// Returns the current position within the file.
168     ///
169     /// This corresponds to the `fd_tell` syscall and is similar to
170     /// `seek` where you offset 0 bytes from the current position.
171     fn tell(&self) -> io::Result<u64>;
172
173     /// Adjust the flags associated with this file.
174     ///
175     /// This corresponds to the `fd_fdstat_set_flags` syscall.
176     fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
177
178     /// Adjust the rights associated with this file.
179     ///
180     /// This corresponds to the `fd_fdstat_set_rights` syscall.
181     fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
182
183     /// Provide file advisory information on a file descriptor.
184     ///
185     /// This corresponds to the `fd_advise` syscall.
186     fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
187
188     /// Force the allocation of space in a file.
189     ///
190     /// This corresponds to the `fd_allocate` syscall.
191     fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
192
193     /// Create a directory.
194     ///
195     /// This corresponds to the `path_create_directory` syscall.
196     fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
197
198     /// Read the contents of a symbolic link.
199     ///
200     /// This corresponds to the `path_readlink` syscall.
201     fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
202
203     /// Return the attributes of a file or directory.
204     ///
205     /// This corresponds to the `path_filestat_get` syscall.
206     fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
207
208     /// Unlink a file.
209     ///
210     /// This corresponds to the `path_unlink_file` syscall.
211     fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
212
213     /// Remove a directory.
214     ///
215     /// This corresponds to the `path_remove_directory` syscall.
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
226 impl FileExt for fs::File {
227     fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
228         self.as_inner().fd().pread(bufs, offset)
229     }
230
231     fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
232         self.as_inner().fd().pwrite(bufs, offset)
233     }
234
235     fn tell(&self) -> io::Result<u64> {
236         self.as_inner().fd().tell()
237     }
238
239     fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
240         self.as_inner().fd().set_flags(flags)
241     }
242
243     fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
244         self.as_inner().fd().set_rights(rights, inheriting)
245     }
246
247     fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
248         self.as_inner().fd().advise(offset, len, advice)
249     }
250
251     fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
252         self.as_inner().fd().allocate(offset, len)
253     }
254
255     fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
256         self.as_inner().fd().create_directory(osstr2str(dir.as_ref().as_ref())?)
257     }
258
259     fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
260         self.as_inner().read_link(path.as_ref())
261     }
262
263     fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
264         let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
265         Ok(FromInner::from_inner(m))
266     }
267
268     fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
269         self.as_inner().fd().unlink_file(osstr2str(path.as_ref().as_ref())?)
270     }
271
272     fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
273         self.as_inner().fd().remove_directory(osstr2str(path.as_ref().as_ref())?)
274     }
275 }
276
277 /// WASI-specific extensions to [`fs::OpenOptions`].
278 pub trait OpenOptionsExt {
279     /// Pass custom `dirflags` argument to `path_open`.
280     ///
281     /// This option configures the `dirflags` argument to the
282     /// `path_open` syscall which `OpenOptions` will eventually call. The
283     /// `dirflags` argument configures how the file is looked up, currently
284     /// primarily affecting whether symlinks are followed or not.
285     ///
286     /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
287     /// followed. You can call this method with 0 to disable following symlinks
288     fn lookup_flags(&mut self, flags: u32) -> &mut Self;
289
290     /// Indicates whether `OpenOptions` must open a directory or not.
291     ///
292     /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
293     /// passed when opening a file. When passed it will require that the opened
294     /// path is a directory.
295     ///
296     /// This option is by default `false`
297     fn directory(&mut self, dir: bool) -> &mut Self;
298
299     /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
300     /// field of `path_open`.
301     ///
302     /// This option is by default `false`
303     fn dsync(&mut self, dsync: bool) -> &mut Self;
304
305     /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
306     /// field of `path_open`.
307     ///
308     /// This option is by default `false`
309     fn nonblock(&mut self, nonblock: bool) -> &mut Self;
310
311     /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
312     /// field of `path_open`.
313     ///
314     /// This option is by default `false`
315     fn rsync(&mut self, rsync: bool) -> &mut Self;
316
317     /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
318     /// field of `path_open`.
319     ///
320     /// This option is by default `false`
321     fn sync(&mut self, sync: bool) -> &mut Self;
322
323     /// Indicates the value that should be passed in for the `fs_rights_base`
324     /// parameter of `path_open`.
325     ///
326     /// This option defaults based on the `read` and `write` configuration of
327     /// this `OpenOptions` builder. If this method is called, however, the
328     /// exact mask passed in will be used instead.
329     fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
330
331     /// Indicates the value that should be passed in for the
332     /// `fs_rights_inheriting` parameter of `path_open`.
333     ///
334     /// The default for this option is the same value as what will be passed
335     /// for the `fs_rights_base` parameter but if this method is called then
336     /// the specified value will be used instead.
337     fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
338
339     /// Open a file or directory.
340     ///
341     /// This corresponds to the `path_open` syscall.
342     fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
343 }
344
345 impl OpenOptionsExt for OpenOptions {
346     fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
347         self.as_inner_mut().lookup_flags(flags);
348         self
349     }
350
351     fn directory(&mut self, dir: bool) -> &mut OpenOptions {
352         self.as_inner_mut().directory(dir);
353         self
354     }
355
356     fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
357         self.as_inner_mut().dsync(enabled);
358         self
359     }
360
361     fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
362         self.as_inner_mut().nonblock(enabled);
363         self
364     }
365
366     fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
367         self.as_inner_mut().rsync(enabled);
368         self
369     }
370
371     fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
372         self.as_inner_mut().sync(enabled);
373         self
374     }
375
376     fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
377         self.as_inner_mut().fs_rights_base(rights);
378         self
379     }
380
381     fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
382         self.as_inner_mut().fs_rights_inheriting(rights);
383         self
384     }
385
386     fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
387         let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
388         Ok(File::from_inner(inner))
389     }
390 }
391
392 /// WASI-specific extensions to [`fs::Metadata`].
393 pub trait MetadataExt {
394     /// Returns the `st_dev` field of the internal `filestat_t`
395     fn dev(&self) -> u64;
396     /// Returns the `st_ino` field of the internal `filestat_t`
397     fn ino(&self) -> u64;
398     /// Returns the `st_nlink` field of the internal `filestat_t`
399     fn nlink(&self) -> u64;
400     /// Returns the `st_atim` field of the internal `filestat_t`
401     fn atim(&self) -> u64;
402     /// Returns the `st_mtim` field of the internal `filestat_t`
403     fn mtim(&self) -> u64;
404     /// Returns the `st_ctim` field of the internal `filestat_t`
405     fn ctim(&self) -> u64;
406 }
407
408 impl MetadataExt for fs::Metadata {
409     fn dev(&self) -> u64 {
410         self.as_inner().as_wasi().dev
411     }
412     fn ino(&self) -> u64 {
413         self.as_inner().as_wasi().ino
414     }
415     fn nlink(&self) -> u64 {
416         self.as_inner().as_wasi().nlink
417     }
418     fn atim(&self) -> u64 {
419         self.as_inner().as_wasi().atim
420     }
421     fn mtim(&self) -> u64 {
422         self.as_inner().as_wasi().mtim
423     }
424     fn ctim(&self) -> u64 {
425         self.as_inner().as_wasi().ctim
426     }
427 }
428
429 /// WASI-specific extensions for [`FileType`].
430 ///
431 /// Adds support for special WASI file types such as block/character devices,
432 /// pipes, and sockets.
433 pub trait FileTypeExt {
434     /// Returns `true` if this file type is a block device.
435     fn is_block_device(&self) -> bool;
436     /// Returns `true` if this file type is a character device.
437     fn is_character_device(&self) -> bool;
438     /// Returns `true` if this file type is a socket datagram.
439     fn is_socket_dgram(&self) -> bool;
440     /// Returns `true` if this file type is a socket stream.
441     fn is_socket_stream(&self) -> bool;
442 }
443
444 impl FileTypeExt for fs::FileType {
445     fn is_block_device(&self) -> bool {
446         self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE
447     }
448     fn is_character_device(&self) -> bool {
449         self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE
450     }
451     fn is_socket_dgram(&self) -> bool {
452         self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM
453     }
454     fn is_socket_stream(&self) -> bool {
455         self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM
456     }
457 }
458
459 /// WASI-specific extension methods for [`fs::DirEntry`].
460 pub trait DirEntryExt {
461     /// Returns the underlying `d_ino` field of the `dirent_t`
462     fn ino(&self) -> u64;
463 }
464
465 impl DirEntryExt for fs::DirEntry {
466     fn ino(&self) -> u64 {
467         self.as_inner().ino()
468     }
469 }
470
471 /// Create a hard link.
472 ///
473 /// This corresponds to the `path_link` syscall.
474 pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
475     old_fd: &File,
476     old_flags: u32,
477     old_path: P,
478     new_fd: &File,
479     new_path: U,
480 ) -> io::Result<()> {
481     old_fd.as_inner().fd().link(
482         old_flags,
483         osstr2str(old_path.as_ref().as_ref())?,
484         new_fd.as_inner().fd(),
485         osstr2str(new_path.as_ref().as_ref())?,
486     )
487 }
488
489 /// Rename a file or directory.
490 ///
491 /// This corresponds to the `path_rename` syscall.
492 pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
493     old_fd: &File,
494     old_path: P,
495     new_fd: &File,
496     new_path: U,
497 ) -> io::Result<()> {
498     old_fd.as_inner().fd().rename(
499         osstr2str(old_path.as_ref().as_ref())?,
500         new_fd.as_inner().fd(),
501         osstr2str(new_path.as_ref().as_ref())?,
502     )
503 }
504
505 /// Create a symbolic link.
506 ///
507 /// This corresponds to the `path_symlink` syscall.
508 pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
509     old_path: P,
510     fd: &File,
511     new_path: U,
512 ) -> io::Result<()> {
513     fd.as_inner()
514         .fd()
515         .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)
516 }