1use std::convert::TryFrom;
10use std::fs::{File, Metadata};
11use std::io;
12use std::time::SystemTime;
13
14pub trait FileExt {
15 fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>>;
28}
29
30impl FileExt for std::fs::File {
31 #[cfg(unix)]
32 fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>> {
33 use std::os::unix::io::AsRawFd;
34
35 let mut chunk = Vec::with_capacity(chunk_size);
36 let offset = libc::off_t::try_from(offset).map_err(|_| {
37 std::io::Error::new(std::io::ErrorKind::InvalidInput, "offset too large")
38 })?;
39
40 let retval = unsafe {
42 libc::pread(
43 self.as_raw_fd(),
44 chunk.as_mut_ptr() as *mut libc::c_void,
45 chunk_size,
46 offset,
47 )
48 };
49 let bytes_read = usize::try_from(retval).map_err(|_| std::io::Error::last_os_error())?;
50
51 if bytes_read == 0 {
52 return Err(std::io::Error::new(
53 std::io::ErrorKind::UnexpectedEof,
54 format!("no bytes beyond position {}", offset),
55 ));
56 }
57
58 unsafe {
60 chunk.set_len(bytes_read);
61 }
62 Ok(chunk)
63 }
64
65 #[cfg(windows)]
66 fn read_at(&self, chunk_size: usize, offset: u64) -> io::Result<Vec<u8>> {
67 use std::os::windows::io::AsRawHandle;
71 use winapi::shared::minwindef::DWORD;
72 let handle = self.as_raw_handle();
73 let mut read = 0;
74 let mut chunk = Vec::with_capacity(chunk_size);
75
76 unsafe {
77 let mut overlapped: winapi::um::minwinbase::OVERLAPPED = std::mem::zeroed();
79 overlapped.u.s_mut().Offset = offset as u32;
80 overlapped.u.s_mut().OffsetHigh = (offset >> 32) as u32;
81
82 if winapi::um::fileapi::ReadFile(
84 handle,
85 chunk.as_mut_ptr() as *mut winapi::ctypes::c_void,
86 DWORD::try_from(chunk_size).unwrap_or(DWORD::MAX), &mut read,
88 &mut overlapped,
89 ) == 0
90 {
91 match winapi::um::errhandlingapi::GetLastError() {
92 #[allow(clippy::print_stderr)]
93 winapi::shared::winerror::ERROR_IO_PENDING => {
94 eprintln!("I/O error: operation failed to complete synchronously");
97 std::process::abort();
98 }
99 winapi::shared::winerror::ERROR_HANDLE_EOF => {
100 return Err(std::io::Error::new(
103 std::io::ErrorKind::UnexpectedEof,
104 format!("no bytes beyond position {}", offset),
105 ));
106 }
107 o => return Err(std::io::Error::from_raw_os_error(o as i32)),
108 }
109 }
110
111 chunk.set_len(usize::try_from(read).expect("u32 should fit in usize"));
113 }
114 Ok(chunk)
115 }
116}
117
118pub struct FileInfo {
119 pub inode: u64,
120 pub len: u64,
121 pub mtime: SystemTime,
122}
123
124#[cfg(windows)]
125fn filetime_to_systemtime(time: winapi::shared::minwindef::FILETIME) -> SystemTime {
130 use std::time::{Duration, UNIX_EPOCH};
131
132 let ticks = (time.dwHighDateTime as u64) << 32 | time.dwLowDateTime as u64;
133
134 const SECS_TO_UNIX_EPOCH: u64 = 11_644_473_600;
136 let secs = ticks / 10_000_000 - SECS_TO_UNIX_EPOCH;
137 let nanos = (ticks % 10_000_000 * 100) as u32;
138
139 let duration = Duration::new(secs, nanos);
140 UNIX_EPOCH + duration
141}
142
143#[cfg(unix)]
144pub fn file_info(_file: &File, metadata: &Metadata) -> io::Result<FileInfo> {
145 use std::os::unix::fs::MetadataExt;
146
147 let info = FileInfo {
148 inode: metadata.ino(),
149 len: metadata.len(),
150 mtime: metadata.modified()?,
151 };
152
153 Ok(info)
154}
155
156#[cfg(windows)]
160pub fn file_info(file: &File, _metadata: &Metadata) -> io::Result<FileInfo> {
161 use std::os::windows::io::AsRawHandle;
162 use winapi::shared::minwindef::FILETIME;
163 use winapi::um::fileapi::{self, BY_HANDLE_FILE_INFORMATION};
164
165 let handle = file.as_raw_handle();
166 let zero_time = FILETIME {
167 dwLowDateTime: 0,
168 dwHighDateTime: 0,
169 };
170 let mut info = BY_HANDLE_FILE_INFORMATION {
171 dwFileAttributes: 0,
172 ftCreationTime: zero_time,
173 ftLastAccessTime: zero_time,
174 ftLastWriteTime: zero_time,
175 dwVolumeSerialNumber: 0,
176 nFileSizeHigh: 0,
177 nFileSizeLow: 0,
178 nNumberOfLinks: 0,
179 nFileIndexHigh: 0,
180 nFileIndexLow: 0,
181 };
182
183 let inode = if unsafe { fileapi::GetFileInformationByHandle(handle, &mut info) } != 0 {
184 (info.nFileIndexHigh as u64) << 32 | info.nFileIndexLow as u64
185 } else {
186 return Err(io::Error::last_os_error());
187 };
188 let mtime = filetime_to_systemtime(info.ftLastWriteTime);
189 let len = (info.nFileSizeHigh as u64) << 32 | info.nFileSizeLow as u64;
190
191 let info = FileInfo { inode, len, mtime };
192 Ok(info)
193}