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