• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::{
6     convert::{TryFrom, TryInto},
7     fs::File as StdFile,
8     io,
9     path::Path,
10 };
11 
12 use sys_util::AsRawDescriptor;
13 
14 use crate::{sys, AsIoBufs};
15 
16 static DOWNCAST_ERROR: &str = "inner error not downcastable to `io::Error`";
17 
18 /// A reference to an open file on the filesystem.
19 ///
20 /// `File` provides asynchronous functions for reading and writing data. When a `File` is dropped,
21 /// the underlying OS handle will be closed once there are no more pending I/O operations on it.
22 ///
23 /// # Errors
24 ///
25 /// Many `File` methods return an `anyhow::Result`. However, it is guaranteed that if an error is
26 /// returned, that error is downcastable to an `io::Error`.
27 ///
28 /// # Examples
29 ///
30 /// Create a new file and write data to it.
31 ///
32 /// ```
33 /// # use cros_async::{Executor, File};
34 /// # use tempfile::TempDir;
35 /// # async fn write_all() -> anyhow::Result<()>{
36 /// #   let dir = TempDir::new()?;
37 /// #   let path = dir.path().join("test");
38 ///     let mut f = File::create(path)?;
39 ///     f.write_all(b"Hello, world!", None).await
40 /// # }
41 /// #
42 /// # Executor::new().run_until(write_all()).unwrap();
43 /// ```
44 ///
45 /// Extract an `io::Error` from a failed operation.
46 ///
47 /// ```
48 /// # use std::io;
49 /// # use cros_async::File;
50 /// # fn extract_io_error() -> io::Error {
51 ///     if let Err(e) = File::open("nonexistent-filesystem-path") {
52 ///         e.downcast::<io::Error>().expect("Error not downcastable to `io::Error`")
53 ///     } else {
54 ///         panic!("nonexistent path exists");
55 ///     }
56 /// # }
57 ///
58 /// # let _ = extract_io_error();
59 /// ```
60 #[derive(Debug)]
61 pub struct File {
62     inner: sys::File,
63 }
64 
65 impl File {
66     /// Attempt to open a file in read-only mode.
67     ///
68     /// This is a convenience wrapper for the standard library `File::open` method.
open<P: AsRef<Path>>(p: P) -> anyhow::Result<File>69     pub fn open<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
70         sys::File::open(p).map(|inner| File { inner })
71     }
72 
73     /// Attempt to open a file in write-only mode.
74     ///
75     /// This function will create a file if it does not exist, and will truncate it if it does. It
76     /// is a convenience wrapper for the `File::create` method from the standard library.
create<P: AsRef<Path>>(p: P) -> anyhow::Result<File>77     pub fn create<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
78         sys::File::create(p).map(|inner| File { inner })
79     }
80 
81     /// Create a `File` from a standard library file.
82     ///
83     /// The conversion may fail if the underlying OS handle cannot be prepared for asynchronous
84     /// operation. For example on epoll-based systems, this requires making the handle non-blocking.
from_std(f: StdFile) -> anyhow::Result<File>85     pub fn from_std(f: StdFile) -> anyhow::Result<File> {
86         File::try_from(f)
87     }
88 
89     /// Convert a `File` back into a standard library file.
90     ///
91     /// The conversion may fail if there are still pending asynchronous operations on the underlying
92     /// OS handle.  In this case, the original `File` is returned as the error.
into_std(self) -> Result<StdFile, File>93     pub fn into_std(self) -> Result<StdFile, File> {
94         self.try_into()
95     }
96 
97     /// Read up to `buf.len()` bytes from the file starting at `offset` into `buf`, returning the
98     /// number of bytes read.
99     ///
100     /// If `offset` is `None` then the bytes are read starting from the kernel offset for the
101     /// underlying OS file handle. Callers should take care when calling this method from multiple
102     /// threads without providing `offset` as the order in which the operations will execute is
103     /// undefined.
104     ///
105     /// When using I/O drivers like `io_uring`, data may be copied from an internal buffer into
106     /// `buf` so this function is best suited for reading small amounts of data. Callers that wish
107     /// to avoid copying data may want to use `File::read_iobuf` instead. Additionally, dropping
108     /// this async fn after it has started may not cancel the underlying asynchronous operation.
read(&self, buf: &mut [u8], offset: Option<u64>) -> anyhow::Result<usize>109     pub async fn read(&self, buf: &mut [u8], offset: Option<u64>) -> anyhow::Result<usize> {
110         self.inner.read(buf, offset).await
111     }
112 
113     /// Read exactly `buf.len()` bytes from the file starting at `offset` into `buf`.
114     ///
115     /// This method calls `File::read` in a loop until `buf.len()` bytes have been read from the
116     /// underlying file. Callers should take care when calling this method from multiple threads
117     /// without providing `offset` as the order in which data is read is undefined and the data
118     /// returned in `buf` may not be from contiguous regions in the underlying file.
119     ///
120     /// When using I/O drivers like `io_uring`, data may be copied from an internal buffer into
121     /// `buf`. Callers that wish to avoid copying data may want to use `File::read_iobuf` instead.
122     /// Additionally, dropping this async fn after it has started may not cancel the underlying
123     /// asynchronous operation.
124     ///
125     /// # Errors
126     ///
127     /// This function will directly return any non-`io::ErrorKind::Interrupted` errors. In this
128     /// case, the number of bytes read from the underlying file and the kernel offset are undefined.
read_exact( &self, mut buf: &mut [u8], mut offset: Option<u64>, ) -> anyhow::Result<()>129     pub async fn read_exact(
130         &self,
131         mut buf: &mut [u8],
132         mut offset: Option<u64>,
133     ) -> anyhow::Result<()> {
134         if let Some(off) = offset {
135             debug_assert!(u64::try_from(buf.len())
136                 .ok()
137                 .and_then(|len| len.checked_add(off))
138                 .is_some());
139         }
140 
141         while !buf.is_empty() {
142             match self.read(buf, offset).await {
143                 Ok(0) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()),
144                 Ok(n) => {
145                     buf = &mut buf[n..];
146                     if let Some(off) = offset {
147                         offset = Some(off + n as u64);
148                     }
149                 }
150                 Err(e) => {
151                     let err = e.downcast_ref::<io::Error>().expect(DOWNCAST_ERROR);
152                     if err.kind() != io::ErrorKind::Interrupted {
153                         return Err(e);
154                     }
155                 }
156             }
157         }
158 
159         Ok(())
160     }
161 
162     /// Reads data from the underlying data starting at `offset` into an owned buffer.
163     ///
164     /// This method is like `File::read` but takes ownership of the buffer. When using I/O drivers
165     /// like `io_uring`, this can improve performance by avoiding the need to first read the data
166     /// into an internal buffer. Dropping this async fn may not cancel the underlying I/O operation.
167     ///
168     /// # Examples
169     ///
170     /// Read file data into a `Vec<u8>`.
171     ///
172     /// ```
173     /// # async fn read_to_vec() -> anyhow::Result<()> {
174     ///     use cros_async::{File, OwnedIoBuf};
175     ///
176     ///     let f = File::open("/dev/zero")?;
177     ///     let buf = OwnedIoBuf::new(vec![0xcc; 64]);
178     ///
179     ///     let (res, buf) = f.read_iobuf(buf, None).await;
180     ///     let count = res?;
181     ///
182     ///     let orig: Vec<u8> = buf.into_inner();
183     ///     assert!(count <= 64);
184     ///     assert_eq!(&orig[..count], &[0u8; 64][..count]);
185     /// #   Ok(())
186     /// # }
187     /// # cros_async::Executor::new().run_until(read_to_vec()).unwrap();
188     /// ```
read_iobuf<B: AsIoBufs + Unpin + 'static>( &self, buf: B, offset: Option<u64>, ) -> (anyhow::Result<usize>, B)189     pub async fn read_iobuf<B: AsIoBufs + Unpin + 'static>(
190         &self,
191         buf: B,
192         offset: Option<u64>,
193     ) -> (anyhow::Result<usize>, B) {
194         self.inner.read_iobuf(buf, offset).await
195     }
196 
197     /// Write up to `buf.len()` bytes from `buf` to the file starting at `offset`, returning the
198     /// number of bytes written.
199     ///
200     /// If `offset` is `None` then the bytes are written starting from the kernel offset for the
201     /// underlying OS file handle. Callers should take care when calling this method from multiple
202     /// threads without providing `offset` as the order in which the operations will execute is
203     /// undefined.
204     ///
205     /// When using I/O drivers like `io_uring`, data may be copied into an internal buffer from
206     /// `buf` so this function is best suited for writing small amounts of data. Callers that wish
207     /// to avoid copying data may want to use `File::write_iobuf` instead. Additionally, dropping
208     /// this async fn after it has started may not cancel the underlying asynchronous operation.
write(&self, buf: &[u8], offset: Option<u64>) -> anyhow::Result<usize>209     pub async fn write(&self, buf: &[u8], offset: Option<u64>) -> anyhow::Result<usize> {
210         self.inner.write(buf, offset).await
211     }
212 
213     /// Write all the data from `buf` into the underlying file starting at `offset`.
214     ///
215     /// This method calls `File::write` in a loop until `buf.len()` bytes have been written to the
216     /// underlying file. Callers should take care when calling this method from multiple threads
217     /// without providing `offset` as the order in which data is written is undefined and the data
218     /// written to the file may not be contiguous with respect to `buf`.
219     ///
220     /// When using I/O drivers like `io_uring`, data may be copied into an internal buffer from
221     /// `buf`. Callers that wish to avoid copying data may want to use `File::write_iobuf` instead.
222     /// Additionally, dropping this async fn after it has started may not cancel the underlying
223     /// asynchronous operation.
224     ///
225     /// # Errors
226     ///
227     /// This function will directly return any non-`io::ErrorKind::Interrupted` errors. In this
228     /// case, the number of bytes written to the underlying file and the kernel offset are undefined.
write_all(&self, mut buf: &[u8], mut offset: Option<u64>) -> anyhow::Result<()>229     pub async fn write_all(&self, mut buf: &[u8], mut offset: Option<u64>) -> anyhow::Result<()> {
230         if let Some(off) = offset {
231             debug_assert!(u64::try_from(buf.len())
232                 .ok()
233                 .and_then(|len| len.checked_add(off))
234                 .is_some());
235         }
236 
237         while !buf.is_empty() {
238             match self.write(buf, offset).await {
239                 Ok(0) => return Err(io::Error::from(io::ErrorKind::WriteZero).into()),
240                 Ok(n) => {
241                     buf = &buf[n..];
242                     if let Some(off) = offset {
243                         offset = Some(off + n as u64);
244                     }
245                 }
246                 Err(e) => {
247                     let err = e.downcast_ref::<io::Error>().expect(DOWNCAST_ERROR);
248                     if err.kind() != io::ErrorKind::Interrupted {
249                         return Err(e);
250                     }
251                 }
252             }
253         }
254 
255         Ok(())
256     }
257 
258     /// Writes data from an owned buffer into the underlying file at `offset`.
259     ///
260     /// This method is like `File::write` but takes ownership of the buffer. When using I/O drivers
261     /// like `io_uring`, this can improve performance by avoiding the need to first copy the data
262     /// into an internal buffer. Dropping this async fn may not cancel the underlying I/O operation.
263     ///
264     /// # Examples
265     ///
266     /// Write file data from a `Vec<u8>`.
267     ///
268     /// ```
269     /// # async fn read_to_vec() -> anyhow::Result<()> {
270     ///     use cros_async::{File, OwnedIoBuf};
271     ///
272     ///     let f = File::open("/dev/null")?;
273     ///     let buf = OwnedIoBuf::new(vec![0xcc; 64]);
274     ///
275     ///     let (res, buf) = f.write_iobuf(buf, None).await;
276     ///     let count = res?;
277     ///
278     ///     let orig: Vec<u8> = buf.into_inner();
279     ///     assert!(count <= 64);
280     /// #   Ok(())
281     /// # }
282     /// # cros_async::Executor::new().run_until(read_to_vec()).unwrap();
283     /// ```
write_iobuf<B: AsIoBufs + Unpin + 'static>( &self, buf: B, offset: Option<u64>, ) -> (anyhow::Result<usize>, B)284     pub async fn write_iobuf<B: AsIoBufs + Unpin + 'static>(
285         &self,
286         buf: B,
287         offset: Option<u64>,
288     ) -> (anyhow::Result<usize>, B) {
289         self.inner.write_iobuf(buf, offset).await
290     }
291 
292     /// Creates a hole in the underlying file of `len` bytes starting at `offset`.
punch_hole(&self, offset: u64, len: u64) -> anyhow::Result<()>293     pub async fn punch_hole(&self, offset: u64, len: u64) -> anyhow::Result<()> {
294         self.inner.punch_hole(offset, len).await
295     }
296 
297     /// Writes `len` bytes of zeroes to the underlying file at `offset`.
write_zeroes(&self, offset: u64, len: u64) -> anyhow::Result<()>298     pub async fn write_zeroes(&self, offset: u64, len: u64) -> anyhow::Result<()> {
299         self.inner.write_zeroes(offset, len).await
300     }
301 
302     /// Allocates `len` bytes of disk storage for the underlying file starting at `offset`.
allocate(&self, offset: u64, len: u64) -> anyhow::Result<()>303     pub async fn allocate(&self, offset: u64, len: u64) -> anyhow::Result<()> {
304         self.inner.allocate(offset, len).await
305     }
306 
307     /// Returns the current length of the file in bytes.
get_len(&self) -> anyhow::Result<u64>308     pub async fn get_len(&self) -> anyhow::Result<u64> {
309         self.inner.get_len().await
310     }
311 
312     /// Sets the current length of the file in bytes.
set_len(&self, len: u64) -> anyhow::Result<()>313     pub async fn set_len(&self, len: u64) -> anyhow::Result<()> {
314         self.inner.set_len(len).await
315     }
316 
317     /// Sync all buffered data and metadata for the underlying file to the disk.
sync_all(&self) -> anyhow::Result<()>318     pub async fn sync_all(&self) -> anyhow::Result<()> {
319         self.inner.sync_all().await
320     }
321 
322     /// Like `File::sync_all` but may not sync file metadata to the disk.
sync_data(&self) -> anyhow::Result<()>323     pub async fn sync_data(&self) -> anyhow::Result<()> {
324         self.inner.sync_data().await
325     }
326 
327     /// Try to clone this `File`.
328     ///
329     /// If successful, the returned `File` will have its own unique OS handle for the underlying
330     /// file.
try_clone(&self) -> anyhow::Result<File>331     pub fn try_clone(&self) -> anyhow::Result<File> {
332         self.inner.try_clone().map(|inner| File { inner })
333     }
334 }
335 
336 impl TryFrom<StdFile> for File {
337     type Error = anyhow::Error;
338 
try_from(f: StdFile) -> anyhow::Result<Self>339     fn try_from(f: StdFile) -> anyhow::Result<Self> {
340         sys::File::try_from(f).map(|inner| File { inner })
341     }
342 }
343 
344 impl TryFrom<File> for StdFile {
345     type Error = File;
try_from(f: File) -> Result<StdFile, File>346     fn try_from(f: File) -> Result<StdFile, File> {
347         StdFile::try_from(f.inner).map_err(|inner| File { inner })
348     }
349 }
350 
351 impl AsRawDescriptor for File {
as_raw_descriptor(&self) -> sys_util::RawDescriptor352     fn as_raw_descriptor(&self) -> sys_util::RawDescriptor {
353         self.inner.as_raw_descriptor()
354     }
355 }
356 
357 #[cfg(test)]
358 mod test {
359     use super::*;
360 
361     use std::{
362         fs::{File as StdFile, OpenOptions},
363         path::PathBuf,
364         thread,
365     };
366 
367     use anyhow::Context;
368     use futures::channel::oneshot::channel;
369 
370     use crate::{Executor, OwnedIoBuf};
371 
372     #[test]
readvec()373     fn readvec() {
374         async fn go() {
375             let f = StdFile::open("/dev/zero")
376                 .context("failed to open /dev/zero")
377                 .and_then(File::try_from)
378                 .unwrap();
379             let v = OwnedIoBuf::new(vec![0x55u8; 32]);
380 
381             let (res, v) = f.read_iobuf(v, None).await;
382             let count = res.unwrap();
383             assert_eq!(count, v.len());
384             assert!(v.iter().all(|&b| b == 0));
385         }
386 
387         let ex = Executor::new();
388         ex.run_until(go()).unwrap();
389     }
390 
391     #[test]
writevec()392     fn writevec() {
393         async fn go() {
394             let f = OpenOptions::new()
395                 .write(true)
396                 .open("/dev/null")
397                 .context("failed to open /dev/null")
398                 .and_then(File::try_from)
399                 .unwrap();
400             let v = OwnedIoBuf::new(vec![0x55u8; 32]);
401             let (res, _v) = f.write_iobuf(v, None).await;
402             let count = res.unwrap();
403             assert_eq!(count, 32);
404         }
405 
406         let ex = Executor::new();
407         ex.run_until(go()).unwrap();
408     }
409 
410     #[test]
file_is_send()411     fn file_is_send() {
412         const TRANSFER_COUNT: usize = 24;
413 
414         let (tx, rx) = channel::<File>();
415         let ex = Executor::new();
416 
417         let ex2 = ex.clone();
418         let worker_thread = thread::spawn(move || {
419             ex2.run_until(async move {
420                 let f = rx.await.unwrap();
421                 let buf = [0xa2; TRANSFER_COUNT];
422                 f.write_all(&buf, None).await.unwrap();
423             })
424             .unwrap();
425         });
426 
427         let (pipe_out, pipe_in) = sys_util::pipe(true).unwrap();
428         ex.run_until(async move {
429             tx.send(File::try_from(pipe_in).unwrap()).unwrap();
430 
431             let pipe = File::try_from(pipe_out).unwrap();
432             let mut buf = [0u8; TRANSFER_COUNT];
433             pipe.read_exact(&mut buf, None).await.unwrap();
434             assert_eq!(buf, [0xa2; TRANSFER_COUNT]);
435         })
436         .unwrap();
437 
438         worker_thread.join().unwrap();
439     }
440 
441     #[test]
fallocate()442     fn fallocate() {
443         let ex = Executor::new();
444         ex.run_until(async {
445             let dir = tempfile::TempDir::new().unwrap();
446             let mut file_path = PathBuf::from(dir.path());
447             file_path.push("test");
448 
449             let f = OpenOptions::new()
450                 .create(true)
451                 .write(true)
452                 .open(&file_path)
453                 .unwrap();
454             let source = File::try_from(f).unwrap();
455             source.allocate(0, 4096).await.unwrap();
456 
457             let meta_data = std::fs::metadata(&file_path).unwrap();
458             assert_eq!(meta_data.len(), 4096);
459         })
460         .unwrap();
461     }
462 }
463