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::{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 {
250 wasip1::fd_fdstat_set_flags(self.as_raw_fd() as wasip1::Fd, flags).map_err(err2io)
251 }
252 }
253
254 #[cfg(target_env = "p1")]
255 fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
256 unsafe {
257 wasip1::fd_fdstat_set_rights(self.as_raw_fd() as wasip1::Fd, rights, inheriting)
258 .map_err(err2io)
259 }
260 }
261
262 #[cfg(target_env = "p1")]
263 fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
264 let advice = match advice {
265 a if a == wasip1::ADVICE_NORMAL.raw() => wasip1::ADVICE_NORMAL,
266 a if a == wasip1::ADVICE_SEQUENTIAL.raw() => wasip1::ADVICE_SEQUENTIAL,
267 a if a == wasip1::ADVICE_RANDOM.raw() => wasip1::ADVICE_RANDOM,
268 a if a == wasip1::ADVICE_WILLNEED.raw() => wasip1::ADVICE_WILLNEED,
269 a if a == wasip1::ADVICE_DONTNEED.raw() => wasip1::ADVICE_DONTNEED,
270 a if a == wasip1::ADVICE_NOREUSE.raw() => wasip1::ADVICE_NOREUSE,
271 _ => {
272 return Err(io::const_error!(
273 io::ErrorKind::InvalidInput,
274 "invalid parameter 'advice'",
275 ));
276 }
277 };
278
279 unsafe {
280 wasip1::fd_advise(self.as_raw_fd() as wasip1::Fd, offset, len, advice).map_err(err2io)
281 }
282 }
283
284 #[cfg(target_env = "p1")]
285 fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
286 unsafe { wasip1::fd_allocate(self.as_raw_fd() as wasip1::Fd, offset, len).map_err(err2io) }
287 }
288
289 #[cfg(target_env = "p1")]
290 fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
291 let path = osstr2str(dir.as_ref().as_ref())?;
292 unsafe {
293 wasip1::path_create_directory(self.as_raw_fd() as wasip1::Fd, path).map_err(err2io)
294 }
295 }
296
297 #[cfg(target_env = "p1")]
298 fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
299 let path = osstr2str(path.as_ref().as_ref())?;
300 unsafe { wasip1::path_unlink_file(self.as_raw_fd() as wasip1::Fd, path).map_err(err2io) }
301 }
302
303 #[cfg(target_env = "p1")]
304 fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
305 let path = osstr2str(path.as_ref().as_ref())?;
306 unsafe {
307 wasip1::path_remove_directory(self.as_raw_fd() as wasip1::Fd, path).map_err(err2io)
308 }
309 }
310}
311
312/// WASI-specific extensions to [`OpenOptions`].
313pub trait OpenOptionsExt {
314 /// Pass custom flags to the `flags` argument of `open`.
315 fn custom_flags(&mut self, flags: i32) -> &mut Self;
316}
317
318impl OpenOptionsExt for OpenOptions {
319 fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
320 self.as_inner_mut().custom_flags(flags);
321 self
322 }
323}
324
325/// WASI-specific extensions to [`fs::Metadata`].
326pub trait MetadataExt {
327 /// Returns the `st_dev` field of the internal `filestat_t`
328 fn dev(&self) -> u64;
329 /// Returns the `st_ino` field of the internal `filestat_t`
330 fn ino(&self) -> u64;
331 /// Returns the `st_nlink` field of the internal `filestat_t`
332 fn nlink(&self) -> u64;
333}
334
335impl MetadataExt for fs::Metadata {
336 fn dev(&self) -> u64 {
337 self.as_inner().as_inner().st_dev
338 }
339 fn ino(&self) -> u64 {
340 self.as_inner().as_inner().st_ino
341 }
342 fn nlink(&self) -> u64 {
343 self.as_inner().as_inner().st_nlink
344 }
345}
346
347/// WASI-specific extensions for [`fs::FileType`].
348///
349/// Adds support for special WASI file types such as block/character devices,
350/// pipes, and sockets.
351pub trait FileTypeExt {
352 /// Returns `true` if this file type is a block device.
353 fn is_block_device(&self) -> bool;
354 /// Returns `true` if this file type is a character device.
355 fn is_char_device(&self) -> bool;
356 /// Returns `true` if this file type is any type of socket.
357 fn is_socket(&self) -> bool;
358}
359
360impl FileTypeExt for fs::FileType {
361 fn is_block_device(&self) -> bool {
362 self.as_inner().is(libc::S_IFBLK)
363 }
364 fn is_char_device(&self) -> bool {
365 self.as_inner().is(libc::S_IFCHR)
366 }
367 fn is_socket(&self) -> bool {
368 self.as_inner().is(libc::S_IFSOCK)
369 }
370}
371
372/// WASI-specific extension methods for [`fs::DirEntry`].
373pub trait DirEntryExt {
374 /// Returns the underlying `d_ino` field of the `dirent_t`
375 fn ino(&self) -> u64;
376}
377
378impl DirEntryExt for fs::DirEntry {
379 fn ino(&self) -> u64 {
380 self.as_inner().ino()
381 }
382}
383
384/// Creates a hard link.
385///
386/// This corresponds to the `path_link` syscall.
387#[doc(alias = "path_link")]
388#[cfg(target_env = "p1")]
389pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
390 old_fd: &File,
391 old_flags: u32,
392 old_path: P,
393 new_fd: &File,
394 new_path: U,
395) -> io::Result<()> {
396 unsafe {
397 wasip1::path_link(
398 old_fd.as_raw_fd() as wasip1::Fd,
399 old_flags,
400 osstr2str(old_path.as_ref().as_ref())?,
401 new_fd.as_raw_fd() as wasip1::Fd,
402 osstr2str(new_path.as_ref().as_ref())?,
403 )
404 .map_err(err2io)
405 }
406}
407
408/// Renames a file or directory.
409///
410/// This corresponds to the `path_rename` syscall.
411#[doc(alias = "path_rename")]
412#[cfg(target_env = "p1")]
413pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
414 old_fd: &File,
415 old_path: P,
416 new_fd: &File,
417 new_path: U,
418) -> io::Result<()> {
419 unsafe {
420 wasip1::path_rename(
421 old_fd.as_raw_fd() as wasip1::Fd,
422 osstr2str(old_path.as_ref().as_ref())?,
423 new_fd.as_raw_fd() as wasip1::Fd,
424 osstr2str(new_path.as_ref().as_ref())?,
425 )
426 .map_err(err2io)
427 }
428}
429
430/// Creates a symbolic link.
431///
432/// This corresponds to the `path_symlink` syscall.
433#[doc(alias = "path_symlink")]
434#[cfg(target_env = "p1")]
435pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
436 old_path: P,
437 fd: &File,
438 new_path: U,
439) -> io::Result<()> {
440 unsafe {
441 wasip1::path_symlink(
442 osstr2str(old_path.as_ref().as_ref())?,
443 fd.as_raw_fd() as wasip1::Fd,
444 osstr2str(new_path.as_ref().as_ref())?,
445 )
446 .map_err(err2io)
447 }
448}
449
450/// Creates a symbolic link.
451///
452/// This is a convenience API similar to `std::os::unix::fs::symlink` and
453/// `std::os::windows::fs::symlink_file` and `std::os::windows::fs::symlink_dir`.
454pub fn symlink_path<P: AsRef<Path>, U: AsRef<Path>>(old_path: P, new_path: U) -> io::Result<()> {
455 crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref())
456}
457
458#[cfg(target_env = "p1")]
459fn osstr2str(f: &OsStr) -> io::Result<&str> {
460 f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
461}