| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #![deny(missing_docs)] | |
| use std::ops::Range; | |
| use crate::AsRawDescriptor; | |
| use crate::Error; | |
| use crate::Result; | |
| enum LseekOption { | |
| Data, | |
| Hole, | |
| } | |
| fn lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64> { | |
| let whence = match option { | |
| LseekOption::Data => libc::SEEK_DATA, | |
| LseekOption::Hole => libc::SEEK_HOLE, | |
| }; | |
| // SAFETY: | |
| // safe because this doesn't modify any memory. | |
| let ret = unsafe { libc::lseek64(fd.as_raw_descriptor(), offset as i64, whence) }; | |
| if ret < 0 { | |
| return Err(Error::last()); | |
| } | |
| Ok(ret as u64) | |
| } | |
| /// Find the offset range of the next data in the file. | |
| /// | |
| /// # Arguments | |
| /// | |
| /// * `fd` - the [trait@AsRawDescriptor] of the file | |
| /// * `offset` - the offset to start traversing from | |
| /// * `len` - the len of the region over which to traverse | |
| pub fn find_next_data( | |
| fd: &dyn AsRawDescriptor, | |
| offset: u64, | |
| len: u64, | |
| ) -> Result<Option<Range<u64>>> { | |
| let end = offset + len; | |
| let offset_data = match lseek(fd, offset, LseekOption::Data) { | |
| Ok(offset) => { | |
| if offset >= end { | |
| return Ok(None); | |
| } else { | |
| offset | |
| } | |
| } | |
| Err(e) => { | |
| return match e.errno() { | |
| libc::ENXIO => Ok(None), | |
| _ => Err(e), | |
| } | |
| } | |
| }; | |
| let offset_hole = lseek(fd, offset_data, LseekOption::Hole)?; | |
| Ok(Some(offset_data..offset_hole.min(end))) | |
| } | |
| /// Iterator returning the offset range of data in the file. | |
| /// | |
| /// This uses `lseek(2)` internally, and thus it changes the file offset. | |
| pub struct FileDataIterator<'a> { | |
| fd: &'a dyn AsRawDescriptor, | |
| offset: u64, | |
| end: u64, | |
| failed: bool, | |
| } | |
| impl<'a> FileDataIterator<'a> { | |
| /// Creates the [FileDataIterator] | |
| /// | |
| /// # Arguments | |
| /// | |
| /// * `fd` - the [trait@AsRawDescriptor] of the file | |
| /// * `offset` - the offset to start traversing from. | |
| /// * `len` - the len of the region over which to iterate | |
| pub fn new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self { | |
| Self { | |
| fd, | |
| offset, | |
| end: offset + len, | |
| failed: false, | |
| } | |
| } | |
| } | |
| impl<'a> Iterator for FileDataIterator<'a> { | |
| type Item = Result<Range<u64>>; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| if self.failed { | |
| return None; | |
| } | |
| match find_next_data(self.fd, self.offset, self.end - self.offset) { | |
| Ok(data_range) => { | |
| if let Some(ref data_range) = data_range { | |
| self.offset = data_range.end; | |
| } | |
| data_range.map(Ok) | |
| } | |
| Err(e) => { | |
| self.failed = true; | |
| Some(Err(e)) | |
| } | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use std::os::unix::fs::FileExt; | |
| use super::*; | |
| use crate::pagesize; | |
| #[test] | |
| fn file_data_iterator() { | |
| let file = tempfile::tempfile().unwrap(); | |
| file.write_at(&[1_u8], 10).unwrap(); | |
| file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap(); | |
| file.write_at(&[1_u8], (4 * pagesize() - 1) as u64).unwrap(); | |
| let iter = FileDataIterator::new(&file, 0, 4 * pagesize() as u64); | |
| let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap(); | |
| assert_eq!(result.len(), 2); | |
| assert_eq!(result[0], 0..(pagesize() as u64)); | |
| assert_eq!(result[1], (2 * pagesize() as u64)..(4 * pagesize() as u64)); | |
| } | |
| #[test] | |
| fn file_data_iterator_subrange() { | |
| let file = tempfile::tempfile().unwrap(); | |
| file.write_at(&[1_u8], 0).unwrap(); | |
| file.write_at(&[1_u8], pagesize() as u64).unwrap(); | |
| file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap(); | |
| file.write_at(&[1_u8], 4 * pagesize() as u64).unwrap(); | |
| let iter = FileDataIterator::new(&file, pagesize() as u64, pagesize() as u64); | |
| let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap(); | |
| assert_eq!(result.len(), 1); | |
| assert_eq!(result[0], (pagesize() as u64)..(2 * pagesize() as u64)); | |
| } | |
| } |