Skip to main content

windows_erg/file/
raw.rs

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
17/// Low-level reader for file content through raw disk cluster reads.
18///
19/// This type is Windows-only and intended for privileged scenarios where
20/// reading file extents directly from disk is required.
21pub 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    /// Open a raw file reader with default tuning values.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error when:
39    /// - `path` is empty or not a drive-qualified path,
40    /// - source file metadata cannot be opened,
41    /// - raw volume metadata cannot be queried,
42    /// - retrieval pointer data is unavailable for the file.
43    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
44        Self::open_with_tuning(path, 16, 32_000)
45    }
46
47    /// Create a builder for opening a raw file reader.
48    ///
49    /// This is the preferred entry point when custom read tuning is needed.
50    pub fn builder() -> RawFileBuilder {
51        RawFileBuilder::new()
52    }
53
54    /// Open a raw file reader with custom tuning values.
55    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    /// Copy this source file into the given destination path.
93    ///
94    /// The destination is created or truncated.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error when:
99    /// - the destination cannot be created,
100    /// - source data cannot be read from raw extents,
101    /// - destination writes or flush fail,
102    /// - the current process does not have required privileges.
103    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}