Skip to main content

tokio/fs/
write.rs

1use crate::{fs::asyncify, util::as_ref::OwnedBuf};
2
3use std::{io, path::Path};
4
5/// Creates a future that will open a file for writing and write the entire
6/// contents of `contents` to it.
7///
8/// This is the async equivalent of [`std::fs::write`][std].
9///
10/// This operation is implemented by running the equivalent blocking operation
11/// on a separate thread pool using [`spawn_blocking`].
12///
13/// [`spawn_blocking`]: crate::task::spawn_blocking
14/// [std]: fn@std::fs::write
15///
16/// # Examples
17///
18/// ```no_run
19/// use tokio::fs;
20///
21/// # async fn dox() -> std::io::Result<()> {
22/// fs::write("foo.txt", b"Hello world!").await?;
23/// # Ok(())
24/// # }
25/// ```
26pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
27    let path = path.as_ref();
28    let contents = crate::util::as_ref::upgrade(contents);
29
30    #[cfg(all(
31        tokio_unstable,
32        feature = "io-uring",
33        feature = "rt",
34        feature = "fs",
35        target_os = "linux"
36    ))]
37    {
38        if let Ok(handle) = crate::runtime::Handle::try_current() {
39            if let Some(driver_handle) = handle.inner.driver().io.as_ref() {
40                if driver_handle
41                    .check_and_init(io_uring::opcode::Write::CODE)
42                    .await?
43                {
44                    return write_uring(path, contents).await;
45                }
46            }
47        }
48    }
49
50    write_spawn_blocking(path, contents).await
51}
52
53#[cfg(all(
54    tokio_unstable,
55    feature = "io-uring",
56    feature = "rt",
57    feature = "fs",
58    target_os = "linux"
59))]
60async fn write_uring(path: &Path, mut buf: OwnedBuf) -> io::Result<()> {
61    use crate::{fs::OpenOptions, runtime::driver::op::Op};
62    use std::os::fd::OwnedFd;
63
64    let file = OpenOptions::new()
65        .write(true)
66        .create(true)
67        .truncate(true)
68        .open(path)
69        .await?;
70
71    let mut fd: OwnedFd = file
72        .try_into_std()
73        .expect("unexpected in-flight operation detected")
74        .into();
75
76    let total: usize = buf.as_ref().len();
77    let mut buf_offset: usize = 0;
78    let mut file_offset: u64 = 0;
79    while buf_offset < total {
80        let (res, r_buf, r_fd) = Op::write_at(fd, buf, buf_offset, file_offset)?.await;
81
82        // A cancelled op returns no resources to continue with, so its error is
83        // terminal; otherwise an interrupted write retries from where it stopped.
84        let (Some(r_buf), Some(r_fd)) = (r_buf, r_fd) else {
85            return Err(res.expect_err("a cancelled op completes with an error"));
86        };
87
88        let n = match res {
89            Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
90            Ok(n) => n,
91            Err(e) if e.kind() == io::ErrorKind::Interrupted => 0,
92            Err(e) => return Err(e),
93        };
94
95        buf = r_buf;
96        fd = r_fd;
97        buf_offset += n as usize;
98        file_offset += n as u64;
99    }
100
101    Ok(())
102}
103
104async fn write_spawn_blocking(path: &Path, contents: OwnedBuf) -> io::Result<()> {
105    let path = path.to_owned();
106    asyncify(move || std::fs::write(path, contents)).await
107}