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