compio_fs/
file.rs

1use std::{future::Future, io, mem::ManuallyDrop, path::Path};
2
3use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut};
4#[cfg(unix)]
5use compio_driver::op::FileStat;
6use compio_driver::{
7    ToSharedFd, impl_raw_fd,
8    op::{
9        AsyncifyFd, BufResultExt, CloseFile, ReadAt, ReadManagedAt, ResultTakeBuffer, Sync, WriteAt,
10    },
11};
12use compio_io::{AsyncReadAt, AsyncReadManagedAt, AsyncWriteAt, util::Splittable};
13use compio_runtime::{Attacher, BorrowedBuffer, BufferPool};
14#[cfg(all(unix, not(solarish)))]
15use {
16    compio_buf::{IoVectoredBuf, IoVectoredBufMut},
17    compio_driver::op::{ReadVectoredAt, WriteVectoredAt},
18};
19
20use crate::{Metadata, OpenOptions, Permissions};
21
22/// A reference to an open file on the filesystem.
23///
24/// An instance of a `File` can be read and/or written depending on what options
25/// it was opened with. The `File` type provides **positional** read and write
26/// operations. The file does not maintain an internal cursor. The caller is
27/// required to specify an offset when issuing an operation.
28///
29///
30/// If you'd like to use methods from [`AsyncRead`](`compio_io::AsyncRead`) or
31/// [`AsyncWrite`](`compio_io::AsyncWrite`) traits, you can wrap `File` with
32/// [`std::io::Cursor`].
33///
34/// # Examples
35/// ```ignore
36/// use compio::fs::File;
37/// use compio::buf::BufResult;
38/// use std::io::Cursor;
39///
40/// let file = File::open("foo.txt").await?;
41/// let cursor = Cursor::new(file);
42///
43/// let int = cursor.read_u32().await?;
44/// let float = cursor.read_f32().await?;
45///
46/// let mut string = String::new();
47/// let BufResult(result, string) = cursor.read_to_string(string).await;
48///
49/// let mut buf = vec![0; 1024];
50/// let BufResult(result, buf) = cursor.read_exact(buf).await;
51/// ```
52#[derive(Debug, Clone)]
53pub struct File {
54    inner: Attacher<std::fs::File>,
55}
56
57impl File {
58    pub(crate) fn from_std(file: std::fs::File) -> io::Result<Self> {
59        Ok(Self {
60            inner: Attacher::new(file)?,
61        })
62    }
63
64    /// Attempts to open a file in read-only mode.
65    ///
66    /// See the [`OpenOptions::open`] method for more details.
67    pub async fn open(path: impl AsRef<Path>) -> io::Result<Self> {
68        OpenOptions::new().read(true).open(path).await
69    }
70
71    /// Opens a file in write-only mode.
72    ///
73    /// This function will create a file if it does not exist,
74    /// and will truncate it if it does.
75    ///
76    /// See the [`OpenOptions::open`] function for more details.
77    pub async fn create(path: impl AsRef<Path>) -> io::Result<Self> {
78        OpenOptions::new()
79            .create(true)
80            .write(true)
81            .truncate(true)
82            .open(path)
83            .await
84    }
85
86    /// Close the file. If the returned future is dropped before polling, the
87    /// file won't be closed.
88    pub fn close(self) -> impl Future<Output = io::Result<()>> {
89        // Make sure that fd won't be dropped after `close` called.
90        // Users may call this method and drop the future immediately. In that way
91        // `close` should be cancelled.
92        let this = ManuallyDrop::new(self);
93        async move {
94            let fd = ManuallyDrop::into_inner(this)
95                .inner
96                .into_inner()
97                .take()
98                .await;
99            if let Some(fd) = fd {
100                let op = CloseFile::new(fd.into());
101                compio_runtime::submit(op).await.0?;
102            }
103            Ok(())
104        }
105    }
106
107    /// Queries metadata about the underlying file.
108    #[cfg(windows)]
109    pub async fn metadata(&self) -> io::Result<Metadata> {
110        let op = AsyncifyFd::new(self.to_shared_fd(), |file: &std::fs::File| {
111            match file.metadata().map(Metadata::from_std) {
112                Ok(meta) => BufResult(Ok(0), Some(meta)),
113                Err(e) => BufResult(Err(e), None),
114            }
115        });
116        let BufResult(res, meta) = compio_runtime::submit(op).await;
117        res.map(|_| meta.into_inner().expect("metadata should be present"))
118    }
119
120    #[cfg(windows)]
121    /// Truncates or extends the underlying file, updating the size of this file
122    /// to become `size`.
123    pub async fn set_len(&self, size: u64) -> io::Result<()> {
124        let op = AsyncifyFd::new(self.to_shared_fd(), move |file: &std::fs::File| {
125            BufResult(file.set_len(size).map(|_| 0), ())
126        });
127
128        compio_runtime::submit(op).await.0.map(|_| ())
129    }
130
131    #[cfg(unix)]
132    /// Truncates or extends the underlying file, updating the size of this file
133    /// to become `size`.
134    ///
135    /// NOTE: On Linux kernel <= 6.9 or when io uring is disabled, the operation
136    /// will be offloaded to the separate blocking thread
137    pub async fn set_len(&self, size: u64) -> io::Result<()> {
138        use compio_driver::op::TruncateFile;
139
140        let op = TruncateFile::new(self.to_shared_fd(), size);
141        compio_runtime::submit(op).await.0.map(|_| ())
142    }
143
144    /// Queries metadata about the underlying file.
145    #[cfg(unix)]
146    pub async fn metadata(&self) -> io::Result<Metadata> {
147        let op = FileStat::new(self.to_shared_fd());
148        let BufResult(res, op) = compio_runtime::submit(op).await;
149        res.map(|_| Metadata::from_stat(op.into_inner()))
150    }
151
152    /// Changes the permissions on the underlying file.
153    pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
154        let op = AsyncifyFd::new(self.to_shared_fd(), move |file: &std::fs::File| {
155            BufResult(file.set_permissions(perm.0).map(|_| 0), ())
156        });
157        compio_runtime::submit(op).await.0.map(|_| ())
158    }
159
160    async fn sync_impl(&self, datasync: bool) -> io::Result<()> {
161        let op = Sync::new(self.to_shared_fd(), datasync);
162        compio_runtime::submit(op).await.0?;
163        Ok(())
164    }
165
166    /// Attempts to sync all OS-internal metadata to disk.
167    ///
168    /// This function will attempt to ensure that all in-memory data reaches the
169    /// filesystem before returning.
170    pub async fn sync_all(&self) -> io::Result<()> {
171        self.sync_impl(false).await
172    }
173
174    /// This function is similar to [`sync_all`], except that it might not
175    /// synchronize file metadata to the filesystem.
176    ///
177    /// This is intended for use cases that must synchronize content, but don't
178    /// need the metadata on disk. The goal of this method is to reduce disk
179    /// operations.
180    ///
181    /// Note that some platforms may simply implement this in terms of
182    /// [`sync_all`].
183    ///
184    /// [`sync_all`]: File::sync_all
185    pub async fn sync_data(&self) -> io::Result<()> {
186        self.sync_impl(true).await
187    }
188}
189
190impl AsyncReadAt for File {
191    async fn read_at<T: IoBufMut>(&self, buffer: T, pos: u64) -> BufResult<usize, T> {
192        let fd = self.inner.to_shared_fd();
193        let op = ReadAt::new(fd, pos, buffer);
194        let res = compio_runtime::submit(op).await.into_inner();
195        unsafe { res.map_advanced() }
196    }
197
198    #[cfg(all(unix, not(solarish)))]
199    async fn read_vectored_at<T: IoVectoredBufMut>(
200        &self,
201        buffer: T,
202        pos: u64,
203    ) -> BufResult<usize, T> {
204        use compio_driver::op::VecBufResultExt;
205
206        let fd = self.inner.to_shared_fd();
207        let op = ReadVectoredAt::new(fd, pos, buffer);
208        let res = compio_runtime::submit(op).await.into_inner();
209        unsafe { res.map_vec_advanced() }
210    }
211}
212
213impl AsyncReadManagedAt for File {
214    type Buffer<'a> = BorrowedBuffer<'a>;
215    type BufferPool = BufferPool;
216
217    async fn read_managed_at<'a>(
218        &self,
219        buffer_pool: &'a Self::BufferPool,
220        len: usize,
221        pos: u64,
222    ) -> io::Result<Self::Buffer<'a>> {
223        let fd = self.inner.to_shared_fd();
224        let buffer_pool = buffer_pool.try_inner()?;
225        let op = ReadManagedAt::new(fd, pos, buffer_pool, len)?;
226        compio_runtime::submit(op)
227            .with_extra()
228            .await
229            .take_buffer(buffer_pool)
230    }
231}
232
233impl AsyncWriteAt for File {
234    #[inline]
235    async fn write_at<T: IoBuf>(&mut self, buf: T, pos: u64) -> BufResult<usize, T> {
236        (&*self).write_at(buf, pos).await
237    }
238
239    #[cfg(all(unix, not(solarish)))]
240    #[inline]
241    async fn write_vectored_at<T: IoVectoredBuf>(
242        &mut self,
243        buf: T,
244        pos: u64,
245    ) -> BufResult<usize, T> {
246        (&*self).write_vectored_at(buf, pos).await
247    }
248}
249
250impl AsyncWriteAt for &File {
251    async fn write_at<T: IoBuf>(&mut self, buffer: T, pos: u64) -> BufResult<usize, T> {
252        let fd = self.inner.to_shared_fd();
253        let op = WriteAt::new(fd, pos, buffer);
254        compio_runtime::submit(op).await.into_inner()
255    }
256
257    #[cfg(all(unix, not(solarish)))]
258    async fn write_vectored_at<T: IoVectoredBuf>(
259        &mut self,
260        buffer: T,
261        pos: u64,
262    ) -> BufResult<usize, T> {
263        let fd = self.inner.to_shared_fd();
264        let op = WriteVectoredAt::new(fd, pos, buffer);
265        compio_runtime::submit(op).await.into_inner()
266    }
267}
268
269impl Splittable for File {
270    type ReadHalf = File;
271    type WriteHalf = File;
272
273    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
274        (self.clone(), self)
275    }
276}
277
278impl Splittable for &File {
279    type ReadHalf = File;
280    type WriteHalf = File;
281
282    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
283        (self.clone(), self.clone())
284    }
285}
286
287impl_raw_fd!(File, std::fs::File, inner, file);