1pub 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
32fn 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 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 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
91pub struct Disk
93{
94 img: Box<dyn img::DiskImage>
95}
96
97impl Disk
98{
99 pub fn from_img(img: Box<dyn img::DiskImage>) -> Result<Self,DYNERR> {
102 Ok(Self {
103 img
104 })
105 }
106 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 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 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 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 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 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 fn write_block(&mut self,data: &[u8], iblock: usize, offset: usize) -> STDRESULT {
241 self.zap_block(data,iblock,offset)
242 }
243 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 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 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 for iblock in 0..6 {
289 self.write_block(&[0;BLOCK_SIZE],iblock,0)?;
290 }
291 for iblock in 6..num_blocks {
293 self.write_block(&[fill;BLOCK_SIZE],iblock,0)?;
294 }
295 let mut dir = Directory::new();
297 dir.header.begin_block = u16::to_le_bytes(0); 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 self.write_block(&dir.to_bytes(),VOL_HEADER_BLOCK,0)?;
309
310 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 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 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 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 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); 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 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 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 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 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 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; 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 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 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 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}