a2kit/fs/fat/
directory.rs

1//! ### FAT directory structures
2//! 
3//! This module encapsulates the FAT directory.  The FAT itself is implemented in
4//! `crate::bios::fat`.  The BPB is in `crate::bios::bpb`.
5
6use 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
13// a2kit_macro automatically derives `new`, `to_bytes`, `from_bytes`, and `length` from a DiskStruct.
14// This spares us having to manually write code to copy bytes in and out for every new structure.
15// The auto-derivation is not used for structures with variable length fields (yet).
16use a2kit_macro::{DiskStructError,DiskStruct};
17use a2kit_macro_derive::DiskStruct;
18
19/// Size of the directory entry in bytes, always 32
20pub const DIR_ENTRY_SIZE: usize = 32;
21/// first name byte for a free entry.
22const FREE: u8 = 0xe5;
23/// first name byte for a free entry, but also indicating no more entries to follow.
24const 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;
33//pub const LONG_NAME_SUB: u8 = 63;
34
35fn 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/// Convenient collection of information about a file.
66/// Flags are broken out into their own variables.
67/// This is the value of the map produced by Directory::build_files.
68#[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
99/// encapsulates information needed to manipulate a directory entry
100pub struct EntryLocation {
101    /// starting cluster of the directory (not the entry!), if cluster1.is_none(), this is a FAT12/16 root directory
102    pub cluster1: Option<Ptr>,
103    /// the entry in this directory we are interested in
104    pub entry: Ptr,
105    /// the entire directory as a vector of entries
106    pub dir: Directory
107}
108
109#[derive(DiskStruct)]
110pub struct Entry {
111    name: [u8;8],
112    ext: [u8;3],
113    /// RO=1,hidden=2,sys=4,vol=8,dir=16,archive=32,long_name=15.
114    /// If this is the volume label, cluster1=0.
115    /// If this is a directory, file_size=0.
116    attr: u8,
117    nt_res: u8,
118    /// tenths of a second, 0-199 according to MS (typo?)
119    creation_tenth: u8,
120    /// to the nearest 2 secs
121    creation_time: [u8;2],
122    creation_date: [u8;2],
123    access_date: [u8;2],
124    cluster1_high: [u8;2],
125    /// set at creation time also
126    write_time: [u8;2],
127    /// set at creation date also
128    write_date: [u8;2],
129    cluster1_low: [u8;2],
130    file_size: [u8;4]
131}
132
133/// Directory is merely a packed sequence of entries.
134pub struct Directory {
135    entries: Vec<[u8;DIR_ENTRY_SIZE]>
136}
137
138impl FileInfo {
139    /// for FAT12 or FAT16 set cluster1=0
140    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    /// represent file info as a wildcard pattern
165    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    /// Create an entry with given name and timestamp (time==None means use current time).
190    /// Not to be used to create a label entry.
191    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    /// Create a label entry with given name and timestamp (time==None means use current time).
217    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    /// Create a subdirectory at `new_cluster`, in directory at `parent_cluster` (0 if root even for FAT32).
254    /// Return the (parent entry, directory buffer), where the buffer includes the dot and dotdot entries.
255    /// The clusters are expected to be written by the caller.
256    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    /// access date is lost with this version of file image
286    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    /// access date is set to modified date
297    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    /// set, unset, or leave the given attributes
332    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        // relative to the entry start
337        // creation date, access date, write date
338        let ans = vec![13,14,15,16,17,18,19,22,23,24,25];
339        ans.iter().map(|x| x + offset).collect()
340    }
341    // pub fn cluster1(&self) -> usize {
342    //     u32::from_le_bytes([self.cluster1_low[0],self.cluster1_low[1],self.cluster1_high[0],self.cluster1_high[1]]) as usize
343    // }
344}
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    /// number of entries (used or not) in the directory
387    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    /// If this is the root directory there may be a disk label entry
431    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            // Don't add for FAT12/16 in case we have wrongly set bits here
452            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    /// Build an alphabetized map of file names to file info.
477    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        // first pass collects everything except passwords
481        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    /// Sort the files based on the order of appearance in the directory.
500    /// Panics if there is a file with an empty entry list.
501    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
510/// Search for a file in the map produced by `Directory::build_files`.
511/// This will try with the given case, and then with upper case.
512/// This will also handle empty extensions reliably.
513pub 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    // the order of these attempts is significant
519    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