a2kit/fs/pascal/
mod.rs

1//! ## Pascal file system module
2//! 
3//! This module is *not* for the Pascal language, but rather the Pascal file system.
4//! Tested only with UCSD Pascal version 1.2.
5
6pub mod types;
7mod boot;
8mod directory;
9mod pack;
10
11use std::collections::HashMap;
12use std::str::FromStr;
13use std::fmt::Write;
14use a2kit_macro::DiskStruct;
15use num_traits::FromPrimitive;
16use types::*;
17use pack::*;
18use directory::*;
19use super::Block;
20use crate::img;
21use crate::{STDRESULT,DYNERR};
22
23pub const FS_NAME: &str = "a2 pascal";
24const IMAGE_TYPES: [img::DiskImageType;5] = [
25    img::DiskImageType::DO,
26    img::DiskImageType::NIB,
27    img::DiskImageType::WOZ1,
28    img::DiskImageType::WOZ2,
29    img::DiskImageType::DOT2MG
30];
31
32/// Load directory structure from a borrowed disk image.
33/// This is used to test images, as well as being called during FS operations.
34fn get_directory(img: &mut Box<dyn img::DiskImage>) -> Result<Directory,DYNERR> {
35    let mut ans = Directory::new();
36    let mut buf = img.read_block(Block::PO(VOL_HEADER_BLOCK))?;
37    ans.header = VolDirHeader::from_bytes(&buf[0..ENTRY_SIZE])?;
38    let beg0 = u16::from_le_bytes(ans.header.begin_block);
39    let beg = VOL_HEADER_BLOCK as u16;
40    let end = u16::from_le_bytes(ans.header.end_block);
41    if beg0!=0 || end<=beg || (end as usize)>ans.total_blocks() {
42        log::debug!("bad header: begin block {}, end block {}",beg,end);
43        return Err(Box::new(Error::BadFormat));
44    }
45    // gather up all the directory blocks in a contiguous buffer; this is convenient
46    // since the entries are allowed to span 2 blocks.
47    buf = Vec::new();
48    for iblock in beg..end {
49        let mut temp = img.read_block(Block::PO(iblock as usize))?;
50        buf.append(&mut temp);
51    }
52    // create all possible entries whether in use or not
53    let max_num_entries = buf.len()/ENTRY_SIZE - 1;
54    let mut offset = ENTRY_SIZE;
55    for _i in 0..max_num_entries {
56        ans.entries.push(DirectoryEntry::from_bytes(&buf[offset..offset+ENTRY_SIZE])?);
57        offset += ENTRY_SIZE;
58    }
59    return Ok(ans);
60}
61
62pub fn new_fimg(chunk_len: usize,set_time: bool,name: &str) -> Result<super::FileImage,DYNERR> {
63    if !is_name_valid(name, false) {
64        return Err(Box::new(Error::BadFormat))
65    }
66    let modified = match set_time {
67        true => pack::pack_date(None).to_vec(),
68        false => vec![0;2]
69    };
70    Ok(super::FileImage {
71        fimg_version: super::FileImage::fimg_version(),
72        file_system: String::from(FS_NAME),
73        fs_type: vec![0;2],
74        aux: vec![],
75        eof: vec![0;4],
76        accessed: vec![],
77        created: vec![],
78        modified,
79        access: vec![],
80        version: vec![],
81        min_version: vec![],
82        chunk_len,
83        full_path: name.to_string(),
84        chunks: HashMap::new()
85    })
86}
87
88pub struct Packer {
89}
90
91/// The primary interface for disk operations.
92pub struct Disk
93{
94    img: Box<dyn img::DiskImage>
95}
96
97impl Disk
98{
99    /// Create a disk file system using the given image as storage.
100    /// The DiskFS takes ownership of the image.
101    pub fn from_img(img: Box<dyn img::DiskImage>) -> Result<Self,DYNERR> {
102        Ok(Self {
103            img
104        })
105    }
106    /// Test an image for the Pascal file system.  Changes method to Auto.
107    /// Usually called before user parameters are applied.
108    pub fn test_img(img: &mut Box<dyn img::DiskImage>) -> bool {
109        if !IMAGE_TYPES.contains(&img.what_am_i()) {
110            return false;
111        }
112        log::info!("trying Pascal");
113        img.change_method(img::tracks::Method::Auto);
114        // test the volume directory header
115         match get_directory(img) {
116            Ok(directory) => {
117                let beg0 = u16::from_le_bytes(directory.header.begin_block);
118                let beg = VOL_HEADER_BLOCK as u16;
119                let end = u16::from_le_bytes(directory.header.end_block);
120                let tot = u16::from_le_bytes(directory.header.total_blocks);
121                if beg0!=0 || end<=beg || end>20 {
122                    log::debug!("header begin {} end {}",beg0,end);
123                    return false;
124                }
125                // if (tot as usize) != block_count {
126                //     log::debug!("header total blocks {}",tot);
127                //     return false;
128                // }
129                if directory.header.name_len>7 || directory.header.name_len==0 {
130                    log::debug!("header name length {}",directory.header.name_len);
131                    return false;
132                }
133                if directory.header.file_type != [0,0] {
134                    log::debug!("header type {}",u16::from_le_bytes(directory.header.file_type));
135                    return false;
136                }
137                for i in 0..directory.header.name_len {
138                    let c = directory.header.name[i as usize];
139                    if c<32 || c>126 {
140                        log::debug!("header name character {}",c);
141                        return false;
142                    }
143                }
144                // test every directory entry that is used
145                for i in 0..u16::from_le_bytes(directory.header.num_files) {
146                    let entry = directory.entries[i as usize];
147                    let ebeg = u16::from_le_bytes(entry.begin_block);
148                    let eend = u16::from_le_bytes(entry.end_block);
149                    if ebeg>0 {
150                        if ebeg<end || eend<=ebeg || (eend as u16) > tot {
151                            log::debug!("entry {} begin {} end {}",i,ebeg,eend);
152                            return false;
153                        }
154                        if entry.name_len>15 || entry.name_len==0 {
155                            log::debug!("entry {} name length {}",i,entry.name_len);
156                            return false;
157                        }
158                        for j in 0..entry.name_len {
159                            let c = entry.name[j as usize];
160                            if c<32 || c>126 {
161                                log::debug!("entry {} name char {}",i,c);
162                                return false;
163                            }
164                        }
165                    }
166                }
167                return true;
168            },
169            Err(e) => {
170                log::debug!("pascal directory was not readable: {}",e);
171                return false;
172            }
173        }
174    }
175    fn get_directory(&mut self) -> Result<Directory,DYNERR> {
176        get_directory(&mut self.img)
177    }
178    fn save_directory(&mut self,dir: &Directory) -> STDRESULT {
179        let beg = VOL_HEADER_BLOCK as u16;
180        let end = u16::from_le_bytes(dir.header.end_block);
181        let buf = dir.to_bytes();
182        for iblock in beg..end {
183            self.write_block(&buf,iblock as usize,(iblock-beg) as usize * BLOCK_SIZE)?;
184        }
185        Ok(())
186    }
187    fn is_block_free(&self,iblock: usize,directory: &Directory) -> bool {
188        if iblock < u16::from_le_bytes(directory.header.end_block) as usize {
189            return false;
190        }
191        for i in 0..u16::from_le_bytes(directory.header.num_files) {
192            let beg = u16::from_le_bytes(directory.entries[i as usize].begin_block);
193            let end = u16::from_le_bytes(directory.entries[i as usize].end_block);
194            if (iblock as u16) >= beg && (iblock as u16) < end {
195                return false;
196            }
197        }
198        return true;
199    }
200    /// Return tuple with (free blocks,largest contiguous span of blocks)
201    fn num_free_blocks(&mut self) -> Result<(u16,u16),DYNERR> {
202        let directory = self.get_directory()?;
203        let mut free: u16 = 0;
204        let mut count: u16 = 0;
205        let mut largest: u16 = 0;
206        for i in 0..directory.total_blocks() {
207            if self.is_block_free(i,&directory) {
208                count += 1;
209                free += 1;
210            } else {
211                if count > largest {
212                    largest = count;
213                }
214                count = 0;
215            }
216        }
217        if count > largest {
218            largest = count;
219        }
220        Ok((free,largest))
221    }
222    /// Read a block of data into buffer `data` starting at `offset` within the buffer.
223    /// Will read as many bytes as will fit in the buffer starting at `offset`.
224    fn read_block(&mut self,data: &mut [u8], iblock: usize, offset: usize) -> STDRESULT {
225        let bytes = 512;
226        let actual_len = match data.len() as i32 - offset as i32 {
227            x if x<0 => panic!("invalid offset in read block"),
228            x if x<=bytes => x,
229            _ => bytes
230        };
231        let buf = self.img.read_block(Block::PO(iblock))?;
232        for i in 0..actual_len as usize {
233            data[offset + i] = buf[i];
234        }
235        Ok(())
236    }
237    /// Writes a block of data from buffer `data`, starting at `offset` within the buffer.
238    /// If `data` is shorter than the block, trailing bytes are unaffected.
239    /// Same as zap since there is no track bitmap in Pascal file system.
240    fn write_block(&mut self,data: &[u8], iblock: usize, offset: usize) -> STDRESULT {
241        self.zap_block(data,iblock,offset)
242    }
243    /// Writes a block of data from buffer `data`, starting at `offset` within the buffer.
244    /// If `data` is shorter than the block, trailing bytes are unaffected.
245    fn zap_block(&mut self,data: &[u8], iblock: usize, offset: usize) -> STDRESULT {
246        let bytes = 512;
247        let actual_len = match data.len() as i32 - offset as i32 {
248            x if x<0 => panic!("invalid offset in write block"),
249            x if x<=bytes => x as usize,
250            _ => bytes as usize
251        };
252        self.img.write_block(Block::PO(iblock), &data[offset..offset+actual_len])
253    }
254    /// Try to find `num` contiguous free blocks.  If found return the first block index.
255    fn get_available_blocks(&mut self,num: u16) -> Result<Option<u16>,DYNERR> {
256        let directory = self.get_directory()?;
257        let mut start = 0;
258        let mut count = 0;
259        for block in 0..directory.total_blocks() as u16 {
260            if self.is_block_free(block as usize,&directory) {
261                if count==0 {
262                    start = block;
263                    count += 1;
264                } else {
265                    count += 1;
266                }
267                if count==num {
268                    return Ok(Some(start));
269                }
270            } else {
271                count = 0;
272                start = 0;
273            }
274        }
275        return Ok(None);
276    }
277    /// Format disk for the Pascal file system
278    pub fn format(&mut self, vol_name: &str, fill: u8, time: Option<chrono::NaiveDateTime>) -> STDRESULT {
279        if !is_name_valid(vol_name, true) {
280            log::error!("invalid pascal volume name");
281            return Err(Box::new(Error::BadTitle));
282        }
283        if self.img.nominal_capacity().is_none() {
284            return Err(Box::new(Error::BadFormat));
285        }
286        let num_blocks = self.img.nominal_capacity().unwrap()/512;
287        // Zero boot and directory blocks
288        for iblock in 0..6 {
289            self.write_block(&[0;BLOCK_SIZE],iblock,0)?;
290        }
291        // Put `fill` value in all remaining blocks
292        for iblock in 6..num_blocks {
293            self.write_block(&[fill;BLOCK_SIZE],iblock,0)?;
294        }
295        // Setup volume directory
296        let mut dir = Directory::new();
297        dir.header.begin_block = u16::to_le_bytes(0); // points to first boot block, not header
298        dir.header.end_block = u16::to_le_bytes(6);
299        dir.header.file_type = u16::to_le_bytes(0);
300        dir.header.name_len = vol_name.len() as u8;
301        dir.header.name = string_to_vol_name(vol_name);
302        dir.header.total_blocks = u16::to_le_bytes(num_blocks as u16);
303        dir.header.num_files = u16::to_le_bytes(0);
304        dir.header.last_access_date = u16::to_le_bytes(0);
305        dir.header.last_set_date = pack_date(time);
306        dir.header.pad = [0,0,0,0];
307        // only need to write the first block, in fact, only first 22 bytes have data
308        self.write_block(&dir.to_bytes(),VOL_HEADER_BLOCK,0)?;
309
310        // boot loader blocks
311        match self.img.kind() {
312            img::names::A2_DOS33_KIND => {
313                self.write_block(&boot::PASCAL_525_BLOCK0, 0, 0)?;
314                self.write_block(&boot::PASCAL_525_BLOCK1, 1, 0)?;
315            },
316            _ => {
317                log::error!("unsupported disk type");
318                return Err(Box::new(Error::NoDev))
319            }
320        }
321        return Ok(());
322    }
323
324    /// Scan the directory to find the named file and return (Option<entry index>, directory).
325    /// N.b. Pascal FS always keeps files in contiguous blocks.
326    fn get_file_entry(&mut self,name: &str) -> Result<(Option<usize>,Directory),DYNERR> {
327        let directory = self.get_directory()?;
328        for i in 0..u16::from_le_bytes(directory.header.num_files) {
329            let entry = &directory.entries[i as usize];
330            let beg = u16::from_le_bytes(entry.begin_block);
331            let end = u16::from_le_bytes(entry.end_block);
332            if beg>0 && end>beg && (end as usize)<directory.total_blocks() {
333                if name.to_uppercase() == file_name_to_string(entry.name, entry.name_len) {
334                    return Ok((Some(i as usize),directory));
335                }
336            }
337        }
338        return Ok((None,directory));
339    }
340    /// Read any file into the sparse file format.  The fact that the Pascal FS does not
341    /// have sparse files presents no difficulty, since `FileImage` is quite general.
342    /// As usual we can use `FileImage::sequence` to make the result sequential.
343    fn read_file(&mut self,name: &str) -> Result<super::FileImage,DYNERR> {
344        if !is_name_valid(name,false) {
345            log::error!("invalid pascal filename");
346            return Err(Box::new(Error::BadFormat));
347        }
348        match self.get_file_entry(name)? { (Some(idx),dir) => {
349            let entry = &dir.entries[idx];
350            let mut ans = new_fimg(BLOCK_SIZE,false,name)?;
351            let mut buf = vec![0;BLOCK_SIZE];
352            let mut count: usize = 0;
353            let beg = u16::from_le_bytes(entry.begin_block);
354            let end = u16::from_le_bytes(entry.end_block);
355            let ftype = u16::from_le_bytes(entry.file_type);
356            for iblock in beg..end {
357                self.read_block(&mut buf, iblock as usize, 0)?;
358                ans.chunks.insert(count,buf.clone());
359                count += 1;
360            }
361            ans.fs_type = u16::to_le_bytes(ftype).to_vec();
362            ans.eof = u32::to_le_bytes(BLOCK_SIZE as u32*ans.chunks.len() as u32 - u16::from_le_bytes(entry.bytes_remaining) as u32).to_vec();
363            ans.modified = entry.mod_date.to_vec();
364            return Ok(ans);
365        } _ => {
366            return Err(Box::new(Error::NoFile));
367        }}
368    }
369    /// Write any file using the sparse file format.  The caller must ensure that the
370    /// chunks are sequential (Pascal only supports sequential data).  This is easy:
371    /// use `FileImage::desequence` to put sequential data into the sparse file format.
372    fn write_file(&mut self,name: &str, fimg: &super::FileImage) -> Result<usize,DYNERR> {
373        if fimg.chunks.len()==0 {
374            log::error!("empty data is not allowed for Pascal file images");
375            return Err(Box::new(Error::NoFile));
376        }
377        if !is_name_valid(name,false) {
378            log::error!("invalid pascal filename");
379            return Err(Box::new(Error::BadFormat));
380        }
381        let (maybe_idx,mut dir) = self.get_file_entry(name)?;
382        if maybe_idx==None {
383            // this is a new file
384            // we do not write anything unless there is room
385            let data_blocks = fimg.chunks.len();
386            let fs_type_usize = fimg.get_ftype();
387            let eof_usize = fimg.get_eof();
388            if let Some(fs_type) = FileType::from_usize(fs_type_usize) {
389                match self.get_available_blocks(data_blocks as u16)? { Some(beg) => {
390                    let i = u16::from_le_bytes(dir.header.num_files) as usize;
391                    if i < dir.entries.len() {
392                        log::debug!("using entry {}",i);
393                        dir.entries[i].begin_block = u16::to_le_bytes(beg);
394                        dir.entries[i].end_block = u16::to_le_bytes(beg+data_blocks as u16);
395                        dir.entries[i].file_type = u16::to_le_bytes(fs_type as u16);
396                        dir.entries[i].name_len = name.len() as u8;
397                        dir.entries[i].name = string_to_file_name(name);
398                        dir.entries[i].bytes_remaining = u16::to_le_bytes((BLOCK_SIZE*data_blocks - eof_usize) as u16);
399                        dir.entries[i].mod_date = pack_date(None); // None means use system clock
400                        dir.header.num_files = u16::to_le_bytes(u16::from_le_bytes(dir.header.num_files)+1);
401                        dir.header.last_access_date = pack_date(None);
402                        self.save_directory(&dir)?;
403                        for b in 0..data_blocks {
404                            if fimg.chunks.contains_key(&b) {
405                                self.write_block(&fimg.chunks[&b],beg as usize+b,0)?;
406                            } else {
407                                log::error!("pascal file image had a hole which is not allowed");
408                                return Err(Box::new(Error::BadFormat));
409                            }
410                        }
411                        return Ok(data_blocks);
412                    }
413                    log::error!("directory is full");
414                    return Err(Box::new(Error::NoRoom));
415                } _ => {
416                    log::error!("not enough contiguous space");
417                    return Err(Box::new(Error::NoRoom));
418                }}
419            } else {
420                log::error!("unknown file type");
421                return Err(Box::new(Error::BadMode));
422            }
423        } else {
424            log::error!("overwriting is not allowed");
425            return Err(Box::new(Error::DuplicateFilename));
426        }
427    }
428    /// Verify that the new name does not already exist
429    fn ok_to_rename(&mut self,new_name: &str) -> STDRESULT {
430        if !is_name_valid(new_name,false) {
431            return Err(Box::new(Error::BadFormat));
432        }
433        match self.get_file_entry(new_name) {
434            Ok((None,_)) => Ok(()),
435            Ok(_) => Err(Box::new(Error::DuplicateFilename)),
436            Err(e) => Err(e)
437        }
438    }
439    /// modify a file entry, optionally rename, retype.
440    fn modify(&mut self,name: &str,maybe_new_name: Option<&str>,maybe_ftype: Option<&str>) -> STDRESULT {
441        if !is_name_valid(name, false) {
442            return Err(Box::new(Error::BadFormat));
443        }
444        match self.get_file_entry(name)? { (Some(idx),mut dir) => {
445            let entry = &mut dir.entries[idx];
446            if let Some(new_name) = maybe_new_name {
447                if !is_name_valid(new_name,false) {
448                    return Err(Box::new(Error::BadFormat));
449                }
450                entry.name = string_to_file_name(new_name);
451                entry.name_len = new_name.len() as u8;
452            }
453            if let Some(ftype) = maybe_ftype {
454                match FileType::from_str(ftype) {
455                    Ok(typ) => entry.file_type = u16::to_le_bytes(typ as u16),
456                    Err(e) => return Err(Box::new(e))
457                }
458            }
459            self.save_directory(&dir)?;
460            return Ok(());
461        } _ => {
462            return Err(Box::new(Error::NoFile));
463        }}
464    }
465}
466
467impl super::DiskFS for Disk {
468    fn new_fimg(&self, chunk_len: Option<usize>,set_time: bool,path: &str) -> Result<super::FileImage,DYNERR> {
469        match chunk_len {
470            Some(l) => new_fimg(l,set_time,path),
471            None => new_fimg(BLOCK_SIZE,set_time,path)
472        }
473    }
474    fn stat(&mut self) -> Result<super::Stat,DYNERR> {
475        let dir = self.get_directory()?;
476        let free_block_tuple = self.num_free_blocks()?;
477        Ok(super::Stat {
478            fs_name: FS_NAME.to_string(),
479            label: vol_name_to_string(dir.header.name,dir.header.name_len),
480            users: Vec::new(),
481            block_size: BLOCK_SIZE,
482            block_beg: 0,
483            block_end: dir.total_blocks(),
484            free_blocks: free_block_tuple.0 as usize,
485            raw: "".to_string()
486        })
487    }
488    fn catalog_to_stdout(&mut self, _path: &str) -> STDRESULT {
489        let typ_map: HashMap<u8,&str> = HashMap::from(TYPE_MAP_DISP);
490        let dir = self.get_directory()?;
491        let total = dir.total_blocks();
492        println!();
493        println!("{}:",vol_name_to_string(dir.header.name,dir.header.name_len));
494        let expected_count = u16::from_le_bytes(dir.header.num_files);
495        let mut file_count = 0;
496        for entry in dir.entries {
497            let beg = u16::from_le_bytes(entry.begin_block);
498            let end = u16::from_le_bytes(entry.end_block);
499            if beg!=0 && end>beg && (end as usize)<total {
500                let name = file_name_to_string(entry.name,entry.name_len);
501                let blocks = end - beg;
502                let mut date = "<NO DATE>".to_string();
503                if entry.mod_date!=[0,0] {
504                    date = unpack_date(entry.mod_date).format("%d-%b-%y").to_string();
505                }
506                let typ = match typ_map.get(&entry.file_type[0]) {
507                    Some(s) => s,
508                    None => "????"
509                };
510                println!("{:15} {:4} {:9}  {:4}",name,blocks,date,typ);
511                file_count += 1;
512            }
513        }
514        println!();
515        let (free,largest) = self.num_free_blocks()?;
516        let used = total-free as usize;
517        println!("{}/{} files<listed/in-dir>, {} blocks used, {} unused, {} in largest",file_count,expected_count,used,free,largest);
518        println!();
519        Ok(())
520    }
521    fn catalog_to_vec(&mut self, path: &str) -> Result<Vec<String>,DYNERR> {
522        if path!="/" && path!="" {
523            return Err(Box::new(Error::NoFile));
524        }
525        let mut ans = Vec::new();
526        let typ_map: HashMap<u8,&str> = HashMap::from(TYPE_MAP_DISP);
527        let dir = self.get_directory()?;
528        let total = dir.total_blocks();
529        for entry in dir.entries {
530            let beg = u16::from_le_bytes(entry.begin_block);
531            let end = u16::from_le_bytes(entry.end_block);
532            if beg!=0 && end>beg && (end as usize)<total {
533                let name = file_name_to_string(entry.name,entry.name_len);
534                let blocks = end - beg;
535                let type_as_hex = "$".to_string()+ &hex::encode_upper(vec![entry.file_type[0]]);
536                let typ = match typ_map.get(&entry.file_type[0]) {
537                    Some(s) => s,
538                    None => type_as_hex.as_str()
539                };
540                ans.push(super::universal_row(typ,blocks as usize,&name));
541            }
542        }
543        Ok(ans)
544    }
545    fn glob(&mut self,pattern: &str,case_sensitive: bool) -> Result<Vec<String>,DYNERR> {
546        let mut ans = Vec::new();
547        let glob = match case_sensitive {
548            true => globset::Glob::new(pattern)?.compile_matcher(),
549            false => globset::Glob::new(&pattern.to_uppercase())?.compile_matcher()
550        };
551        let dir = self.get_directory()?;
552        let total = dir.total_blocks();
553        for entry in dir.entries {
554            let beg = u16::from_le_bytes(entry.begin_block);
555            let end = u16::from_le_bytes(entry.end_block);
556            if beg!=0 && end>beg && (end as usize)<total {
557                let name = match case_sensitive {
558                    true => file_name_to_string(entry.name, entry.name_len),
559                    false => file_name_to_string(entry.name, entry.name_len).to_uppercase()
560                };
561                if glob.is_match(&name) {
562                    ans.push(name);
563                }
564            }
565        }
566        Ok(ans)
567    }
568    fn tree(&mut self,include_meta: bool,indent: Option<u16>) -> Result<String,DYNERR> {
569        let dir = self.get_directory()?;
570        let total = dir.total_blocks();
571        const TIME_FMT: &str = "%Y/%m/%d %H:%M";
572        let mut tree = json::JsonValue::new_object();
573        tree["file_system"] = json::JsonValue::String(FS_NAME.to_string());
574        tree["files"] = json::JsonValue::new_object();
575        tree["label"] = json::JsonValue::new_object();
576        tree["label"]["name"] = json::JsonValue::String(vol_name_to_string(dir.header.name, dir.header.name_len));
577        tree["label"]["time_created"] = json::JsonValue::String(unpack_date(dir.header.last_set_date).format(TIME_FMT).to_string());
578        tree["label"]["time_modified"] = json::JsonValue::String(unpack_date(dir.header.last_set_date).format(TIME_FMT).to_string());
579        for entry in dir.entries {
580            let beg = u16::from_le_bytes(entry.begin_block);
581            let end = u16::from_le_bytes(entry.end_block);
582            if beg!=0 && end>beg && (end as usize)<total {
583                let key = file_name_to_string(entry.name, entry.name_len);
584                tree["files"][&key] = json::JsonValue::new_object();
585                // file nodes must have no files object at all
586                if include_meta {
587                    let blocks = (end - beg) as usize;
588                    let bytes = blocks*BLOCK_SIZE + u16::from_le_bytes(entry.bytes_remaining) as usize - BLOCK_SIZE;
589                        tree["files"][&key]["meta"] = json::JsonValue::new_object();
590                    let meta = &mut tree["files"][&key]["meta"];
591                    meta["type"] = json::JsonValue::String(hex::encode_upper(entry.file_type.to_vec()));
592                    meta["eof"] = json::JsonValue::Number(bytes.into());
593                    if entry.mod_date!=[0,0] {
594                        meta["time_modified"] = json::JsonValue::String(unpack_date(entry.mod_date).format(TIME_FMT).to_string());
595                    }
596                    meta["blocks"] = json::JsonValue::Number(blocks.into());
597                }
598            }
599        }
600        if let Some(spaces) = indent {
601            Ok(json::stringify_pretty(tree, spaces))
602        } else {
603            Ok(json::stringify(tree))
604        }
605    }
606    fn create(&mut self,_path: &str) -> STDRESULT {
607        log::error!("pascal implementation does not support operation");
608        Err(Box::new(Error::DevErr))
609    }
610    fn delete(&mut self,name: &str) -> STDRESULT {
611        match self.get_file_entry(name)? { (Some(idx),mut dir) => {
612            for i in idx..dir.entries.len() {
613                if i+1 < dir.entries.len() {
614                    dir.entries[i] = dir.entries[i+1];
615                } else {
616                    // probably unnecessary
617                    dir.entries[i].begin_block = [0,0];
618                    dir.entries[i].end_block = [0,0];
619                }
620            }
621            dir.header.num_files = u16::to_le_bytes(u16::from_le_bytes(dir.header.num_files)-1);
622            self.save_directory(&dir)?;
623            return Ok(());
624        } _ => {
625            return Err(Box::new(Error::NoFile));
626        }}
627    }
628    fn set_attrib(&mut self,_path: &str,_permissions: crate::fs::Attributes,_password: Option<&str>) -> STDRESULT {
629        log::error!("pascal does not support operation");
630        Err(Box::new(Error::DevErr))
631    }
632    fn rename(&mut self,old_name: &str,new_name: &str) -> STDRESULT {
633        self.ok_to_rename(new_name)?;
634        self.modify(old_name,Some(new_name),None)
635    }
636    fn retype(&mut self,name: &str,new_type: &str,_sub_type: &str) -> STDRESULT {
637        self.modify(name, None, Some(new_type))
638    }
639    fn get(&mut self,name: &str) -> Result<super::FileImage,DYNERR> {
640        self.read_file(name)
641    }
642    fn put(&mut self,fimg: &super::FileImage) -> Result<usize,DYNERR> {
643        if fimg.file_system!=FS_NAME {
644            log::error!("cannot write {} file image to a2 pascal",fimg.file_system);
645            return Err(Box::new(Error::DevErr));
646        }
647        if fimg.chunk_len!=BLOCK_SIZE {
648            log::error!("chunk length {} is incompatible with Pascal",fimg.chunk_len);
649            return Err(Box::new(Error::DevErr));
650        }
651        self.write_file(&fimg.full_path,fimg)
652    }
653    fn read_block(&mut self,num: &str) -> Result<Vec<u8>,DYNERR> {
654        match usize::from_str(num) {
655            Ok(block) => self.img.read_block(Block::PO(block)),
656            Err(e) => Err(Box::new(e))
657        }
658    }
659    fn write_block(&mut self, num: &str, dat: &[u8]) -> Result<usize,DYNERR> {
660        match usize::from_str(num) {
661            Ok(block) => {
662                if dat.len() > BLOCK_SIZE {
663                    return Err(Box::new(Error::DevErr));
664                }
665                match self.img.write_block(Block::PO(block), dat) {
666                    Ok(()) => Ok(dat.len()),
667                    Err(e) => Err(e)
668                }
669            },
670            Err(e) => Err(Box::new(e))
671        }
672    }
673    fn standardize(&mut self,_ref_con: u16) -> HashMap<Block,Vec<usize>> {
674        // want to ignore dates, these occur at offest 18 and 20 in the header,
675        // and at offset 24 in every entry.  Also ignore unused name bytes.
676        // Also ignore unused directory entries entirely.
677        let mut ans: HashMap<Block,Vec<usize>> = HashMap::new();
678        let dir = self.get_directory().expect("could not get directory");
679        let beg = VOL_HEADER_BLOCK; // begin_block points to boot block
680        super::add_ignorable_offsets(&mut ans,Block::PO(beg),vec![18,19,20,21]);
681        let vol_name_len = dir.header.name_len as usize;
682        super::add_ignorable_offsets(&mut ans,Block::PO(beg),(7+vol_name_len..14).collect());
683        // header is done, now do entries
684        let mut aeoffset = dir.header.len();
685        let num_files = u16::from_le_bytes(dir.header.num_files) as usize;
686        for idx in 0..dir.entries.len() {
687            // form vector of absolute offsets into the directory
688            let aoffsets = match idx {
689                _i if _i >= num_files => (aeoffset..aeoffset+ENTRY_SIZE).collect(),
690                _ => {
691                    let datestamp = vec![aeoffset+24, aeoffset+25];
692                    let mut nlen = dir.entries[idx].name_len as usize;
693                    if nlen > 15 {
694                        nlen = 15;
695                    }
696                    let namebytes = (aeoffset+7+nlen..aeoffset+22).collect();
697                    [datestamp,namebytes].concat()
698                }
699            };
700            // work out the block and relative offset for each byte
701            for aoff in aoffsets {
702                let key = Block::PO(beg+aoff/BLOCK_SIZE);
703                let val = aoff%BLOCK_SIZE;
704                super::add_ignorable_offsets(&mut ans, key, vec![val]);
705            }
706            aeoffset += ENTRY_SIZE;
707        }
708        return ans;
709    }
710    fn compare(&mut self,path: &std::path::Path,ignore: &HashMap<Block,Vec<usize>>) {
711        let mut emulator_disk = crate::create_fs_from_file(&path.to_str().unwrap(),None).expect("read error");
712        let dir = self.get_directory().expect("could not get directory");
713        for block in 0..dir.total_blocks() {
714            let addr = Block::PO(block);
715            let mut actual = self.img.read_block(addr).expect("bad sector access");
716            let mut expected = emulator_disk.get_img().read_block(addr).expect("bad sector access");
717            if let Some(ignorable) = ignore.get(&addr) {
718                for offset in ignorable {
719                    actual[*offset] = 0;
720                    expected[*offset] = 0;
721                }
722            }
723            for row in 0..16 {
724                let mut fmt_actual = String::new();
725                let mut fmt_expected = String::new();
726                let offset = row*32;
727                write!(&mut fmt_actual,"{:02X?}",&actual[offset..offset+32].to_vec()).expect("format error");
728                write!(&mut fmt_expected,"{:02X?}",&expected[offset..offset+32].to_vec()).expect("format error");
729                assert_eq!(fmt_actual,fmt_expected," at block {}, row {}",block,row)
730            }
731        }
732    }
733    fn get_img(&mut self) -> &mut Box<dyn img::DiskImage> {
734        &mut self.img
735    }
736}