#![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,
};
let ret = unsafe { libc::lseek64(fd.as_raw_descriptor(), offset as i64, whence) };
if ret < 0 {
return Err(Error::last());
}
Ok(ret as u64)
}
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)))
}
pub struct FileDataIterator<'a> {
fd: &'a dyn AsRawDescriptor,
offset: u64,
end: u64,
failed: bool,
}
impl<'a> FileDataIterator<'a> {
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));
}
}