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    /// Queries metadata about the underlying file.
121    #[cfg(unix)]
122    pub async fn metadata(&self) -> io::Result<Metadata> {
123        let op = FileStat::new(self.to_shared_fd());
124        let BufResult(res, op) = compio_runtime::submit(op).await;
125        res.map(|_| Metadata::from_stat(op.into_inner()))
126    }
127
128    /// Changes the permissions on the underlying file.
129    pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
130        let op = AsyncifyFd::new(self.to_shared_fd(), move |file: &std::fs::File| {
131            BufResult(file.set_permissions(perm.0).map(|_| 0), ())
132        });
133        compio_runtime::submit(op).await.0.map(|_| ())
134    }
135
136    async fn sync_impl(&self, datasync: bool) -> io::Result<()> {
137        let op = Sync::new(self.to_shared_fd(), datasync);
138        compio_runtime::submit(op).await.0?;
139        Ok(())
140    }
141
142    /// Attempts to sync all OS-internal metadata to disk.
143    ///
144    /// This function will attempt to ensure that all in-memory data reaches the
145    /// filesystem before returning.
146    pub async fn sync_all(&self) -> io::Result<()> {
147        self.sync_impl(false).await
148    }
149
150    /// This function is similar to [`sync_all`], except that it might not
151    /// synchronize file metadata to the filesystem.
152    ///
153    /// This is intended for use cases that must synchronize content, but don't
154    /// need the metadata on disk. The goal of this method is to reduce disk
155    /// operations.
156    ///
157    /// Note that some platforms may simply implement this in terms of
158    /// [`sync_all`].
159    ///
160    /// [`sync_all`]: File::sync_all
161    pub async fn sync_data(&self) -> io::Result<()> {
162        self.sync_impl(true).await
163    }
164}
165
166impl AsyncReadAt for File {
167    async fn read_at<T: IoBufMut>(&self, buffer: T, pos: u64) -> BufResult<usize, T> {
168        let fd = self.inner.to_shared_fd();
169        let op = ReadAt::new(fd, pos, buffer);
170        compio_runtime::submit(op).await.into_inner().map_advanced()
171    }
172
173    #[cfg(all(unix, not(solarish)))]
174    async fn read_vectored_at<T: IoVectoredBufMut>(
175        &self,
176        buffer: T,
177        pos: u64,
178    ) -> BufResult<usize, T> {
179        let fd = self.inner.to_shared_fd();
180        let op = ReadVectoredAt::new(fd, pos, buffer);
181        compio_runtime::submit(op).await.into_inner().map_advanced()
182    }
183}
184
185impl AsyncReadManagedAt for File {
186    type Buffer<'a> = BorrowedBuffer<'a>;
187    type BufferPool = BufferPool;
188
189    async fn read_managed_at<'a>(
190        &self,
191        buffer_pool: &'a Self::BufferPool,
192        len: usize,
193        pos: u64,
194    ) -> io::Result<Self::Buffer<'a>> {
195        let fd = self.inner.to_shared_fd();
196        let buffer_pool = buffer_pool.try_inner()?;
197        let op = ReadManagedAt::new(fd, pos, buffer_pool, len)?;
198        compio_runtime::submit_with_flags(op)
199            .await
200            .take_buffer(buffer_pool)
201    }
202}
203
204impl AsyncWriteAt for File {
205    #[inline]
206    async fn write_at<T: IoBuf>(&mut self, buf: T, pos: u64) -> BufResult<usize, T> {
207        (&*self).write_at(buf, pos).await
208    }
209
210    #[cfg(all(unix, not(solarish)))]
211    #[inline]
212    async fn write_vectored_at<T: IoVectoredBuf>(
213        &mut self,
214        buf: T,
215        pos: u64,
216    ) -> BufResult<usize, T> {
217        (&*self).write_vectored_at(buf, pos).await
218    }
219}
220
221impl AsyncWriteAt for &File {
222    async fn write_at<T: IoBuf>(&mut self, buffer: T, pos: u64) -> BufResult<usize, T> {
223        let fd = self.inner.to_shared_fd();
224        let op = WriteAt::new(fd, pos, buffer);
225        compio_runtime::submit(op).await.into_inner()
226    }
227
228    #[cfg(all(unix, not(solarish)))]
229    async fn write_vectored_at<T: IoVectoredBuf>(
230        &mut self,
231        buffer: T,
232        pos: u64,
233    ) -> BufResult<usize, T> {
234        let fd = self.inner.to_shared_fd();
235        let op = WriteVectoredAt::new(fd, pos, buffer);
236        compio_runtime::submit(op).await.into_inner()
237    }
238}
239
240impl Splittable for File {
241    type ReadHalf = File;
242    type WriteHalf = File;
243
244    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
245        (self.clone(), self)
246    }
247}
248
249impl Splittable for &File {
250    type ReadHalf = File;
251    type WriteHalf = File;
252
253    fn split(self) -> (Self::ReadHalf, Self::WriteHalf) {
254        (self.clone(), self.clone())
255    }
256}
257
258impl_raw_fd!(File, std::fs::File, inner, file);