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