use std::ffi::c_int;
use std::io::Error;
use std::os::fd::RawFd;
use libc::{ENXIO, SEEK_END};
use nix::errno::errno;
use nix::libc::{SEEK_DATA, SEEK_HOLE};
use super::*;
type SeekWhence = c_int;
pub fn seek_hole_whence(
fd: RawFd,
offset: i64,
whence: impl Into<SeekWhence>,
) -> io::Result<Option<RegionOffset>> {
match lseek(fd, offset, whence) {
Ok(offset) => Ok(Some(offset as RegionOffset)),
Err(errno) => {
if errno == ENXIO {
Ok(None)
} else {
Err(Error::from_raw_os_error(errno))
}
}
}
}
fn lseek(fd: RawFd, offset: i64, whence: impl Into<SeekWhence>) -> Result<RegionOffset, i32> {
let new_offset = unsafe { nix::libc::lseek(fd, offset, whence.into()) };
if new_offset == -1 {
return Err(errno());
}
Ok(new_offset as RegionOffset)
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RegionType {
Hole,
Data,
}
impl From<RegionType> for SeekWhence {
fn from(value: RegionType) -> Self {
match value {
Hole => SEEK_HOLE,
Data => SEEK_DATA,
}
}
}
impl std::ops::Not for RegionType {
type Output = RegionType;
fn not(self) -> Self::Output {
match self {
Hole => Data,
Data => Hole,
}
}
}
pub use RegionType::*;
pub type RegionOffset = u64;
#[derive(Debug, Clone, PartialEq)]
pub struct Region {
pub region_type: RegionType,
pub start: RegionOffset,
pub end: RegionOffset,
}
impl Region {
pub fn length(&self) -> RegionOffset {
self.end - self.start
}
}
pub fn file_regions(file: &mut File) -> Result<Vec<Region>> {
let fd = file.as_raw_fd();
let mut offsets = vec![];
{
let mut offset = 0;
let mut whence = Data;
loop {
let new_offset = match seek_hole_whence(fd, offset as i64, whence)? {
Some(a) => a,
None => match whence {
Hole => break,
Data => file.seek(End(0))?,
},
};
offsets.push((new_offset, whence));
whence = !whence;
offset = new_offset;
}
}
offsets.sort_by_key(|tuple| tuple.0);
let mut last_offset = 0;
let mut last_type = Hole;
let mut output = vec![];
for (offset, region_type) in offsets {
let region = Region {
region_type: last_type,
start: last_offset,
end: offset,
};
last_type = region_type;
if region.length() == 0 {
continue;
}
last_offset = offset;
output.push(region);
}
assert_eq!(output, regions_iter_to_vec(file).unwrap());
Ok(output)
}
pub struct Iter {
last_whence: RegionType,
offset: RegionOffset,
fd: RawFd,
}
impl Iter {
pub fn new(fd: RawFd) -> Self {
Self {
last_whence: Data,
offset: 0,
fd,
}
}
}
impl Iterator for Iter {
type Item = io::Result<Region>;
fn next(&mut self) -> Option<Self::Item> {
let first_whence = !self.last_whence;
let mut whence = first_whence;
loop {
match seek_hole_whence(self.fd, self.offset as i64, whence) {
Ok(Some(offset)) if offset != self.offset => {
let region = Region {
region_type: !whence,
start: self.offset,
end: offset,
};
self.last_whence = whence;
self.offset = offset;
return Some(Ok(region));
}
Err(err) => return Some(Err(err)),
Ok(None | Some(_)) => {}
}
whence = !whence;
if whence == first_whence {
break;
}
}
match lseek(self.fd, 0, SEEK_END) {
Err(errno) => Some(Err(Error::from_raw_os_error(errno))),
Ok(offset) => {
if offset == self.offset {
None
} else {
let region = Region {
region_type: Hole,
start: self.offset,
end: offset,
};
self.last_whence = Hole;
self.offset = offset;
Some(Ok(region))
}
}
}
}
}
fn regions_iter_to_vec(file: &mut File) -> io::Result<Vec<Region>> {
let fd = file.as_raw_fd();
let itered: Vec<_> = Iter::new(fd).collect::<io::Result<Vec<_>>>()?;
Ok(itered)
}
#[cfg(test)]
mod tests {
use std::env::temp_dir;
use std::os::fd::AsRawFd;
use super::*;
use crate::pathconf::path_min_hole_size;
use crate::testing::write_random_tempfile;
fn get_regions(file: &mut File) -> Result<Vec<Region>> {
let fd = file.as_raw_fd();
let itered: Vec<_> = Iter::new(fd).collect::<io::Result<Vec<_>>>()?;
let vec = file_regions(file)?;
assert_eq!(itered, vec);
Ok(vec)
}
#[self::test]
fn just_a_hole() -> Result<()> {
let os_temp_dir = temp_dir();
let mut min_hole_size = path_min_hole_size(&os_temp_dir)?;
if min_hole_size <= 1 {
min_hole_size = 2;
}
let mut file = write_random_tempfile(min_hole_size)?;
let regions = get_regions(file.as_file_mut())?;
assert_eq!(
regions,
vec![Region {
region_type: Data,
start: 0,
end: min_hole_size
}]
);
punchfile(file.as_raw_fd(), 0, min_hole_size as libc::off_t)?;
file.seek(Start(0))?;
let regions: Vec<_> = get_regions(file.as_file_mut())?;
assert_eq!(
regions,
vec![Region {
region_type: Hole,
start: 0,
end: min_hole_size
}]
);
Ok(())
}
}