use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::io::AsRawFd;
#[cfg(target_env = "musl")]
use libc::{c_int, lseek64, ENXIO};
#[cfg(target_env = "gnu")]
use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
#[cfg(all(not(target_env = "musl"), target_os = "android"))]
use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
pub trait SeekHole {
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>>;
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
}
#[cfg(target_env = "musl")]
const SEEK_DATA: c_int = 3;
#[cfg(target_env = "musl")]
const SEEK_HOLE: c_int = 4;
fn lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>> {
let res = unsafe { lseek64(file.as_raw_fd(), offset, whence) };
if res < 0 {
let err = Error::last_os_error();
if let Some(errno) = Error::raw_os_error(&err) {
if errno == ENXIO {
return Ok(None);
}
}
Err(err)
} else {
Ok(Some(res as u64))
}
}
impl SeekHole for File {
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
lseek(self, offset as i64, SEEK_HOLE)
}
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>> {
lseek(self, offset as i64, SEEK_DATA)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tempdir::TempDir;
use std::fs::File;
use std::io::{Seek, SeekFrom, Write};
use std::path::PathBuf;
fn seek_cur(file: &mut File) -> u64 {
file.stream_position().unwrap()
}
#[test]
fn seek_data() {
let tempdir = TempDir::new_with_prefix("/tmp/seek_data_test").unwrap();
let mut path = PathBuf::from(tempdir.as_path());
path.push("test_file");
let mut file = File::create(&path).unwrap();
assert_eq!(file.seek_data(0).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
file.set_len(0x10000).unwrap();
assert_eq!(file.seek_data(0).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_data(0x10000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_data(0x10001).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
let b = [0x55u8; 0x10000];
file.seek(SeekFrom::Start(0x10000)).unwrap();
file.write_all(&b).unwrap();
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
assert_eq!(seek_cur(&mut file), 0x10000);
assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
assert_eq!(seek_cur(&mut file), 0x10000);
assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
assert_eq!(seek_cur(&mut file), 0x10001);
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
assert_eq!(seek_cur(&mut file), 0x1FFFF);
file.set_len(0x30000).unwrap();
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
assert_eq!(seek_cur(&mut file), 0x10000);
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
assert_eq!(seek_cur(&mut file), 0x1FFFF);
assert_eq!(file.seek_data(0x20000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0x1FFFF);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn seek_hole() {
let tempdir = TempDir::new_with_prefix("/tmp/seek_hole_test").unwrap();
let mut path = PathBuf::from(tempdir.as_path());
path.push("test_file");
let mut file = File::create(&path).unwrap();
assert_eq!(file.seek_hole(0).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
file.set_len(0x10000).unwrap();
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
assert_eq!(seek_cur(&mut file), 0xFFFF);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x10000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_hole(0x10001).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
let b = [0x55u8; 0x10000];
file.seek(SeekFrom::Start(0x10000)).unwrap();
file.write_all(&b).unwrap();
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
assert_eq!(seek_cur(&mut file), 0xFFFF);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x20000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
file.set_len(0x30000).unwrap();
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
assert_eq!(seek_cur(&mut file), 0);
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
assert_eq!(seek_cur(&mut file), 0xFFFF);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
assert_eq!(seek_cur(&mut file), 0x20000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
assert_eq!(seek_cur(&mut file), 0x20001);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
file.seek(SeekFrom::Start(0x20000)).unwrap();
file.write_all(&b).unwrap();
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
assert_eq!(seek_cur(&mut file), 0x30000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
assert_eq!(seek_cur(&mut file), 0x30000);
file.rewind().unwrap();
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0);
}
}