1use std::collections::BTreeMap;
7use chrono::{NaiveDate,NaiveTime};
8use log::{debug,warn,trace};
9use super::types::*;
10use crate::fs::{FileImage,Attributes};
11use crate::{STDRESULT,DYNERR};
12
13use a2kit_macro::{DiskStructError,DiskStruct};
17use a2kit_macro_derive::DiskStruct;
18
19pub const DIR_ENTRY_SIZE: usize = 32;
21const FREE: u8 = 0xe5;
23const FREE_AND_NO_MORE: u8 = 0x00;
25
26pub const READ_ONLY: u8 = 1;
27pub const HIDDEN: u8 = 2;
28pub const SYSTEM: u8 = 4;
29pub const VOLUME_ID: u8 = 8;
30pub const DIRECTORY: u8 = 16;
31pub const ARCHIVE: u8 = 32;
32pub const LONG_NAME: u8 = 15;
33fn update_attrib(curr: u8,what: Attributes) -> u8 {
36 let mut ans = curr;
37 let mut change_flag = |setting: bool,flag: u8| {
38 if setting {
39 ans |= flag;
40 } else {
41 ans &= u8::MAX ^ flag;
42 }
43 };
44 if let Some(setting) = what.backup {
45 change_flag(setting,ARCHIVE);
46 }
47 if let Some(setting) = what.write {
48 change_flag(!setting,READ_ONLY);
49 }
50 if let Some(setting) = what.hidden {
51 change_flag(setting,HIDDEN);
52 }
53 if let Some(setting) = what.system {
54 change_flag(setting,SYSTEM);
55 }
56 if let Some(setting) = what.dir {
57 change_flag(setting,DIRECTORY);
58 }
59 if let Some(setting) = what.vol {
60 change_flag(setting,VOLUME_ID);
61 }
62 ans
63}
64
65#[derive(Clone)]
69pub struct FileInfo {
70 pub wildcard: String,
71 pub idx: usize,
72 pub name: String,
73 pub typ: String,
74 pub read_only: bool,
75 pub hidden: bool,
76 pub system: bool,
77 pub volume_id: bool,
78 pub directory: bool,
79 pub archived: bool,
80 pub create_date: Option<NaiveDate>,
81 pub create_time: Option<NaiveTime>,
82 pub write_date: Option<NaiveDate>,
83 pub write_time: Option<NaiveTime>,
84 pub access_date: Option<NaiveDate>,
85 pub eof: usize,
86 pub cluster1: Option<Ptr>
87}
88
89#[derive(PartialEq)]
90pub enum EntryType {
91 Free,
92 FreeAndNoMore,
93 File,
94 Directory,
95 VolumeLabel,
96 LongName
97}
98
99pub struct EntryLocation {
101 pub cluster1: Option<Ptr>,
103 pub entry: Ptr,
105 pub dir: Directory
107}
108
109#[derive(DiskStruct)]
110pub struct Entry {
111 name: [u8;8],
112 ext: [u8;3],
113 attr: u8,
117 nt_res: u8,
118 creation_tenth: u8,
120 creation_time: [u8;2],
122 creation_date: [u8;2],
123 access_date: [u8;2],
124 cluster1_high: [u8;2],
125 write_time: [u8;2],
127 write_date: [u8;2],
129 cluster1_low: [u8;2],
130 file_size: [u8;4]
131}
132
133pub struct Directory {
135 entries: Vec<[u8;DIR_ENTRY_SIZE]>
136}
137
138impl FileInfo {
139 pub fn create_root(cluster1: usize) -> Self {
141 Self {
142 wildcard: String::new(),
143 idx: 0,
144 name: "".to_string(),
145 typ: "".to_string(),
146 read_only: false,
147 hidden: false,
148 system: false,
149 volume_id: false,
150 directory: true,
151 archived: false,
152 create_date: None,
153 create_time: None,
154 write_date: None,
155 write_time: None,
156 access_date: None,
157 eof: 0,
158 cluster1: match cluster1 {
159 0 => None,
160 _ => Some(Ptr::Cluster(cluster1))
161 }
162 }
163 }
164 pub fn create_wildcard(pattern: &str) -> Self {
166 Self {
167 wildcard: String::from(pattern),
168 idx: 0,
169 name: "".to_string(),
170 typ: "".to_string(),
171 read_only: false,
172 hidden: false,
173 system: false,
174 volume_id: false,
175 directory: true,
176 archived: false,
177 create_date: None,
178 create_time: None,
179 write_date: None,
180 write_time: None,
181 access_date: None,
182 eof: 0,
183 cluster1: None
184 }
185 }
186}
187
188impl Entry {
189 pub fn create(name: &str, time: Option<chrono::NaiveDateTime>) -> Self {
192 let now = match time {
193 Some(t) => t,
194 None => chrono::Local::now().naive_local()
195 };
196 let tenths = super::pack::pack_tenths(Some(now));
197 let time = super::pack::pack_time(Some(now));
198 let date = super::pack::pack_date(Some(now));
199 let (base,ext) = super::pack::string_to_file_name(name);
200 Self {
201 name: base,
202 ext,
203 attr: 0,
204 nt_res: 0,
205 creation_tenth: tenths,
206 creation_time: time,
207 creation_date: date,
208 access_date: date,
209 cluster1_high: [0,0],
210 write_time: time,
211 write_date: date,
212 cluster1_low: [0,0],
213 file_size: [0,0,0,0]
214 }
215 }
216 pub fn create_label(name: &str, time: Option<chrono::NaiveDateTime>) -> Self {
218 let now = match time {
219 Some(t) => t,
220 None => chrono::Local::now().naive_local()
221 };
222 let tenths = super::pack::pack_tenths(Some(now));
223 let time = super::pack::pack_time(Some(now));
224 let date = super::pack::pack_date(Some(now));
225 let (base,ext) = super::pack::string_to_label_name(name);
226 Self {
227 name: base,
228 ext,
229 attr: VOLUME_ID,
230 nt_res: 0,
231 creation_tenth: tenths,
232 creation_time: time,
233 creation_date: date,
234 access_date: date,
235 cluster1_high: [0,0],
236 write_time: time,
237 write_date: date,
238 cluster1_low: [0,0],
239 file_size: [0,0,0,0]
240 }
241 }
242 pub fn erase(&mut self,none_follow: bool) {
243 match none_follow {
244 true => self.name[0] = FREE_AND_NO_MORE,
245 false => self.name[0] = FREE
246 }
247 }
248 pub fn set_cluster(&mut self,cluster: usize) {
249 let [b1,b2,b3,b4] = u32::to_le_bytes(cluster as u32);
250 self.cluster1_low = [b1,b2];
251 self.cluster1_high = [b3,b4];
252 }
253 pub fn create_subdir(name: &str,parent_cluster: usize,new_cluster: usize,block_size: usize,time: Option<chrono::NaiveDateTime>) -> (Self,Vec<u8>) {
257 let mut dot = Entry::create(".",time);
258 let mut dotdot = Entry::create("..",time);
259 dot.attr = DIRECTORY;
260 dot.set_cluster(new_cluster);
261 dotdot.attr = DIRECTORY;
262 dotdot.set_cluster(parent_cluster);
263 let mut dir = Directory::new();
264 dir.expand(block_size/DIR_ENTRY_SIZE);
265 dir.set_entry(&Ptr::Entry(0),&dot);
266 dir.set_entry(&Ptr::Entry(1),&dotdot);
267 dot.rename(name);
268 (dot,dir.to_bytes())
269 }
270 pub fn name(&self,label: bool) -> String {
271 let prim = super::pack::file_name_to_string(self.name, self.ext);
272 match label {
273 true => prim.replace(".",""),
274 false => prim
275 }
276 }
277 pub fn rename(&mut self,new_name: &str) {
278 let (name,ext) = super::pack::string_to_file_name(new_name);
279 self.name = name;
280 self.ext = ext;
281 }
282 pub fn eof(&self) -> usize {
283 u32::from_le_bytes(self.file_size) as usize
284 }
285 pub fn metadata_to_fimg(&self,fimg: &mut FileImage) {
287 fimg.set_eof(self.eof());
288 fimg.access = vec![self.attr];
289 fimg.fs_type = self.ext.to_vec();
290 fimg.aux = vec![];
291 fimg.created = [vec![self.creation_tenth],self.creation_time.to_vec(),self.creation_date.to_vec()].concat();
292 fimg.modified = [self.write_time.to_vec(),self.write_date.to_vec()].concat();
293 fimg.version = vec![];
294 fimg.min_version = vec![];
295 }
296 pub fn fimg_to_metadata(&mut self,fimg: &FileImage,use_fimg_time: bool) -> STDRESULT {
298 self.file_size = match fimg.eof[0..4].try_into() {
299 Ok(x) => x,
300 Err(e) => return Err(Box::new(e))
301 };
302 self.attr = fimg.access[0];
303 if use_fimg_time {
304 self.creation_tenth =fimg.created[0];
305 self.creation_time = match fimg.created[1..3].try_into() {
306 Ok(x) => x,
307 Err(e) => return Err(Box::new(e))
308 };
309 self.creation_date = match fimg.created[3..5].try_into() {
310 Ok(x) => x,
311 Err(e) => return Err(Box::new(e))
312 };
313 self.write_time = match fimg.modified[0..2].try_into() {
314 Ok(x) => x,
315 Err(e) => return Err(Box::new(e))
316 };
317 self.write_date = match fimg.modified[2..4].try_into() {
318 Ok(x) => x,
319 Err(e) => return Err(Box::new(e))
320 };
321 self.access_date = match fimg.modified[2..4].try_into() {
322 Ok(x) => x,
323 Err(e) => return Err(Box::new(e))
324 };
325 }
326 Ok(())
327 }
328 pub fn get_attr(&self,mask: u8) -> bool {
329 (self.attr & mask) > 0
330 }
331 pub fn set_attr(&mut self,attrib: Attributes) {
333 self.attr = update_attrib(self.attr, attrib);
334 }
335 pub fn standardize(offset: usize) -> Vec<usize> {
336 let ans = vec![13,14,15,16,17,18,19,22,23,24,25];
339 ans.iter().map(|x| x + offset).collect()
340 }
341 }
345
346impl DiskStruct for Directory {
347 fn new() -> Self {
348 let entries: Vec<[u8;DIR_ENTRY_SIZE]> = Vec::new();
349 Self {
350 entries
351 }
352 }
353 fn to_bytes(&self) -> Vec<u8> {
354 let mut ans: Vec<u8> = Vec::new();
355 for x in &self.entries {
356 ans.append(&mut x.to_vec());
357 }
358 return ans;
359 }
360 fn update_from_bytes(&mut self,bytes: &[u8]) -> Result<(),DiskStructError> {
361 self.entries = Vec::new();
362 let num_entries = bytes.len()/DIR_ENTRY_SIZE;
363 if bytes.len()%DIR_ENTRY_SIZE!=0 {
364 warn!("directory buffer wrong size");
365 }
366 for i in 0..num_entries {
367 let entry_buf = match bytes[i*DIR_ENTRY_SIZE..(i+1)*DIR_ENTRY_SIZE].try_into() {
368 Ok(buf) => buf,
369 Err(_) => return Err(DiskStructError::OutOfData)
370 };
371 self.entries.push(entry_buf);
372 }
373 Ok(())
374 }
375 fn from_bytes(bytes: &[u8]) -> Result<Self,DiskStructError> {
376 let mut ans = Self::new();
377 ans.update_from_bytes(bytes)?;
378 Ok(ans)
379 }
380 fn len(&self) -> usize {
381 return DIR_ENTRY_SIZE*(self.entries.len());
382 }
383}
384
385impl Directory {
386 pub fn num_entries(&self) -> usize {
388 self.entries.len()
389 }
390 pub fn expand(&mut self,count: usize) {
391 for _i in 0..count {
392 self.entries.push([0;32]);
393 }
394 }
395 pub fn get_type(&self,ptr: &Ptr) -> EntryType {
396 let (idx,nm0,attr) = match ptr {
397 Ptr::Entry(i) => (*i,self.entries[*i][0],self.entries[*i][11]),
398 _ => panic!("wrong pointer type")
399 };
400 trace!("entry {} has name[0] {} and attr {}",idx,nm0,attr);
401 match (nm0,attr) {
402 (0xe5,_) => EntryType::Free,
403 (0x00,_) => EntryType::FreeAndNoMore,
404 (_,a) if a & LONG_NAME >= LONG_NAME => EntryType::LongName,
405 (_,a) if a & VOLUME_ID > 0 => EntryType::VolumeLabel,
406 (_,a) if a & DIRECTORY > 0 => EntryType::Directory,
407 _ => EntryType::File
408 }
409 }
410 pub fn get_raw_entry(&self,ptr: &Ptr) -> [u8;DIR_ENTRY_SIZE] {
411 match ptr {
412 Ptr::Entry(i) => self.entries[*i].clone(),
413 _ => panic!("wrong pointer type")
414 }
415 }
416 pub fn get_entry(&self,ptr: &Ptr) -> Entry {
417 match ptr {
418 Ptr::Entry(idx) => Entry::from_bytes(&self.entries[*idx]).expect("unexpected size"),
419 _ => panic!("wrong pointer type")
420 }
421 }
422 pub fn set_entry(&mut self,ptr: &Ptr,entry: &Entry) {
423 match ptr {
424 Ptr::Entry(idx) => {
425 self.entries[*idx] = entry.to_bytes().try_into().expect("unexpected size")
426 },
427 _ => panic!("wrong pointer type")
428 }
429 }
430 pub fn find_label(&self) -> Option<Entry> {
432 for i in 0..self.num_entries() {
433 let ptr = Ptr::Entry(i);
434 if self.get_type(&ptr)==EntryType::VolumeLabel {
435 return Some(self.get_entry(&ptr));
436 }
437 }
438 None
439 }
440 fn add_file(&self,ans: &mut BTreeMap<String,FileInfo>,fat_typ: usize,entry_idx: usize) -> Result<bool,DYNERR> {
441 let entry = self.get_entry(&Ptr::Entry(entry_idx));
442 let (name,typ) = super::pack::file_name_to_split_string(entry.name, entry.ext);
443 let key = [name.clone(),".".to_string(),typ.clone()].concat();
444 trace!("entry in use: {}",key);
445 if ans.contains_key(&key) {
446 debug!("duplicate file {} in directory",key);
447 return Err(Box::new(Error::DuplicateFile));
448 }
449 let mut cluster1 = u16::from_le_bytes(entry.cluster1_low) as usize;
450 if fat_typ == 32 {
451 cluster1 += (u16::MAX as usize) * (u16::from_le_bytes(entry.cluster1_high) as usize);
453 }
454 let finfo: FileInfo = FileInfo {
455 wildcard: String::new(),
456 idx: entry_idx,
457 name,
458 typ,
459 read_only: (entry.attr & READ_ONLY) > 0,
460 hidden: (entry.attr & HIDDEN) > 0,
461 system: (entry.attr & SYSTEM) > 0,
462 volume_id: (entry.attr & VOLUME_ID) > 0,
463 directory: (entry.attr & DIRECTORY) > 0,
464 archived: (entry.attr & ARCHIVE) > 0,
465 write_date: super::pack::unpack_date(entry.write_date),
466 write_time: super::pack::unpack_time(entry.write_time,0),
467 create_date: super::pack::unpack_date(entry.creation_date),
468 create_time: super::pack::unpack_time(entry.creation_time,entry.creation_tenth),
469 access_date: super::pack::unpack_date(entry.access_date),
470 eof: u32::from_le_bytes(entry.file_size) as usize,
471 cluster1: Some(Ptr::Cluster(cluster1))
472 };
473 ans.insert(key.clone(),finfo);
474 Ok(super::pack::is_name_valid(&key))
475 }
476 pub fn build_files(&self,fat_typ: usize) -> Result<BTreeMap<String,FileInfo>,DYNERR> {
478 let mut bad_names = 0;
479 let mut ans = BTreeMap::new();
480 for i in 0..self.num_entries() {
482 let etyp = self.get_type(&Ptr::Entry(i));
483 if etyp==EntryType::Free {
484 continue;
485 }
486 if etyp==EntryType::FreeAndNoMore {
487 break;
488 }
489 if bad_names > 2 {
490 debug!("after {} bad file names rejecting disk",bad_names);
491 return Err(Box::new(Error::Syntax));
492 }
493 if !self.add_file(&mut ans,fat_typ,i)? {
494 bad_names += 1;
495 }
496 }
497 Ok(ans)
498 }
499 pub fn sort_on_entry_index(&self,files: &BTreeMap<String,FileInfo>) -> BTreeMap<usize,FileInfo> {
502 let mut ans = BTreeMap::new();
503 for f in files.values() {
504 ans.insert(f.idx,f.clone());
505 }
506 ans
507 }
508 }
509
510pub fn get_file<'a>(name: &str,files: &'a BTreeMap<String,FileInfo>) -> Option<&'a FileInfo> {
514 let mut trimmed = name.trim_end().to_string();
515 if !name.contains(".") {
516 trimmed += ".";
517 }
518 if let Some(finfo) = files.get(&trimmed) {
520 return Some(finfo);
521 }
522 if let Some(finfo) = files.get(&trimmed.to_uppercase()) {
523 return Some(finfo);
524 }
525 return None;
526}
527