mod boot;
pub mod types;
mod directory;
mod pack;
use std::collections::HashMap;
use a2kit_macro::DiskStruct;
use std::str::FromStr;
use std::fmt::Write;
use colored::*;
use log::{trace,debug,error};
use types::*;
use pack::*;
use directory::*;
use super::{Block,Attributes};
use crate::img;
use crate::{DYNERR,STDRESULT};
pub const FS_NAME: &str = "prodos";
const IMAGE_TYPES: [img::DiskImageType;6] = [
img::DiskImageType::DO,
img::DiskImageType::PO,
img::DiskImageType::NIB,
img::DiskImageType::WOZ1,
img::DiskImageType::WOZ2,
img::DiskImageType::DOT2MG
];
pub struct Disk {
img: Box<dyn img::DiskImage>,
total_blocks: usize,
maybe_bitmap: Option<Vec<u8>>,
bitmap_blocks: Vec<usize>,
curr_path: Vec<String>
}
fn pack_index_ptr(buf: &mut [u8],ptr: u16,idx: usize) {
let bytes = u16::to_le_bytes(ptr);
buf[idx] = bytes[0];
buf[idx+256] = bytes[1];
}
pub fn new_fimg(chunk_len: usize,set_time: bool,path: &str) -> Result<super::FileImage,DYNERR> {
if !is_path_valid(path) {
return Err(Box::new(Error::Syntax));
}
let created = match set_time {
true => pack::pack_time(None).to_vec(),
false => vec![0;4]
};
Ok(super::FileImage {
fimg_version: super::FileImage::fimg_version(),
file_system: String::from(FS_NAME),
fs_type: vec![0],
aux: vec![0;2],
eof: vec![0;3],
accessed: vec![],
created,
modified: vec![0;4],
access: vec![0],
version: vec![0],
min_version: vec![0],
chunk_len,
full_path: path.to_string(),
chunks: HashMap::new()
})
}
pub struct Packer {
}
impl Disk {
pub fn from_img(img: Box<dyn img::DiskImage>) -> Result<Self,DYNERR> {
if img.nominal_capacity().is_none() {
return Err(Box::new(Error::IOError));
}
let total_blocks = img.nominal_capacity().unwrap()/512;
Ok(Self {
img,
total_blocks,
maybe_bitmap: None,
bitmap_blocks: Vec::new(),
curr_path: Vec::new()
})
}
pub fn test_img(img: &mut Box<dyn img::DiskImage>) -> bool {
if !IMAGE_TYPES.contains(&img.what_am_i()) {
return false;
}
log::info!("trying ProDOS");
let mut maybe_buf: Result<Vec<u8>,DYNERR> = Err(Box::new(Error::IOError));
for test_method in [img::tracks::Method::Auto,img::tracks::Method::Emulate] {
img.change_method(test_method);
maybe_buf = img.read_block(Block::PO(2));
if maybe_buf.is_ok() {
break;
}
}
match maybe_buf { Ok(buf) => {
let first_char_patt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.";
let char_patt = [first_char_patt,"0123456789"].concat();
let vol_key: KeyBlock<VolDirHeader> = match KeyBlock::from_bytes(&buf) {
Ok(k) => k,
Err(_) => return false
};
let (nibs,name) = vol_key.header.fname();
let total_blocks = u16::from_le_bytes([buf[0x29],buf[0x2A]]);
if total_blocks<280 {
debug!("peculiar block count {}",total_blocks);
return false;
}
if buf[0x23]!=0x27 || (buf[0x24]!=0x0D && buf[0x24]!=0x0C) {
debug!("unexpected header bytes {}, {}",buf[0x23],buf[0x24]);
return false;
}
if vol_key.prev()!=0 || vol_key.next()!=3 || (nibs >> 4)!=15 {
debug!("unexpected volume name length or links");
return false;
}
if !first_char_patt.contains(name[0] as char) {
debug!("volume name unexpected character");
return false;
}
for i in 1..(nibs & 0x0F) {
if !char_patt.contains(name[i as usize] as char) {
debug!("volume name unexpected character");
return false;
}
}
return true;
} Err(e) => {
debug!("ProDOS volume directory was not readable: {}",e);
return false;
}}
}
fn open_bitmap_buffer(&mut self) -> STDRESULT {
if self.maybe_bitmap==None {
self.bitmap_blocks = Vec::new();
let bitmap_block_count = 1 + self.total_blocks / 4096;
let mut ans = Vec::new();
let bptr = u16::from_le_bytes(self.get_vol_header()?.bitmap_ptr) as usize;
for iblock in bptr..bptr+bitmap_block_count {
let mut buf = [0;512].to_vec();
self.read_block(&mut buf,iblock,0)?;
ans.append(&mut buf);
self.bitmap_blocks.push(iblock);
}
self.maybe_bitmap = Some(ans);
}
Ok(())
}
fn get_bitmap_buffer(&mut self) -> Result<&mut Vec<u8>,DYNERR> {
self.open_bitmap_buffer()?;
if let Some(buf) = self.maybe_bitmap.as_mut() {
return Ok(buf);
}
panic!("bitmap buffer failed to open");
}
fn writeback_bitmap_buffer(&mut self) -> STDRESULT {
let buf = match self.maybe_bitmap.as_ref() {
Some(bitmap) => bitmap.clone(),
None => return Ok(())
};
if self.bitmap_blocks.len()>0 {
let first = self.bitmap_blocks[0];
let bitmap_block_count = 1 + self.total_blocks / 4096;
for iblock in first..first+bitmap_block_count {
self.zap_block(&buf,iblock,(iblock-first)*512)?;
}
}
Ok(())
}
fn allocate_block(&mut self,iblock: usize) -> STDRESULT {
let buf = self.get_bitmap_buffer()?;
let byte = iblock / 8;
let bit = 7 - iblock % 8;
buf[byte] &= (1 << bit as u8) ^ u8::MAX;
Ok(())
}
fn deallocate_block(&mut self,iblock: usize) -> STDRESULT {
let buf = self.get_bitmap_buffer()?;
let byte = iblock / 8;
let bit = 7 - iblock % 8;
buf[byte] |= 1 << bit as u8;
Ok(())
}
fn is_block_free(&mut self,iblock: usize) -> Result<bool,DYNERR> {
let buf = self.get_bitmap_buffer()?;
let byte = iblock / 8;
let bit = 7 - iblock % 8;
Ok((buf[byte] & (1 << bit as u8)) > 0)
}
fn num_free_blocks(&mut self) -> Result<u16,DYNERR> {
let mut free: u16 = 0;
for i in 0..self.total_blocks {
if self.is_block_free(i)? {
free += 1;
}
}
Ok(free)
}
fn read_block(&mut self,data: &mut [u8], iblock: usize, offset: usize) -> STDRESULT {
let bytes = 512;
let actual_len = match data.len() as i32 - offset as i32 {
x if x<0 => panic!("invalid offset in read block"),
x if x<=bytes => x,
_ => bytes
};
if self.bitmap_blocks.contains(&iblock) {
let first = self.bitmap_blocks[0];
let buf = self.get_bitmap_buffer()?;
for i in 0..actual_len as usize {
data[offset + i] = buf[(iblock-first)*512 + i];
}
return Ok(());
}
match self.img.read_block(Block::PO(iblock)) {
Ok(buf) => {
for i in 0..actual_len as usize {
data[offset + i] = buf[i];
}
Ok(())
}
Err(e) => Err(e)
}
}
fn write_block(&mut self,data: &[u8], iblock: usize, offset: usize) -> STDRESULT {
if self.bitmap_blocks.contains(&iblock) {
panic!("attempt to write bitmap block, zap it instead");
}
self.zap_block(data,iblock,offset)?;
self.allocate_block(iblock)
}
fn zap_block(&mut self,data: &[u8], iblock: usize, offset: usize) -> STDRESULT {
let bytes = 512;
let actual_len = match data.len() as i32 - offset as i32 {
x if x<0 => panic!("invalid offset in write block"),
x if x<=bytes => x as usize,
_ => bytes as usize
};
if self.bitmap_blocks.contains(&iblock) {
self.maybe_bitmap = None;
}
self.img.write_block(Block::PO(iblock), &data[offset..offset+actual_len])
}
fn get_available_block(&mut self) -> Result<Option<u16>,DYNERR> {
for block in 0..self.total_blocks {
if self.is_block_free(block)? {
return Ok(Some(block as u16));
}
}
return Ok(None);
}
pub fn format(&mut self, vol_name: &str, floppy: bool, time: Option<chrono::NaiveDateTime>) -> STDRESULT {
trace!("formatting: zero all");
for iblock in 0..self.total_blocks {
self.zap_block(&[0;512],iblock,0)?;
}
let mut volume_dir = KeyBlock::<VolDirHeader>::new();
let bitmap_blocks = 1 + self.total_blocks / 4096;
volume_dir.set_links(Some(0), Some(VOL_KEY_BLOCK+1));
volume_dir.header.format(self.total_blocks as u16,vol_name,time);
let first = u16::from_le_bytes(volume_dir.header.bitmap_ptr) as usize;
trace!("formatting: volume key");
self.zap_block(&volume_dir.to_bytes(),VOL_KEY_BLOCK as usize,0)?;
trace!("formatting: free all");
for b in 0..self.total_blocks {
self.deallocate_block(b)?;
}
trace!("formatting: allocate key and bitmap");
self.allocate_block(VOL_KEY_BLOCK as usize)?;
for b in first..first + bitmap_blocks {
self.allocate_block(b)?;
}
trace!("formatting: boot loader");
if floppy {
self.write_block(&boot::FLOPPY_BLOCK0,0,0)?;
}
else {
self.write_block(&boot::HD_BLOCK0, 0, 0)?;
}
self.write_block(&vec![0;512],1,0)?;
trace!("formatting: volume directory");
for b in 3..6 {
let mut this = EntryBlock::new();
if b==5 {
this.set_links(Some(b-1), Some(0));
} else {
this.set_links(Some(b-1), Some(b+1));
}
self.write_block(&this.to_bytes(),b as usize,0)?;
}
Ok(())
}
fn get_vol_header(&mut self) -> Result<VolDirHeader,DYNERR> {
let mut buf: Vec<u8> = vec![0;512];
self.read_block(&mut buf,VOL_KEY_BLOCK as usize,0)?;
let volume_dir = KeyBlock::<VolDirHeader>::from_bytes(&buf)?;
Ok(volume_dir.header)
}
fn get_directory(&mut self,iblock: usize) -> Result<Box<dyn Directory>,DYNERR> {
let mut buf: Vec<u8> = vec![0;512];
self.read_block(&mut buf,iblock,0)?;
match (iblock==VOL_KEY_BLOCK as usize,buf[0]==0 && buf[1]==0) {
(true,true) => Ok(Box::new(KeyBlock::<VolDirHeader>::from_bytes(&buf)?)),
(true,false) => Ok(Box::new(KeyBlock::<VolDirHeader>::from_bytes(&buf)?)),
(false,true) => Ok(Box::new(KeyBlock::<SubDirHeader>::from_bytes(&buf)?)),
(false,false) => Ok(Box::new(EntryBlock::from_bytes(&buf)?))
}
}
fn get_key_directory(&mut self,ptr: u16) -> Result<(u16,Box<dyn Directory>),DYNERR> {
let mut curr = ptr;
for _try in 0..100 {
let test_dir = self.get_directory(curr as usize)?;
if test_dir.prev()==0 {
return Ok((curr,test_dir));
}
curr = test_dir.prev();
}
error!("directory block count not plausible, aborting");
Err(Box::new(Error::EndOfData))
}
fn read_entry(&mut self,loc: &EntryLocation) -> Result<Entry,DYNERR> {
let dir = self.get_directory(loc.block as usize)?;
Ok(dir.get_entry(loc))
}
fn write_entry(&mut self,loc: &EntryLocation,entry: &Entry) -> STDRESULT {
let mut dir = self.get_directory(loc.block as usize)?;
dir.set_entry(loc,*entry);
let buf = dir.to_bytes();
self.write_block(&buf,loc.block as usize,0)
}
fn expand_directory(&mut self, parent_loc: &EntryLocation) -> Result<EntryLocation,DYNERR> {
let mut entry = self.read_entry(&parent_loc)?;
if entry.storage_type()!=StorageType::SubDirEntry {
return Err(Box::new(Error::FileTypeMismatch));
}
let mut curr = entry.get_ptr();
for _try in 0..100 {
let mut dir = self.get_directory(curr as usize)?;
if dir.next()==0 {
match self.get_available_block()? { Some(avail) => {
entry.set_eof(entry.eof()+512);
entry.delta_blocks(1);
self.write_entry(parent_loc,&entry)?;
dir.set_links(None, Some(avail));
self.write_block(&dir.to_bytes(),curr as usize,0)?;
dir = Box::new(EntryBlock::new());
dir.set_links(Some(curr),Some(0));
self.write_block(&dir.to_bytes(),avail as usize,0)?;
return Ok(EntryLocation { block: avail, idx: 1});
} _ => {
return Err(Box::new(Error::DiskFull));
}}
}
curr = dir.next();
}
error!("directory block count not plausible, aborting");
Err(Box::new(Error::EndOfData))
}
fn get_available_entry(&mut self, key_block: u16) -> Result<EntryLocation,DYNERR> {
let mut curr = key_block;
for _try in 0..100 {
let mut dir = self.get_directory(curr as usize)?;
let locs = dir.entry_locations(curr);
for loc in locs {
if !dir.get_entry(&loc).is_active() {
return Ok(loc);
}
}
curr = dir.next();
if curr==0 {
dir = self.get_directory(key_block as usize)?;
match dir.parent_entry_loc() { Some(parent_loc) => {
return match self.expand_directory(&parent_loc) {
Ok(loc) => Ok(loc),
Err(e) => Err(e)
}
} _ => {
return Err(Box::new(Error::DirectoryFull));
}}
}
}
error!("directory block count not plausible, aborting");
Err(Box::new(Error::EndOfData))
}
fn search_entries(&mut self,stype: &Vec<StorageType>,name: &String,key_block: u16) -> Result<Option<EntryLocation>,DYNERR> {
if !is_name_valid(&name) {
error!("invalid ProDOS name {}",&name);
return Err(Box::new(Error::Syntax));
}
let mut curr = key_block;
for _try in 0..100 {
let dir = self.get_directory(curr as usize)?;
let locs = dir.entry_locations(curr);
for loc in locs {
let entry = dir.get_entry(&loc);
if entry.is_active() && is_file_match::<Entry>(stype,name,&entry) {
return Ok(Some(loc));
}
}
curr = dir.next();
if curr==0 {
return Ok(None);
}
}
error!("directory block count not plausible, aborting");
Err(Box::new(Error::EndOfData))
}
fn normalize_path(&mut self,vol_name: &str,path: &str) -> Result<Vec<String>,DYNERR> {
let mut path_nodes: Vec<String> = path.split("/").map(|s| s.to_string().to_uppercase()).collect();
if &path[0..1]!="/" {
path_nodes.insert(0,vol_name.to_string());
} else {
path_nodes = path_nodes[1..].to_vec();
}
let mut prefix_len = 0;
let mut rel_path_len = 0;
for s in path_nodes.iter() {
if rel_path_len>0 {
rel_path_len += 1 + s.len();
} else {
prefix_len += 1 + s.len();
if prefix_len>64 {
prefix_len -= 1 + s.len();
rel_path_len += 1 + s.len();
}
}
}
if rel_path_len>64 {
error!("ProDOS path too long, prefix {}, relative {}",prefix_len,rel_path_len);
return Err(Box::new(Error::Range));
}
return Ok(path_nodes);
}
fn split_path(&mut self,vol_name: &str,path: &str) -> Result<[String;2],DYNERR> {
let mut path_nodes = self.normalize_path(vol_name,path)?;
if path_nodes[path_nodes.len()-1].len()==0 {
path_nodes = path_nodes[0..path_nodes.len()-1].to_vec();
}
let name = path_nodes[path_nodes.len()-1].clone();
if path_nodes.len()<2 {
return Err(Box::new(Error::PathNotFound));
} else {
path_nodes = path_nodes[0..path_nodes.len()-1].to_vec();
}
let parent_path: String = path_nodes.iter().map(|s| "/".to_string() + s).collect::<Vec<String>>().concat();
return Ok([parent_path,name]);
}
fn search_volume(&mut self,file_types: &Vec<StorageType>,path: &str) -> Result<EntryLocation,DYNERR> {
let vhdr = self.get_vol_header()?;
let path_nodes = self.normalize_path(&vhdr.name(),path)?;
if &path_nodes[0]!=&vhdr.name() {
return Err(Box::new(Error::PathNotFound));
}
let n = path_nodes.len();
if n<3 && path_nodes[n-1]=="" {
return Err(Box::new(Error::PathNotFound));
}
let mut curr: u16 = VOL_KEY_BLOCK;
for level in 1..n {
let subdir = path_nodes[level].clone();
let file_types_now = match level {
l if l==n-1 => file_types.clone(),
_ => vec![StorageType::SubDirEntry]
};
match self.search_entries(&file_types_now, &subdir, curr)? { Some(loc) => {
if level==n-1 || level==n-2 && path_nodes[n-1]=="" && file_types.contains(&types::StorageType::SubDirEntry) {
return Ok(loc);
}
let entry = self.read_entry(&loc)?;
curr = entry.get_ptr();
} _ => {
return Err(Box::new(Error::PathNotFound));
}}
}
return Err(Box::new(Error::PathNotFound));
}
fn find_file(&mut self,path: &str) -> Result<EntryLocation,DYNERR> {
self.search_volume(&vec![StorageType::Seedling,StorageType::Sapling,StorageType::Tree],path)
}
fn find_dir_key_block(&mut self,path: &str) -> Result<u16,DYNERR> {
let vhdr = self.get_vol_header()?;
let vname = "/".to_string() + &vhdr.name().to_lowercase();
let vname2 = vname.clone() + "/";
if path=="/" || path=="" || path.to_lowercase()==vname || path.to_lowercase()==vname2 {
return Ok(VOL_KEY_BLOCK);
}
if let Ok(loc) = self.search_volume(&vec![StorageType::SubDirEntry], path) {
let entry = self.read_entry(&loc)?;
return Ok(entry.get_ptr());
}
return Err(Box::new(Error::PathNotFound));
}
fn read_index_block(
&mut self,entry: &Entry,index_ptr: u16,buf: &mut [u8],fimg: &mut super::FileImage,count: &mut usize,eof: &mut usize
) -> STDRESULT {
self.read_block(buf,index_ptr as usize,0)?;
let index_block = buf.to_vec();
for idx in 0..256 {
if *eof >= entry.eof() {
break;
}
let ptr = u16::from_le_bytes([index_block[idx],index_block[idx+256]]);
let mut bytes = 512;
if *eof + bytes > entry.eof() {
bytes = entry.eof() - *eof;
}
if ptr>0 {
self.read_block(buf,ptr as usize,0)?;
fimg.chunks.insert(*count,buf.to_vec());
}
*count += 1;
*eof += bytes;
}
Ok(())
}
fn deallocate_index_block(&mut self,index_ptr: u16,buf: &mut [u8]) -> STDRESULT {
self.read_block(buf,index_ptr as usize,0)?;
let index_block = buf.to_vec();
for idx in 0..256 {
let ptr = u16::from_le_bytes([index_block[idx],index_block[idx+256]]);
if ptr>0 {
self.deallocate_block(ptr as usize)?;
}
}
let swapped = [index_block[256..512].to_vec(),index_block[0..256].to_vec()].concat();
self.write_block(&swapped,index_ptr as usize,0)?;
self.deallocate_block(index_ptr as usize)?;
Ok(())
}
fn deallocate_file_blocks(&mut self,entry: &Entry) -> STDRESULT {
let mut buf: Vec<u8> = vec![0;512];
let master_ptr = entry.get_ptr();
match entry.storage_type() {
StorageType::Seedling => {
self.deallocate_block(master_ptr as usize)?;
},
StorageType::Sapling => {
self.deallocate_index_block(master_ptr, &mut buf)?;
},
StorageType::Tree => {
self.read_block(&mut buf,master_ptr as usize,0)?;
let master_block = buf.clone();
for idx in 0..256 {
let ptr = u16::from_le_bytes([master_block[idx],master_block[idx+256]]);
if ptr>0 {
self.deallocate_index_block(ptr, &mut buf)?;
}
}
let swapped = [master_block[256..512].to_vec(),master_block[0..256].to_vec()].concat();
self.write_block(&swapped,master_ptr as usize,0)?;
self.deallocate_block(master_ptr as usize)?;
}
_ => panic!("cannot read file of this type")
}
Ok(())
}
fn read_file(&mut self,entry: &Entry) -> Result<super::FileImage,DYNERR> {
let mut fimg = new_fimg(512,false,"temp")?;
entry.metadata_to_fimg(&mut fimg);
let mut buf: Vec<u8> = vec![0;512];
let master_ptr = entry.get_ptr();
let mut eof: usize = 0;
let mut count: usize = 0;
match entry.storage_type() {
StorageType::Seedling => {
self.read_block(&mut buf, master_ptr as usize, 0)?;
fimg.chunks.insert(0, buf.clone());
return Ok(fimg);
},
StorageType::Sapling => {
self.read_index_block(&entry, master_ptr, &mut buf, &mut fimg, &mut count, &mut eof)?;
return Ok(fimg);
},
StorageType::Tree => {
self.read_block(&mut buf,master_ptr as usize,0)?;
let master_block = buf.clone();
for idx in 0..256 {
let ptr = u16::from_le_bytes([master_block[idx],master_block[idx+256]]);
if ptr>0 {
self.read_index_block(entry, ptr, &mut buf, &mut fimg, &mut count, &mut eof)?;
} else {
count += 256;
eof += 256*512;
}
}
return Ok(fimg);
}
_ => panic!("cannot read file of this type")
}
}
fn ok_to_rename(&mut self,path: &str,new_name: &str) -> STDRESULT {
if !is_name_valid(new_name) {
error!("invalid ProDOS name {}",new_name);
return Err(Box::new(Error::Syntax));
}
let types = vec![StorageType::Seedling,StorageType::Sapling,StorageType::Tree,StorageType::SubDirEntry];
let vhdr = self.get_vol_header()?;
let [parent_path,_old_name] = self.split_path(&vhdr.name(),path)?;
if let Ok(key_block) = self.find_dir_key_block(&parent_path) {
if let Some(_loc) = self.search_entries(&types, &new_name.to_string(), key_block)? {
return Err(Box::new(Error::DuplicateFilename));
}
}
return Ok(());
}
fn prepare_to_write(&mut self,path: &str) -> Result<(String,u16,EntryLocation,u16),DYNERR> {
let types = vec![StorageType::Seedling,StorageType::Sapling,StorageType::Tree,StorageType::SubDirEntry];
let vhdr = self.get_vol_header()?;
let [parent_path,name] = self.split_path(&vhdr.name(),path)?;
if !is_name_valid(&name) {
error!("invalid ProDOS name {}",&name);
return Err(Box::new(Error::Syntax));
}
match self.find_dir_key_block(&parent_path) { Ok(key_block) => {
if let Some(_loc) = self.search_entries(&types, &name, key_block)? {
return Err(Box::new(Error::DuplicateFilename));
}
match self.get_available_entry(key_block) {
Ok(loc) => {
match self.get_available_block()? { Some(new_block) => {
return Ok((name,key_block,loc,new_block));
} _ => {
return Err(Box::new(Error::DiskFull));
}}
},
Err(e) => return Err(e)
}
} _ => {
return Err(Box::new(Error::PathNotFound));
}}
}
fn write_data_block_or_not(&mut self,count: usize,end: usize,ent: &mut Entry,buf_maybe: Option<&Vec<u8>>) -> Result<u16,DYNERR> {
let mut eof = ent.eof();
if let Some(buf) = buf_maybe {
match self.get_available_block()? { Some(data_block) => {
self.write_block(&buf,data_block as usize,0)?;
eof += match count {
c if c+1 < end => 512,
_ => buf.len()
};
ent.delta_blocks(1);
ent.set_eof(eof);
return Ok(data_block);
} _ => {
error!("block not available, but it should have been");
return Err(Box::new(Error::DiskFull));
}}
} else {
eof += 512;
ent.set_eof(eof);
return Ok(0);
}
}
fn write_file(&mut self,loc: EntryLocation,fimg: &super::FileImage) -> Result<usize,DYNERR> {
if fimg.chunks.len()==0 {
error!("empty data is not allowed for ProDOS file images");
return Err(Box::new(Error::EndOfData));
}
let mut storage = StorageType::Seedling;
let mut master_buf: Vec<u8> = vec![0;512];
let mut master_ptr: u16 = 0;
let mut master_count: u16 = 0;
let mut index_buf: Vec<u8> = vec![0;512];
let mut index_ptr: u16 = 0;
let mut index_count: u16 = 0;
let dir = self.get_directory(loc.block as usize)?;
let mut entry = dir.get_entry(&loc);
entry.set_eof(0);
for count in 0..fimg.end() {
let buf_maybe = fimg.chunks.get(&count);
if master_count > 127 {
return Err(Box::new(Error::DiskFull));
}
let blocks_needed = match storage {
StorageType::Seedling => {
match (buf_maybe,count) {
(_,i) if i==0 => 1, (None,_) => 1, (Some(_v),_) => 2 }
},
StorageType::Sapling => {
match (buf_maybe,index_count) {
(None,i) if i<256 => 0,
(Some(_v),i) if i<256 => 1, (None,_) => 1, (Some(_v),_) => 3 }
},
StorageType::Tree => {
match (buf_maybe,index_count,index_ptr) {
(None,_,_) => 0,
(Some(_v),i,p) if i<256 && p>0 => 1, (Some(_v),i,p) if i<256 && p==0 => 2, (Some(_v),_,_) => 2 }
}
_ => panic!("unexpected storage type during write")
};
if blocks_needed > self.num_free_blocks()? {
return Err(Box::new(Error::DiskFull));
}
match storage {
StorageType::Seedling => {
if count>0 {
storage = StorageType::Sapling;
entry.change_storage_type(storage);
index_ptr = self.get_available_block().expect("unreachable").unwrap();
self.allocate_block(index_ptr as usize)?;
entry.delta_blocks(1);
pack_index_ptr(&mut index_buf,entry.get_ptr(),0);
entry.set_ptr(index_ptr);
index_count += 1;
let curr = self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
pack_index_ptr(&mut index_buf, curr, index_count as usize);
self.write_block(&index_buf, index_ptr as usize, 0)?;
index_count += 1;
} else {
self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
}
},
StorageType::Sapling => {
if index_count > 255 {
storage = StorageType::Tree;
entry.change_storage_type(storage);
master_ptr = self.get_available_block().expect("unreachable").unwrap();
self.allocate_block(master_ptr as usize)?;
entry.set_ptr(master_ptr);
entry.delta_blocks(1);
pack_index_ptr(&mut master_buf,index_ptr,0);
master_count += 1;
index_ptr = 0;
index_count = 0;
index_buf = vec![0;512];
if buf_maybe!=None {
index_ptr = self.get_available_block().expect("unreachable").unwrap();
self.allocate_block(index_ptr as usize)?;
entry.delta_blocks(1);
let curr = self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
pack_index_ptr(&mut index_buf, curr, 0);
self.write_block(&index_buf,index_ptr as usize,0)?;
} else {
self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
}
index_count += 1;
} else {
let curr = self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
pack_index_ptr(&mut index_buf,curr,index_count as usize);
self.write_block(&index_buf,index_ptr as usize,0)?;
index_count += 1;
}
},
StorageType::Tree => {
if index_count > 255 {
master_count += 1;
index_ptr = 0;
index_count = 0;
index_buf = vec![0;512];
}
if index_ptr==0 && buf_maybe!=None {
index_ptr = self.get_available_block().expect("unreachable").unwrap();
self.allocate_block(index_ptr as usize)?;
entry.delta_blocks(1);
}
let curr = self.write_data_block_or_not(count,fimg.end(),&mut entry,buf_maybe)?;
pack_index_ptr(&mut index_buf, curr, index_count as usize);
if index_ptr > 0 {
self.write_block(&index_buf,index_ptr as usize,0)?;
}
pack_index_ptr(&mut master_buf, index_ptr, master_count as usize);
self.write_block(&master_buf,master_ptr as usize,0)?;
index_count += 1;
},
_ => panic!("unexpected storage type during write")
}
}
let eof = fimg.get_eof();
if eof>0 {
entry.set_eof(eof);
}
entry.overwrite_all_access(fimg.access[0]);
self.write_entry(&loc,&entry)?;
return Ok(eof);
}
fn modify(&mut self,loc: &EntryLocation,permissions: Attributes,maybe_new_name: Option<&str>,
maybe_new_type: Option<&str>,maybe_new_aux: Option<u16>) -> STDRESULT {
let dir = self.get_directory(loc.block as usize)?;
let mut entry = dir.get_entry(&loc);
if !entry.get_access(Access::Rename) && maybe_new_name!=None {
return Err(Box::new(Error::WriteProtected));
}
entry.set_access(permissions);
if let Some(new_name) = maybe_new_name {
entry.rename(new_name);
}
if let Some(new_type) = maybe_new_type {
match FileType::from_str(new_type) {
Ok(typ) => entry.set_ftype(typ as u8),
Err(e) => return Err(Box::new(e))
}
}
if let Some(new_aux) = maybe_new_aux {
entry.set_aux(new_aux);
}
self.write_entry(loc, &entry)?;
return Ok(());
}
fn glob_node(&mut self,pattern: &str,dir_block: u16,case_sensitive: bool) -> Result<Vec<String>,DYNERR> {
let mut files = Vec::new();
let glob = match case_sensitive {
true => globset::GlobBuilder::new(&pattern).literal_separator(true).build()?.compile_matcher(),
false => globset::GlobBuilder::new(&pattern.to_uppercase()).literal_separator(true).build()?.compile_matcher()
};
let mut curr = dir_block;
while curr>0 {
let dir = self.get_directory(curr as usize)?;
for loc in dir.entry_locations(curr) {
let entry = dir.get_entry(&loc);
if entry.is_active() {
let key = entry.name();
let test = match case_sensitive {
true => [self.curr_path.concat(),key.clone()].concat(),
false => [self.curr_path.concat(),key.clone()].concat().to_uppercase(),
};
if entry.storage_type()!=StorageType::SubDirEntry && glob.is_match(&test) {
let mut full_path = self.curr_path.concat();
full_path += &key;
files.push(full_path);
}
if entry.storage_type()==StorageType::SubDirEntry {
trace!("descend into directory {}",key);
self.curr_path.push(key + "/");
files.append(&mut self.glob_node(pattern,entry.get_ptr(),case_sensitive)?);
}
}
}
curr = dir.next();
}
self.curr_path.pop();
Ok(files)
}
fn tree_node(&mut self,dir_block: u16,include_meta: bool) -> Result<json::JsonValue,DYNERR> {
let mut files = json::JsonValue::new_object();
let mut curr = dir_block;
while curr>0 {
let dir = self.get_directory(curr as usize)?;
for loc in dir.entry_locations(curr) {
let entry = dir.get_entry(&loc);
if entry.is_active() {
let key = entry.name();
files[&key] = json::JsonValue::new_object();
if entry.storage_type()==StorageType::SubDirEntry {
trace!("descend into directory {}",key);
files[&key]["files"] = self.tree_node(entry.get_ptr(),include_meta)?;
}
if include_meta {
files[&key]["meta"] = entry.meta_to_json();
}
}
}
curr = dir.next();
}
Ok(files)
}
}
impl super::DiskFS for Disk {
fn new_fimg(&self, chunk_len: Option<usize>,set_time: bool,path: &str) -> Result<super::FileImage,DYNERR> {
match chunk_len {
Some(l) => new_fimg(l,set_time,path),
None => new_fimg(BLOCK_SIZE,set_time,path)
}
}
fn stat(&mut self) -> Result<super::Stat,DYNERR> {
let vheader = self.get_vol_header()?;
Ok(super::Stat {
fs_name: FS_NAME.to_string(),
label: vheader.name(),
users: Vec::new(),
block_size: BLOCK_SIZE,
block_beg: 0,
block_end: self.total_blocks,
free_blocks: self.num_free_blocks()? as usize,
raw: "".to_string()
})
}
fn catalog_to_stdout(&mut self, path: &str) -> STDRESULT {
let b = self.find_dir_key_block(path)?;
let mut dir = self.get_directory(b as usize)?;
println!();
if b==2 {
println!("{}{}","/".bright_blue().bold(),dir.name().bright_blue().bold());
} else {
println!("{}",dir.name().bright_blue().bold());
}
println!();
println!(" {:15} {:4} {:6} {:16} {:16} {:7} {:7}",
"NAME".bold(),"TYPE".bold(),"BLOCKS".bold(),
"MODIFIED".bold(),"CREATED".bold(),"ENDFILE".bold(),"SUBTYPE".bold());
println!();
let mut curr = b;
while curr>0 {
dir = self.get_directory(curr as usize)?;
for loc in dir.entry_locations(curr) {
let entry = dir.get_entry(&loc);
if entry.is_active() {
println!("{}",entry);
}
}
curr = dir.next();
}
println!();
let free = self.num_free_blocks()? as usize;
let used = self.total_blocks-free;
println!("BLOCKS FREE: {} BLOCKS USED: {} TOTAL BLOCKS: {}",free,used,self.total_blocks);
println!();
Ok(())
}
fn catalog_to_vec(&mut self, path: &str) -> Result<Vec<String>,DYNERR> {
let mut ans = Vec::new();
let mut curr = self.find_dir_key_block(path)?;
while curr>0 {
let dir = self.get_directory(curr as usize)?;
for loc in dir.entry_locations(curr) {
let entry = dir.get_entry(&loc);
if entry.is_active() {
ans.push(entry.universal_row());
}
}
curr = dir.next();
}
Ok(ans)
}
fn glob(&mut self,pattern: &str,case_sensitive: bool) -> Result<Vec<String>,DYNERR> {
let vhdr = self.get_vol_header()?;
let dir_block = self.find_dir_key_block("/")?;
let vol_path = ["/",&vhdr.name(),"/"].concat();
self.curr_path = vec![vol_path.clone()];
if pattern.starts_with("/") {
self.glob_node(pattern, dir_block, case_sensitive)
} else {
self.glob_node(&(vol_path + pattern), dir_block, case_sensitive)
}
}
fn tree(&mut self,include_meta: bool,indent: Option<u16>) -> Result<String,DYNERR> {
let vhdr = self.get_vol_header()?;
let dir_block = self.find_dir_key_block("/")?;
let mut tree = json::JsonValue::new_object();
tree["file_system"] = json::JsonValue::String(FS_NAME.to_string());
tree["files"] = self.tree_node(dir_block,include_meta)?;
tree["label"] = json::JsonValue::new_object();
tree["label"]["name"] = json::JsonValue::String(vhdr.name());
if let Some(spaces) = indent {
Ok(json::stringify_pretty(tree,spaces))
} else {
Ok(json::stringify(tree))
}
}
fn create(&mut self,path: &str) -> STDRESULT {
match self.prepare_to_write(path) {
Ok((name,key_block,loc,new_block)) => {
let mut dir = self.get_directory(key_block as usize)?;
dir.inc_file_count();
self.write_block(&dir.to_bytes(),key_block as usize,0)?;
let mut entry = Entry::create_subdir(&name,new_block,key_block,None);
entry.delta_blocks(1);
entry.set_eof(512);
self.write_entry(&loc,&entry)?;
let mut subdir = KeyBlock::<SubDirHeader>::new();
subdir.header.create(&name,loc.block,loc.idx as u8,None);
self.write_block(&subdir.to_bytes(),new_block as usize,0)?;
Ok(())
},
Err(e) => Err(e)
}
}
fn delete(&mut self,path: &str) -> STDRESULT {
if let Ok(loc) = self.find_file(path) {
let entry = self.read_entry(&loc)?;
if !entry.get_access(Access::Destroy) {
return Err(Box::new(Error::WriteProtected));
}
self.deallocate_file_blocks(&entry)?;
let mut dir = self.get_directory(loc.block as usize)?;
dir.delete_entry(&loc);
self.write_block(&dir.to_bytes(),loc.block as usize,0)?;
let (key_ptr,mut key_dir) = self.get_key_directory(loc.block)?;
key_dir.dec_file_count();
self.write_block(&key_dir.to_bytes(),key_ptr as usize,0)?;
return Ok(());
}
if let Ok(ptr) = self.find_dir_key_block(path) {
let mut dir = self.get_directory(ptr as usize)?;
match dir.parent_entry_loc() { Some(parent_loc) => {
let mut parent_dir = self.get_directory(parent_loc.block as usize)?;
if dir.file_count()>0 {
return Err(Box::new(Error::WriteProtected));
}
dir.delete();
self.write_block(&dir.to_bytes(),ptr as usize,0)?;
let mut next = ptr;
for _try in 0..100 {
self.deallocate_block(next as usize)?;
next = dir.next();
if next==0 {
parent_dir.delete_entry(&parent_loc);
self.write_block(&parent_dir.to_bytes(),parent_loc.block as usize,0)?;
let (key_ptr,mut key_dir) = self.get_key_directory(parent_loc.block)?;
key_dir.dec_file_count();
self.write_block(&key_dir.to_bytes(),key_ptr as usize,0)?;
return Ok(());
}
}
error!("directory block count not plausible, aborting");
return Err(Box::new(Error::EndOfData));
} _ => {
return Err(Box::new(Error::WriteProtected));
}}
}
return Err(Box::new(Error::PathNotFound));
}
fn set_attrib(&mut self,path: &str,permissions: Attributes,_password: Option<&str>) -> STDRESULT {
if let Ok(loc) = self.find_file(path) {
return self.modify(&loc,permissions,None,None,None);
}
if let Ok(ptr) = self.find_dir_key_block(path) {
let dir = self.get_directory(ptr as usize)?;
if let Some(parent_loc) = dir.parent_entry_loc() {
return self.modify(&parent_loc,permissions,None,None,None);
}
}
return Err(Box::new(Error::PathNotFound));
}
fn rename(&mut self,path: &str,name: &str) -> STDRESULT {
self.ok_to_rename(path, name)?;
if let Ok(loc) = self.find_file(path) {
return self.modify(&loc,Attributes::new(),Some(name),None,None);
}
if let Ok(ptr) = self.find_dir_key_block(path) {
let dir = self.get_directory(ptr as usize)?;
if let Some(parent_loc) = dir.parent_entry_loc() {
return self.modify(&parent_loc,Attributes::new(),Some(name),None,None);
}
}
return Err(Box::new(Error::PathNotFound));
}
fn retype(&mut self,path: &str,new_type: &str,sub_type: &str) -> STDRESULT {
match u16::from_str(sub_type) {
Ok(aux) => match self.find_file(path) {
Ok(loc) => {
self.modify(&loc, Attributes::new(), None,Some(new_type),Some(aux))
},
Err(e) => Err(e)
}
Err(e) => Err(Box::new(e))
}
}
fn get(&mut self,path: &str) -> Result<super::FileImage,DYNERR> {
match self.find_file(path) {
Ok(loc) => {
let entry = self.read_entry(&loc)?;
let mut fimg = self.read_file(&entry)?;
fimg.full_path = path.to_string();
return Ok(fimg);
},
Err(e) => return Err(e)
}
}
fn put(&mut self,fimg: &super::FileImage) -> Result<usize,DYNERR> {
if fimg.file_system!=FS_NAME {
error!("cannot write {} file image to prodos",fimg.file_system);
return Err(Box::new(Error::IOError));
}
if fimg.chunk_len!=512 {
error!("chunk length {} is incompatible with ProDOS",fimg.chunk_len);
return Err(Box::new(Error::Range));
}
match self.prepare_to_write(&fimg.full_path) {
Ok((name,dir_key_block,loc,new_key_block)) => {
let mut dir = self.get_directory(dir_key_block as usize)?;
dir.inc_file_count();
self.write_block(&dir.to_bytes(),dir_key_block as usize,0)?;
match Entry::create_file(&name,fimg,new_key_block,dir_key_block,None) {
Ok(entry) => self.write_entry(&loc,&entry)?,
Err(e) => return Err(e)
}
match self.write_file(loc,fimg) {
Ok(len) => Ok(len),
Err(e) => Err(e)
}
},
Err(e) => Err(e)
}
}
fn read_block(&mut self,num: &str) -> Result<Vec<u8>,DYNERR> {
match usize::from_str(num) {
Ok(block) => {
let mut buf: Vec<u8> = vec![0;512];
if block>=self.total_blocks {
return Err(Box::new(Error::Range));
}
self.read_block(&mut buf,block,0)?;
Ok(buf)
},
Err(e) => Err(Box::new(e))
}
}
fn write_block(&mut self, num: &str, dat: &[u8]) -> Result<usize,DYNERR> {
match usize::from_str(num) {
Ok(block) => {
if dat.len() > 512 || block>=self.total_blocks {
return Err(Box::new(Error::Range));
}
self.zap_block(dat,block,0)?;
Ok(dat.len())
},
Err(e) => Err(Box::new(e))
}
}
fn standardize(&mut self,ref_con: u16) -> HashMap<Block,Vec<usize>> {
let mut ans: HashMap<Block,Vec<usize>> = HashMap::new();
let mut curr = ref_con;
while curr>0 {
let mut dir = self.get_directory(curr as usize).expect("disk error");
let locs = dir.entry_locations(curr);
super::add_ignorable_offsets(&mut ans,Block::PO(curr as usize),dir.standardize(0));
for loc in locs {
let mut entry = dir.get_entry(&loc);
let offset = 4 + (loc.idx-1)*0x27;
super::add_ignorable_offsets(&mut ans,Block::PO(curr as usize),entry.standardize(offset));
if entry.storage_type()==StorageType::SubDirEntry {
let sub_map = self.standardize(entry.get_ptr());
super::combine_ignorable_offsets(&mut ans, sub_map);
}
}
curr = dir.next();
}
return ans;
}
fn compare(&mut self,path: &std::path::Path,ignore: &HashMap<Block,Vec<usize>>) {
self.writeback_bitmap_buffer().expect("disk error");
let mut emulator_disk = crate::create_fs_from_file(&path.to_str().unwrap(),None).expect("read error");
let vhdr = self.get_vol_header().expect("disk error");
for block in 0..vhdr.total_blocks() {
let addr = Block::PO(block as usize);
let mut actual = self.img.read_block(addr).expect("bad sector access");
let mut expected = emulator_disk.get_img().read_block(addr).expect("bad sector access");
if let Some(ignorable) = ignore.get(&addr) {
for offset in ignorable {
actual[*offset] = 0;
expected[*offset] = 0;
}
}
for row in 0..16 {
let mut fmt_actual = String::new();
let mut fmt_expected = String::new();
let offset = row*32;
write!(&mut fmt_actual,"{:02X?}",&actual[offset..offset+32].to_vec()).expect("format error");
write!(&mut fmt_expected,"{:02X?}",&expected[offset..offset+32].to_vec()).expect("format error");
assert_eq!(fmt_actual,fmt_expected," at block {}, row {}",block,row)
}
}
}
fn get_img(&mut self) -> &mut Box<dyn img::DiskImage> {
self.writeback_bitmap_buffer().expect("could not write back bitmap buffer");
&mut self.img
}
}
#[test]
fn test_path_normalize() {
let img = Box::new(crate::img::dsk_po::PO::create(280));
let mut disk = Disk::from_img(img).expect("failed to create disk");
disk.format(&String::from("NEW.DISK"),true,None).expect("disk error");
match disk.normalize_path("NEW.DISK","DIR1") {
Ok(res) => assert_eq!(res,["NEW.DISK","DIR1"]),
Err(e) => panic!("{}",e)
}
match disk.normalize_path("NEW.DISK","dir1/") {
Ok(res) => assert_eq!(res,["NEW.DISK","DIR1",""]),
Err(e) => panic!("{}",e)
}
match disk.normalize_path("NEW.DISK","dir1/sub2") {
Ok(res) => assert_eq!(res,["NEW.DISK","DIR1","SUB2"]),
Err(e) => panic!("{}",e)
}
match disk.normalize_path("NEW.DISK","/new.disk/dir1/sub2") {
Ok(res) => assert_eq!(res,["NEW.DISK","DIR1","SUB2"]),
Err(e) => panic!("{}",e)
}
match disk.normalize_path("NEW.DISK","abcdefghijklmno/abcdefghijklmno/abcdefghijklmno/abcdefghijklmno/abcdefghijklmno/abcdefghijklmno/abcdefghijklmno/abcdefghijklmno") {
Ok(_res) => panic!("normalize_path should have failed with path too long"),
Err(e) => assert_eq!(e.to_string(),"RANGE ERROR")
}
}