use super::*;
use std::fs::File;
use std::io::Error;
use std::os::unix::io::AsRawFd;
use errno::errno;
use libc::{c_int, lseek, off_t, EINVAL, ENXIO, SEEK_END};
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")]{
const SEEK_HOLE: c_int = 3;
const SEEK_DATA: c_int = 4;
} else {
use libc::{SEEK_DATA, SEEK_HOLE};
}
}
impl SparseFile for File {
fn scan_chunks(&mut self) -> Result<Vec<Segment>, ScanError> {
let mut tags: Vec<Segment> = Vec::new();
let fd = self.as_raw_fd();
let end = safe_lseek(fd, 0, SEEK_END)?.unwrap_or(0);
if end == 0 {
return Ok(vec![]);
}
let mut last_seek = safe_lseek(fd, 0, SEEK_HOLE)?.unwrap_or(end);
let mut last_type = SegmentType::Hole;
if last_seek > 0 {
tags.push(Segment {
segment_type: SegmentType::Data,
range: 0..last_seek,
})
}
while last_seek < end {
let seek_type = match last_type {
SegmentType::Hole => SEEK_DATA,
SegmentType::Data => SEEK_HOLE,
};
let next_seek = safe_lseek(fd, last_seek, seek_type)?.unwrap_or(end);
tags.push(Segment {
segment_type: last_type,
range: last_seek..next_seek,
});
last_seek = next_seek;
last_type = last_type.opposite();
}
Ok(tags)
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd",))]
fn drill_hole(&self, start: u64, end: u64) -> Result<(), ScanError> {
unsafe {
use libc::{fallocate, FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE};
use std::io::Error;
use std::os::unix::io::AsRawFd;
if fallocate(
self.as_raw_fd(),
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
start as libc::off_t,
(end - start) as libc::off_t,
) < 0
{
return Err(Error::last_os_error().into());
}
}
Ok(())
}
#[cfg(target_os = "macos")]
fn drill_hole(&self, start: u64, end: u64) -> Result<(), ScanError> {
use libc::fcntl;
use std::os::unix::io::AsRawFd;
#[repr(C)]
struct fpunchhole_t {
fp_flags: c_int,
reserved: c_int,
fp_offset: u64,
fp_length: u64,
}
const F_PUNCHHOLE: c_int = 99;
let hole = fpunchhole_t {
fp_flags: 0,
reserved: 0,
fp_offset: start,
fp_length: (end - start),
};
unsafe {
let ret = fcntl(self.as_raw_fd(), F_PUNCHHOLE, &hole);
if ret < 0 {
return Err(Error::last_os_error().into());
}
}
Ok(())
}
}
fn safe_lseek(fd: c_int, offset: u64, seek_type: c_int) -> Result<Option<u64>, ScanError> {
unsafe {
let new_offset = lseek(fd, offset as off_t, seek_type);
if new_offset < 0 {
let errno = errno().into();
match errno {
EINVAL => Err(ScanError::UnsupportedFileSystem),
ENXIO => Ok(None),
_ => Err(Error::last_os_error().into()),
}
} else {
Ok(Some(new_offset as u64))
}
}
}