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