use std::convert::TryFrom;
use std::fs::{File, Metadata};
use std::io;
use std::time::SystemTime;
pub trait FileExt {
fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>>;
}
impl FileExt for std::fs::File {
#[cfg(unix)]
fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>> {
use std::os::unix::io::AsRawFd;
let mut chunk = Vec::with_capacity(chunk_size);
let offset = libc::off_t::try_from(offset).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "offset too large")
})?;
let retval = unsafe {
libc::pread(
self.as_raw_fd(),
chunk.as_mut_ptr() as *mut libc::c_void,
chunk_size,
offset,
)
};
let bytes_read = usize::try_from(retval).map_err(|_| std::io::Error::last_os_error())?;
if bytes_read == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("no bytes beyond position {}", offset),
));
}
unsafe {
chunk.set_len(bytes_read);
}
Ok(chunk)
}
#[cfg(windows)]
fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>> {
use std::os::windows::io::AsRawHandle;
use winapi::shared::minwindef::DWORD;
let handle = self.as_raw_handle();
let mut read = 0;
let mut chunk = Vec::with_capacity(chunk_size);
unsafe {
let mut overlapped: winapi::um::minwinbase::OVERLAPPED = std::mem::zeroed();
overlapped.u.s_mut().Offset = offset as u32;
overlapped.u.s_mut().OffsetHigh = (offset >> 32) as u32;
if winapi::um::fileapi::ReadFile(
handle,
chunk.as_mut_ptr() as *mut winapi::ctypes::c_void,
DWORD::try_from(chunk_size).unwrap_or(DWORD::MAX), &mut read,
&mut overlapped,
) == 0
{
match winapi::um::errhandlingapi::GetLastError() {
#[allow(clippy::print_stderr)]
winapi::shared::winerror::ERROR_IO_PENDING => {
eprintln!("I/O error: operation failed to complete synchronously");
std::process::abort();
}
winapi::shared::winerror::ERROR_HANDLE_EOF => {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("no bytes beyond position {}", offset),
));
}
o => return Err(std::io::Error::from_raw_os_error(o as i32)),
}
}
chunk.set_len(usize::try_from(read).expect("u32 should fit in usize"));
}
Ok(chunk)
}
}
pub struct FileInfo {
pub inode: u64,
pub len: u64,
pub mtime: SystemTime,
}
#[cfg(windows)]
fn filetime_to_systemtime(time: winapi::shared::minwindef::FILETIME) -> SystemTime {
use std::time::{Duration, UNIX_EPOCH};
let ticks = (time.dwHighDateTime as u64) << 32 | time.dwLowDateTime as u64;
const SECS_TO_UNIX_EPOCH: u64 = 11_644_473_600;
let secs = ticks / 10_000_000 - SECS_TO_UNIX_EPOCH;
let nanos = (ticks % 10_000_000 * 100) as u32;
let duration = Duration::new(secs, nanos);
UNIX_EPOCH + duration
}
#[cfg(unix)]
pub fn file_info(_file: &File, metadata: &Metadata) -> io::Result<FileInfo> {
use std::os::unix::fs::MetadataExt;
let info = FileInfo {
inode: metadata.ino(),
len: metadata.len(),
mtime: metadata.modified()?,
};
Ok(info)
}
#[cfg(windows)]
pub fn file_info(file: &File, _metadata: &Metadata) -> io::Result<FileInfo> {
use std::os::windows::io::AsRawHandle;
use winapi::shared::minwindef::FILETIME;
use winapi::um::fileapi::{self, BY_HANDLE_FILE_INFORMATION};
let handle = file.as_raw_handle();
let zero_time = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let mut info = BY_HANDLE_FILE_INFORMATION {
dwFileAttributes: 0,
ftCreationTime: zero_time,
ftLastAccessTime: zero_time,
ftLastWriteTime: zero_time,
dwVolumeSerialNumber: 0,
nFileSizeHigh: 0,
nFileSizeLow: 0,
nNumberOfLinks: 0,
nFileIndexHigh: 0,
nFileIndexLow: 0,
};
let inode = if unsafe { fileapi::GetFileInformationByHandle(handle, &mut info) } != 0 {
(info.nFileIndexHigh as u64) << 32 | info.nFileIndexLow as u64
} else {
return Err(io::Error::last_os_error());
};
let mtime = filetime_to_systemtime(info.ftLastWriteTime);
let len = (info.nFileSizeHigh as u64) << 32 | info.nFileSizeLow as u64;
let info = FileInfo { inode, len, mtime };
Ok(info)
}