Skip to main content

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