std/os/wasi/fs.rs
1//! WASIp1-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
11#[cfg(target_env = "p1")]
12use crate::ffi::OsStr;
13use crate::fs::{self, File, OpenOptions};
14use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
15#[cfg(target_env = "p1")]
16use crate::os::fd::AsRawFd;
17use crate::path::Path;
18#[cfg(target_env = "p1")]
19use crate::sys::err2io;
20use crate::sys_common::{AsInner, AsInnerMut};
21
22/// WASI-specific extensions to [`File`].
23pub trait FileExt {
24 /// Reads a number of bytes starting from a given offset.
25 ///
26 /// Returns the number of bytes read.
27 ///
28 /// The offset is relative to the start of the file and thus independent
29 /// from the current cursor.
30 ///
31 /// The current file cursor is not affected by this function.
32 ///
33 /// Note that similar to [`File::read`], it is not an error to return with a
34 /// short read.
35 fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
36
37 /// Reads a number of bytes starting from a given offset.
38 ///
39 /// Returns the number of bytes read.
40 ///
41 /// The offset is relative to the start of the file and thus independent
42 /// from the current cursor.
43 ///
44 /// The current file cursor is not affected by this function.
45 ///
46 /// Note that similar to [`File::read_vectored`], it is not an error to
47 /// return with a short read.
48 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize>;
49
50 /// Reads some bytes starting from a given offset into the buffer.
51 ///
52 /// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a
53 /// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new
54 /// data will be appended to any existing contents of `buf`.
55 fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()>;
56
57 /// Reads the exact number of byte required to fill `buf` from the given offset.
58 ///
59 /// The offset is relative to the start of the file and thus independent
60 /// from the current cursor.
61 ///
62 /// The current file cursor is not affected by this function.
63 ///
64 /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`.
65 ///
66 /// [`read_at`]: FileExt::read_at
67 ///
68 /// # Errors
69 ///
70 /// If this function encounters an error of the kind
71 /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation
72 /// will continue.
73 ///
74 /// If this function encounters an "end of file" before completely filling
75 /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`].
76 /// The contents of `buf` are unspecified in this case.
77 ///
78 /// If any other read error is encountered then this function immediately
79 /// returns. The contents of `buf` are unspecified in this case.
80 ///
81 /// If this function returns an error, it is unspecified how many bytes it
82 /// has read, but it will never read more than would be necessary to
83 /// completely fill the buffer.
84 fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
85 while !buf.is_empty() {
86 match self.read_at(buf, offset) {
87 Ok(0) => break,
88 Ok(n) => {
89 let tmp = buf;
90 buf = &mut tmp[n..];
91 offset += n as u64;
92 }
93 Err(ref e) if e.is_interrupted() => {}
94 Err(e) => return Err(e),
95 }
96 }
97 if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) }
98 }
99
100 /// Writes a number of bytes starting from a given offset.
101 ///
102 /// Returns the number of bytes written.
103 ///
104 /// The offset is relative to the start of the file and thus independent
105 /// from the current cursor.
106 ///
107 /// The current file cursor is not affected by this function.
108 ///
109 /// When writing beyond the end of the file, the file is appropriately
110 /// extended and the intermediate bytes are initialized with the value 0.
111 ///
112 /// Note that similar to [`File::write`], it is not an error to return a
113 /// short write.
114 fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
115
116 /// Writes a number of bytes starting from a given offset.
117 ///
118 /// Returns the number of bytes written.
119 ///
120 /// The offset is relative to the start of the file and thus independent
121 /// from the current cursor.
122 ///
123 /// The current file cursor is not affected by this function.
124 ///
125 /// When writing beyond the end of the file, the file is appropriately
126 /// extended and the intermediate bytes are initialized with the value 0.
127 ///
128 /// Note that similar to [`File::write_vectored`], it is not an error to return a
129 /// short write.
130 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize>;
131
132 /// Attempts to write an entire buffer starting from a given offset.
133 ///
134 /// The offset is relative to the start of the file and thus independent
135 /// from the current cursor.
136 ///
137 /// The current file cursor is not affected by this function.
138 ///
139 /// This method will continuously call [`write_at`] until there is no more data
140 /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is
141 /// returned. This method will not return until the entire buffer has been
142 /// successfully written or such an error occurs. The first error that is
143 /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be
144 /// returned.
145 ///
146 /// # Errors
147 ///
148 /// This function will return the first error of
149 /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns.
150 ///
151 /// [`write_at`]: FileExt::write_at
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::Error::WRITE_ALL_EOF);
157 }
158 Ok(n) => {
159 buf = &buf[n..];
160 offset += n as u64
161 }
162 Err(ref e) if e.is_interrupted() => {}
163 Err(e) => return Err(e),
164 }
165 }
166 Ok(())
167 }
168
169 /// Adjusts the flags associated with this file.
170 ///
171 /// This corresponds to the `fd_fdstat_set_flags` syscall.
172 #[doc(alias = "fd_fdstat_set_flags")]
173 #[cfg(target_env = "p1")]
174 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
175
176 /// Adjusts the rights associated with this file.
177 ///
178 /// This corresponds to the `fd_fdstat_set_rights` syscall.
179 #[doc(alias = "fd_fdstat_set_rights")]
180 #[cfg(target_env = "p1")]
181 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
182
183 /// Provides file advisory information on a file descriptor.
184 ///
185 /// This corresponds to the `fd_advise` syscall.
186 #[doc(alias = "fd_advise")]
187 #[cfg(target_env = "p1")]
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 #[cfg(target_env = "p1")]
195 fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
196
197 /// Creates a directory.
198 ///
199 /// This corresponds to the `path_create_directory` syscall.
200 #[doc(alias = "path_create_directory")]
201 #[cfg(target_env = "p1")]
202 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
203
204 /// Unlinks a file.
205 ///
206 /// This corresponds to the `path_unlink_file` syscall.
207 #[doc(alias = "path_unlink_file")]
208 #[cfg(target_env = "p1")]
209 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
210
211 /// Removes a directory.
212 ///
213 /// This corresponds to the `path_remove_directory` syscall.
214 #[doc(alias = "path_remove_directory")]
215 #[cfg(target_env = "p1")]
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
226impl FileExt for File {
227 fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
228 self.as_inner().read_at(buf, offset)
229 }
230
231 fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
232 self.as_inner().read_buf_at(buf, offset)
233 }
234
235 fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
236 self.as_inner().read_vectored_at(bufs, offset)
237 }
238
239 fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
240 self.as_inner().write_at(buf, offset)
241 }
242
243 fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
244 self.as_inner().write_vectored_at(bufs, offset)
245 }
246
247 #[cfg(target_env = "p1")]
248 fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
249 unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) }
250 }
251
252 #[cfg(target_env = "p1")]
253 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
254 unsafe {
255 wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, rights, inheriting)
256 .map_err(err2io)
257 }
258 }
259
260 #[cfg(target_env = "p1")]
261 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
262 let advice = match advice {
263 a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL,
264 a if a == wasi::ADVICE_SEQUENTIAL.raw() => wasi::ADVICE_SEQUENTIAL,
265 a if a == wasi::ADVICE_RANDOM.raw() => wasi::ADVICE_RANDOM,
266 a if a == wasi::ADVICE_WILLNEED.raw() => wasi::ADVICE_WILLNEED,
267 a if a == wasi::ADVICE_DONTNEED.raw() => wasi::ADVICE_DONTNEED,
268 a if a == wasi::ADVICE_NOREUSE.raw() => wasi::ADVICE_NOREUSE,
269 _ => {
270 return Err(io::const_error!(
271 io::ErrorKind::InvalidInput,
272 "invalid parameter 'advice'",
273 ));
274 }
275 };
276
277 unsafe {
278 wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io)
279 }
280 }
281
282 #[cfg(target_env = "p1")]
283 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
284 unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) }
285 }
286
287 #[cfg(target_env = "p1")]
288 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
289 let path = osstr2str(dir.as_ref().as_ref())?;
290 unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
291 }
292
293 #[cfg(target_env = "p1")]
294 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
295 let path = osstr2str(path.as_ref().as_ref())?;
296 unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
297 }
298
299 #[cfg(target_env = "p1")]
300 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
301 let path = osstr2str(path.as_ref().as_ref())?;
302 unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
303 }
304}
305
306/// WASI-specific extensions to [`OpenOptions`].
307pub trait OpenOptionsExt {
308 /// Pass custom flags to the `flags` argument of `open`.
309 fn custom_flags(&mut self, flags: i32) -> &mut Self;
310}
311
312impl OpenOptionsExt for OpenOptions {
313 fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
314 self.as_inner_mut().custom_flags(flags);
315 self
316 }
317}
318
319/// WASI-specific extensions to [`fs::Metadata`].
320pub trait MetadataExt {
321 /// Returns the `st_dev` field of the internal `filestat_t`
322 fn dev(&self) -> u64;
323 /// Returns the `st_ino` field of the internal `filestat_t`
324 fn ino(&self) -> u64;
325 /// Returns the `st_nlink` field of the internal `filestat_t`
326 fn nlink(&self) -> u64;
327}
328
329impl MetadataExt for fs::Metadata {
330 fn dev(&self) -> u64 {
331 self.as_inner().as_inner().st_dev
332 }
333 fn ino(&self) -> u64 {
334 self.as_inner().as_inner().st_ino
335 }
336 fn nlink(&self) -> u64 {
337 self.as_inner().as_inner().st_nlink
338 }
339}
340
341/// WASI-specific extensions for [`fs::FileType`].
342///
343/// Adds support for special WASI file types such as block/character devices,
344/// pipes, and sockets.
345pub trait FileTypeExt {
346 /// Returns `true` if this file type is a block device.
347 fn is_block_device(&self) -> bool;
348 /// Returns `true` if this file type is a character device.
349 fn is_char_device(&self) -> bool;
350 /// Returns `true` if this file type is any type of socket.
351 fn is_socket(&self) -> bool;
352}
353
354impl FileTypeExt for fs::FileType {
355 fn is_block_device(&self) -> bool {
356 self.as_inner().is(libc::S_IFBLK)
357 }
358 fn is_char_device(&self) -> bool {
359 self.as_inner().is(libc::S_IFCHR)
360 }
361 fn is_socket(&self) -> bool {
362 self.as_inner().is(libc::S_IFSOCK)
363 }
364}
365
366/// WASI-specific extension methods for [`fs::DirEntry`].
367pub trait DirEntryExt {
368 /// Returns the underlying `d_ino` field of the `dirent_t`
369 fn ino(&self) -> u64;
370}
371
372impl DirEntryExt for fs::DirEntry {
373 fn ino(&self) -> u64 {
374 self.as_inner().ino()
375 }
376}
377
378/// Creates a hard link.
379///
380/// This corresponds to the `path_link` syscall.
381#[doc(alias = "path_link")]
382#[cfg(target_env = "p1")]
383pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
384 old_fd: &File,
385 old_flags: u32,
386 old_path: P,
387 new_fd: &File,
388 new_path: U,
389) -> io::Result<()> {
390 unsafe {
391 wasi::path_link(
392 old_fd.as_raw_fd() as wasi::Fd,
393 old_flags,
394 osstr2str(old_path.as_ref().as_ref())?,
395 new_fd.as_raw_fd() as wasi::Fd,
396 osstr2str(new_path.as_ref().as_ref())?,
397 )
398 .map_err(err2io)
399 }
400}
401
402/// Renames a file or directory.
403///
404/// This corresponds to the `path_rename` syscall.
405#[doc(alias = "path_rename")]
406#[cfg(target_env = "p1")]
407pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
408 old_fd: &File,
409 old_path: P,
410 new_fd: &File,
411 new_path: U,
412) -> io::Result<()> {
413 unsafe {
414 wasi::path_rename(
415 old_fd.as_raw_fd() as wasi::Fd,
416 osstr2str(old_path.as_ref().as_ref())?,
417 new_fd.as_raw_fd() as wasi::Fd,
418 osstr2str(new_path.as_ref().as_ref())?,
419 )
420 .map_err(err2io)
421 }
422}
423
424/// Creates a symbolic link.
425///
426/// This corresponds to the `path_symlink` syscall.
427#[doc(alias = "path_symlink")]
428#[cfg(target_env = "p1")]
429pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
430 old_path: P,
431 fd: &File,
432 new_path: U,
433) -> io::Result<()> {
434 unsafe {
435 wasi::path_symlink(
436 osstr2str(old_path.as_ref().as_ref())?,
437 fd.as_raw_fd() as wasi::Fd,
438 osstr2str(new_path.as_ref().as_ref())?,
439 )
440 .map_err(err2io)
441 }
442}
443
444/// Creates a symbolic link.
445///
446/// This is a convenience API similar to `std::os::unix::fs::symlink` and
447/// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`.
448pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> {
449 crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref())
450}
451
452#[cfg(target_env = "p1")]
453fn osstr2str(f: &OsStr) -> io::Result<&str> {
454 f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
455}