1mod sector_reader;
13use anyhow::{bail, Context, Result};
14
15use ntfs::indexes::NtfsFileNameIndex;
16use ntfs::structured_values::{
17 NtfsFileName, NtfsFileNamespace,
18};
19use ntfs::{Ntfs, NtfsFile, NtfsReadSeek};
20use sector_reader::SectorReader;
21
22use std::collections::VecDeque;
23use std::fs::{File, OpenOptions};
24
25use std::io::{BufReader, Read, Seek, Write};
26use std::path::{Component, PathBuf};
27
28use std::{path};
29
30struct CommandInfo<'n, T>
31where
32 T: Read + Seek,
33{
34 current_directory: Vec<NtfsFile<'n>>,
35 current_directory_string: String,
36 fs: T,
37 ntfs: &'n Ntfs,
38}
39pub fn rawcopy(file_path: &str, save_path: &str) -> Result<()> {
50 let path = path::PathBuf::from(file_path);
51
52 let mut components: VecDeque<_> = path
55 .components()
56 .filter_map(|comp| {
57 if Component::RootDir != comp {
58 Some(comp.as_os_str().to_str().unwrap())
59 } else {
60 None
61 }
62 })
63 .collect();
64
65 let prefix = components.pop_front().unwrap();
66 let file = components.pop_back().unwrap();
67
68 let prefix = format!(r"\\.\{prefix}");let f = File::open(prefix)?;
71 let sr = SectorReader::new(f, 4096)?;
72 let mut fs = BufReader::new(sr);
73 let mut ntfs = Ntfs::new(&mut fs)?;
74 ntfs.read_upcase_table(&mut fs)?;
75 let current_directory = vec![ntfs.root_directory(&mut fs)?];
76
77 let mut info = CommandInfo {
78 current_directory,
79 current_directory_string: String::new(),
80 fs,
81 ntfs: &ntfs,
82 };
83
84 for ele in components {
85 cd(ele, &mut info)?;
87 }
88
89 get(file, save_path, &mut info)?;
90 Ok(())
91}
92fn best_file_name<T>(
93 info: &mut CommandInfo<T>,
94 file: &NtfsFile,
95 parent_record_number: u64,
96) -> Result<NtfsFileName>
97where
98 T: Read + Seek,
99{
100 let priority = [
104 Some(NtfsFileNamespace::Win32),
105 Some(NtfsFileNamespace::Win32AndDos),
106 None,
107 ];
108
109 for match_namespace in priority {
110 if let Some(file_name) =
111 file.name(&mut info.fs, match_namespace, Some(parent_record_number))
112 {
113 let file_name = file_name?;
114 return Ok(file_name);
115 }
116 }
117
118 bail!(
119 "Found no FileName attribute for File Record {:#x}",
120 file.file_record_number()
121 )
122}
123fn cd<T>(arg: &str, info: &mut CommandInfo<T>) -> Result<()>
124where
125 T: Read + Seek,
126{
127 if arg.is_empty() {
128 return Ok(());
129 }
130
131 if arg == ".." {
132 if info.current_directory_string.is_empty() {
133 return Ok(());
134 }
135
136 info.current_directory.pop();
137
138 let new_len = info.current_directory_string.rfind('\\').unwrap_or(0);
139 info.current_directory_string.truncate(new_len);
140 } else {
141 let index = info
142 .current_directory
143 .last()
144 .unwrap()
145 .directory_index(&mut info.fs)?;
146 let mut finder = index.finder();
147 let maybe_entry = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg);
148
149 if maybe_entry.is_none() {
150 log::error!("Cannot find subdirectory \"{arg}\".");
151 return Ok(());
152 }
153
154 let entry = maybe_entry.unwrap()?;
155 let file_name = entry
156 .key()
157 .expect("key must exist for a found Index Entry")?;
158
159 if !file_name.is_directory() {
160 log::error!("\"{arg}\" is not a directory.");
161 return Ok(());
162 }
163
164 let file = entry.to_file(info.ntfs, &mut info.fs)?;
165 let file_name = best_file_name(
166 info,
167 &file,
168 info.current_directory.last().unwrap().file_record_number(),
169 )?;
170 if !info.current_directory_string.is_empty() {
171 info.current_directory_string += "\\";
172 }
173 info.current_directory_string += &file_name.name().to_string_lossy();
174
175 info.current_directory.push(file);
176 }
177
178 Ok(())
179}
180fn get<T>(file: &str, save_path: &str, info: &mut CommandInfo<T>) -> Result<()>
181where
182 T: Read + Seek,
183{
184 let (file_name, data_stream_name) = match file.find(':') {
186 Some(mid) => (&file[..mid], &file[mid + 1..]),
187 None => (file, ""),
188 };
189
190 let output_file_name = if data_stream_name.is_empty() {
193 file_name.to_string()
194 } else {
195 format!("{file_name}_{data_stream_name}")
196 };
197 let output_file_path = [save_path, &output_file_name].iter().collect::<PathBuf>();
198 let mut output_file = OpenOptions::new()
199 .write(true)
200 .create_new(true)
201 .open(&output_file_path)
202 .with_context(|| format!("Tried to open \"{output_file_name}\" for writing"))?;
203
204 let file = parse_file_arg(file_name, info)?;
206 let data_item = match file.data(&mut info.fs, data_stream_name) {
207 Some(data_item) => data_item,
208 None => {
209 log::error!("The file does not have a \"{data_stream_name}\" $DATA attribute.");
210 return Ok(());
211 }
212 };
213 let data_item = data_item?;
214 let data_attribute = data_item.to_attribute()?;
215 let mut data_value = data_attribute.value(&mut info.fs)?;
216
217 log::debug!(
218 "Saving {} bytes of data in \"{}\"...",
219 data_value.len(),
220 output_file_name
221 );
222 let mut buf = [0u8; 4096];
223
224 loop {
225 let bytes_read = data_value.read(&mut info.fs, &mut buf)?;
226 if bytes_read == 0 {
227 break;
228 }
229
230 output_file.write_all(&buf[..bytes_read])?;
231 }
232 log::debug!("Done! save to {}", &output_file_path.to_str().unwrap());
233 Ok(())
234}
235#[allow(clippy::from_str_radix_10)]
236fn parse_file_arg<'n, T>(arg: &str, info: &mut CommandInfo<'n, T>) -> Result<NtfsFile<'n>>
237where
238 T: Read + Seek,
239{
240 if arg.is_empty() {
241 bail!("Missing argument!");
242 }
243
244 if let Some(record_number_arg) = arg.strip_prefix('/') {
245 let record_number = match record_number_arg.strip_prefix("0x") {
246 Some(hex_record_number_arg) => u64::from_str_radix(hex_record_number_arg, 16),
247 None => u64::from_str_radix(record_number_arg, 10),
248 };
249
250 if let Ok(record_number) = record_number {
251 let file = info.ntfs.file(&mut info.fs, record_number)?;
252 Ok(file)
253 } else {
254 bail!(
255 "Cannot parse record number argument \"{}\"",
256 record_number_arg
257 )
258 }
259 } else {
260 let index = info
261 .current_directory
262 .last()
263 .unwrap()
264 .directory_index(&mut info.fs)?;
265 let mut finder = index.finder();
266
267 if let Some(entry) = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg) {
268 let entry = entry?;
269 let file = entry.to_file(info.ntfs, &mut info.fs)?;
270 Ok(file)
271 } else {
272 bail!("No such file or directory \"{}\".", arg)
273 }
274 }
275}