1use std::borrow::Cow;
2use std::io::{Read, Write};
3use std::path::{Path, PathBuf};
4
5use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
6
7use crate::Result;
8use crate::error::{Error, FileOperationError, InvalidParameterError};
9use crate::utils::OwnedHandle;
10
11use super::builder::RawFileBuilder;
12use super::win::{
13 Buffer, PointerExtent, RetrievalPointersBuffer, get_drive_metadata, get_file_pointer_and_size,
14 get_retrieval_pointers, move_disk_position, read_file_from_disk_pointer,
15};
16
17pub struct RawFile {
22 source_path: PathBuf,
23 disk_handle: OwnedHandle,
24 file_size: u64,
25 retrieval_pointers: RetrievalPointersBuffer,
26 bytes_per_cluster: usize,
27 clusters_per_read: usize,
28 extent_index: usize,
29 bytes_read: usize,
30 cluster_index: usize,
31}
32
33impl RawFile {
34 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
44 Self::open_with_tuning(path, 16, 32_000)
45 }
46
47 pub fn builder() -> RawFileBuilder {
51 RawFileBuilder::new()
52 }
53
54 pub(crate) fn open_with_tuning<P: AsRef<Path>>(
56 path: P,
57 clusters_per_read: usize,
58 metadata_buffer_capacity: usize,
59 ) -> Result<Self> {
60 let source_path = path.as_ref().to_path_buf();
61 if source_path.as_os_str().is_empty() {
62 return Err(Error::InvalidParameter(InvalidParameterError::new(
63 "path",
64 "Raw file path cannot be empty",
65 )));
66 }
67
68 let mut work_buffer = Buffer::with_capacity(metadata_buffer_capacity.max(4096));
69
70 let (disk_handle, sectors_in_cluster, bytes_per_sector) =
71 get_drive_metadata(&source_path, &mut work_buffer)?;
72
73 let (file_metadata_handle, file_size) =
74 get_file_pointer_and_size(&source_path, &mut work_buffer)?;
75
76 let metadata_handle = OwnedHandle::with_ownership(file_metadata_handle, true);
77 let retrieval_pointers = get_retrieval_pointers(metadata_handle.raw(), &mut work_buffer)?;
78
79 Ok(Self {
80 source_path,
81 disk_handle: OwnedHandle::with_ownership(disk_handle, true),
82 file_size,
83 retrieval_pointers,
84 bytes_per_cluster: (bytes_per_sector * sectors_in_cluster) as usize,
85 clusters_per_read: clusters_per_read.max(1),
86 extent_index: 0,
87 bytes_read: 0,
88 cluster_index: 0,
89 })
90 }
91
92 pub fn copy_to<P: AsRef<Path>>(&self, destination: P) -> Result<()> {
104 let destination_path = destination.as_ref();
105
106 let mut source = RawFile::builder()
107 .path(&self.source_path)
108 .clusters_per_read(self.clusters_per_read)
109 .open()?;
110
111 let mut read_buffer = vec![0u8; source.bytes_per_cluster * source.clusters_per_read];
112
113 let file = std::fs::File::create(destination_path).map_err(|e| {
114 file_op_error_with_io_code(destination_path, "create destination file", e)
115 })?;
116
117 let mut writer = std::io::BufWriter::new(file);
118
119 loop {
120 let read = source.read(&mut read_buffer).map_err(|e| {
121 file_op_error_with_io_code(&self.source_path, "read source file", e)
122 })?;
123
124 if read == 0 {
125 break;
126 }
127
128 writer.write_all(&read_buffer[..read]).map_err(|e| {
129 file_op_error_with_io_code(destination_path, "write destination file", e)
130 })?;
131 }
132
133 writer.flush().map_err(|e| {
134 file_op_error_with_io_code(destination_path, "flush destination file", e)
135 })?;
136
137 Ok(())
138 }
139
140 fn current_extent(&self) -> Option<&PointerExtent> {
141 self.retrieval_pointers.extents.get(self.extent_index)
142 }
143}
144
145impl Read for RawFile {
146 fn read(&mut self, out_buffer: &mut [u8]) -> std::io::Result<usize> {
147 if out_buffer.len() < self.bytes_per_cluster {
148 return Err(std::io::Error::from_raw_os_error(
149 ERROR_INSUFFICIENT_BUFFER.0 as i32,
150 ));
151 }
152
153 if self.bytes_read >= self.file_size as usize {
154 return Ok(0);
155 }
156
157 while let Some(extent) = self.current_extent() {
158 let previous_vcn = if self.extent_index > 0 {
159 self.retrieval_pointers.extents[self.extent_index - 1].next_vcn
160 } else {
161 self.retrieval_pointers.starting_vcn
162 };
163
164 if extent.next_vcn < previous_vcn {
165 return Err(std::io::Error::new(
166 std::io::ErrorKind::InvalidData,
167 "Retrieval pointers are not monotonic",
168 ));
169 }
170
171 let extent_cluster_count = (extent.next_vcn - previous_vcn) as usize;
172 if self.cluster_index >= extent_cluster_count {
173 self.extent_index += 1;
174 self.cluster_index = 0;
175 continue;
176 }
177
178 let clusters_available = extent_cluster_count - self.cluster_index;
179 let cluster_capacity = out_buffer.len() / self.bytes_per_cluster;
180 let clusters_to_read = clusters_available.min(cluster_capacity);
181
182 if clusters_to_read == 0 {
183 return Ok(0);
184 }
185
186 let disk_offset =
187 (extent.lcn + self.cluster_index as i64) * self.bytes_per_cluster as i64;
188 move_disk_position(self.disk_handle.raw(), disk_offset)?;
189
190 let bytes_to_read = (clusters_to_read * self.bytes_per_cluster) as u32;
191 let mut read_bytes =
192 read_file_from_disk_pointer(self.disk_handle.raw(), out_buffer, bytes_to_read)?;
193
194 if self.bytes_read + read_bytes as usize > self.file_size as usize {
195 read_bytes = (self.file_size - self.bytes_read as u64) as u32;
196 }
197
198 self.bytes_read += read_bytes as usize;
199
200 if clusters_to_read == clusters_available {
201 self.extent_index += 1;
202 self.cluster_index = 0;
203 } else {
204 self.cluster_index += clusters_to_read;
205 }
206
207 return Ok(read_bytes as usize);
208 }
209
210 Ok(0)
211 }
212}
213
214fn file_op_error_with_io_code(
215 path: &Path,
216 operation: &'static str,
217 error: std::io::Error,
218) -> Error {
219 let path_text = Cow::Owned(path.to_string_lossy().to_string());
220 if let Some(code) = error.raw_os_error() {
221 Error::FileOperation(FileOperationError::with_code(path_text, operation, code))
222 } else {
223 Error::FileOperation(FileOperationError::new(path_text, operation))
224 }
225}