Skip to content

Commit

Permalink
ranged access, polish TD0 support
Browse files Browse the repository at this point in the history
  • Loading branch information
dfgordon committed Sep 3, 2023
1 parent 7c23ebc commit 207cd04
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 115 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ num-traits = "0.2.14"
num-derive = "0.3.3"
a2kit_macro = "0.1.0"
a2kit_macro_derive = "0.1.0"
retrocompressor = "0.1.0"
retrocompressor = "0.1.1"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Command line interface and library for manipulating retro disk images, file syst
- full read and write access
- high or low level manipulations
- interface for handling sparse (random access) files
* Disk Images - 2MG, D13, DO, DSK, IMD, NIB, PO, WOZ
* Disk Images - 2MG, D13, DO, DSK, IMD, NIB, PO, TD0, WOZ

## Documentation

Expand Down
6 changes: 3 additions & 3 deletions src/bios/dpb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pub const DSDD_525_OFF1: DiskParameterBlock = DiskParameterBlock {

// This covers Amstrad PCW9512 and maybe PCW8256 (at least 1 image has extra cylinder with 8 sectors)
// There is a "superblock" at track 0 sector 0 [40 cyl, 9 secs, sector shift 2, off 1, bsh 3, dir blocks 2]
pub const SSDD_525_AMSTRAD_184K: DiskParameterBlock = DiskParameterBlock {
pub const SSDD_3: DiskParameterBlock = DiskParameterBlock {
spt: 36,
bsh: 3,
blm: 7,
Expand Down Expand Up @@ -213,7 +213,7 @@ impl DiskParameterBlock {
crate::img::names::OSBORNE1_DD_KIND => SSDD_525_OFF3,
crate::img::names::KAYPROII_KIND => SSDD_525_OFF1,
crate::img::names::KAYPRO4_KIND => DSDD_525_OFF1,
crate::img::names::AMSTRAD_184K_KIND => SSDD_525_AMSTRAD_184K,
crate::img::names::AMSTRAD_SS_KIND => SSDD_3,
crate::img::names::TRS80_M2_CPM_KIND => TRS80_M2,
crate::img::names::NABU_CPM_KIND => NABU,
_ => panic!("Disk kind not supported")
Expand Down Expand Up @@ -353,7 +353,7 @@ impl fmt::Display for DiskParameterBlock {
SSDD_525_OFF1 => write!(f,"IBM 5.25 inch SSDD"),
SSDD_525_OFF3 => write!(f,"IBM 5.25 inch SSDD"),
DSDD_525_OFF1 => write!(f,"IBM 5.25 inch DSSD"),
SSDD_525_AMSTRAD_184K => write!(f,"IBM 5.25 inch SSDD"),
SSDD_3 => write!(f,"IBM 3 inch SSDD"),
TRS80_M2 => write!(f,"IBM 8 inch SSDD"),
NABU => write!(f,"IBM 8 inch DSDD"),
_ => write!(f,"unknown disk")
Expand Down
9 changes: 8 additions & 1 deletion src/commands/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ pub fn get(cmd: &clap::ArgMatches) -> STDRESULT {
Ok(ItemType::Binary) => disk.bload(&src_path),
Ok(ItemType::Text) => disk.read_text(&src_path),
Ok(ItemType::Raw) => disk.read_raw(&src_path,trunc),
Ok(ItemType::Block) => disk.read_block(&src_path),
Ok(ItemType::Block) => {
let mut cum: Vec<u8> = Vec::new();
let blocks = super::parse_block_request(&src_path)?;
for b in blocks {
cum.append(&mut disk.read_block(&b.to_string())?.1);
}
Ok((0,cum))
},
_ => Err::<(u16,Vec<u8>),DYNERR>(Box::new(CommandError::UnsupportedItemType))
};
return output_get(maybe_object,typ,Some(disk));
Expand Down
43 changes: 10 additions & 33 deletions src/commands/get_img.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,13 @@
use clap;
use std::io::Write;
use std::str::FromStr;
use log::{debug,error};
use log::error;
use super::{ItemType,CommandError};
use crate::img::DiskImage;
use crate::{STDRESULT,DYNERR};
use crate::STDRESULT;

const RCH: &str = "unreachable was reached";

fn parse_sector(farg: &str) -> Result<[usize;3],DYNERR> {
let fcopy = String::from(farg);
let it: Vec<&str> = fcopy.split(',').collect();
if it.len()!=3 {
error!("sector specification should be in form `cylinder,head,sector`");
return Err(Box::new(CommandError::InvalidCommand));
}
let cyl = usize::from_str(it[0])?;
let head = usize::from_str(it[1])?;
let sec = usize::from_str(it[2])?;
debug!("user requested cyl {} head {} sec {}",cyl,head,sec);
Ok([cyl,head,sec])
}

fn parse_track(farg: &str) -> Result<[usize;2],DYNERR> {
let fcopy = String::from(farg);
let it: Vec<&str> = fcopy.split(',').collect();
if it.len()!=2 {
error!("track specification should be in form `cylinder,head`");
return Err(Box::new(CommandError::InvalidCommand));
}
let cyl = usize::from_str(it[0])?;
let head = usize::from_str(it[1])?;
debug!("user requested cyl {} head {}",cyl,head);
Ok([cyl,head])
}

fn output_get(dat: Vec<u8>,typ: ItemType,img: Box<dyn DiskImage>) {
if atty::is(atty::Stream::Stdout) {
match typ {
Expand All @@ -61,15 +34,19 @@ pub fn get(cmd: &clap::ArgMatches) -> STDRESULT {
Ok(mut img) => {
let bytes = match typ {
ItemType::Sector => {
let [cyl,head,sec] = parse_sector(&src_path)?;
img.read_sector(cyl, head, sec)?
let mut cum: Vec<u8> = Vec::new();
let sector_list = super::parse_sector_request(&src_path)?;
for [cyl,head,sec] in sector_list {
cum.append(&mut img.read_sector(cyl,head,sec)?);
}
cum
},
ItemType::Track => {
let [cyl,head] = parse_track(&src_path)?;
let [cyl,head] = super::parse_track_request(&src_path)?;
img.get_track_nibbles(cyl, head)?
},
ItemType::RawTrack => {
let [cyl,head] = parse_track(&src_path)?;
let [cyl,head] = super::parse_track_request(&src_path)?;
img.get_track_buf(cyl, head)?
},
_ => panic!("{}",RCH)
Expand Down
4 changes: 2 additions & 2 deletions src/commands/mkdsk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ fn mkimage(img_typ: &DiskImageType,kind: &DiskKind,maybe_vol: Option<&String>,ma
(DiskImageType::IMD,names::KAYPRO4_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::TRS80_M2_CPM_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::NABU_CPM_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::AMSTRAD_184K_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::IMD,names::AMSTRAD_SS_KIND) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::TD0,names::IBM_CPM1_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::OSBORNE1_SD_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::OSBORNE1_DD_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::KAYPROII_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::KAYPRO4_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::TRS80_M2_CPM_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::NABU_CPM_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::AMSTRAD_184K_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::TD0,names::AMSTRAD_SS_KIND) => Ok(Box::new(img::td0::Td0::create(*kind))),
_ => {
error!("pairing of image type and disk kind is not supported");
Err(Box::new(CommandError::UnsupportedItemType))
Expand Down
132 changes: 132 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub mod get_img;
pub mod put_img;

use std::str::FromStr;
use log::{debug,error};

use crate::DYNERR;

#[derive(thiserror::Error,Debug)]
pub enum CommandError {
Expand Down Expand Up @@ -84,3 +87,132 @@ impl FromStr for ItemType {
}
}

const SEC_MESS: &str =
"sector specification should be <cyl>,<head>,<sec>` or a range";

fn parse_range(range: &str) -> Result<[usize;2],DYNERR> {
let mut ans = [0,1];
let mut lims = range.split("..");
for j in 0..2 {
match (j,lims.next()) {
(0,Some(lim)) => {
ans[0] = usize::from_str(lim)?;
},
(1,Some(lim)) => {
ans[1] = usize::from_str(lim)?;
if ans[1] <= ans[0] {
error!("sector range end was <= start");
return Err(Box::new(CommandError::InvalidCommand));
}
},
(1,None) => {
ans[1] = ans[0] + 1;
},
_ => panic!("unexpected pattern parsing sector request")
}
}
if lims.next().is_some() {
error!("range specification should be in form `<beg>[..<end>]`");
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(ans)
}

/// parse a sector request in the form `c1[..c2],h1[..h2],s1[..s2][,,next_range]`
fn parse_sector_request(farg: &str) -> Result<Vec<[usize;3]>,DYNERR> {
let mut ans: Vec<[usize;3]> = Vec::new();
let mut contiguous_areas = farg.split(",,");
while let Some(contig) = contiguous_areas.next() {
let mut ranges = contig.split(',');
let mut bounds_set: Vec<[usize;2]> = Vec::new();
for _i in 0..3 {
match ranges.next() {
Some(range) => {
let rng = parse_range(range)?;
bounds_set.push(rng);
},
None => {
error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
}
}
if ranges.next().is_some() {
error!("{}",SEC_MESS);
return Err(Box::new(CommandError::InvalidCommand));
}
for cyl in bounds_set[0][0]..bounds_set[0][1] {
for head in bounds_set[1][0]..bounds_set[1][1] {
for sec in bounds_set[2][0]..bounds_set[2][1] {
ans.push([cyl,head,sec]);
if ans.len()>4*(u16::MAX as usize) {
error!("sector request has too many sectors");
return Err(Box::new(CommandError::InvalidCommand));
}
}
}
}
}
Ok(ans)
}

/// parse a sector request in the form `cyl,head` (ranges not allowed)
fn parse_track_request(farg: &str) -> Result<[usize;2],DYNERR> {
let fcopy = String::from(farg);
let it: Vec<&str> = fcopy.split(',').collect();
if it.len()!=2 {
error!("track specification should be in form `cylinder,head`");
return Err(Box::new(CommandError::InvalidCommand));
}
let cyl = usize::from_str(it[0])?;
let head = usize::from_str(it[1])?;
debug!("user requested cyl {} head {}",cyl,head);
Ok([cyl,head])
}

/// parse a sector request in the form `b1[..b2][,,next_range]`
fn parse_block_request(farg: &str) -> Result<Vec<usize>,DYNERR> {
let mut ans: Vec<usize> = Vec::new();
let mut contiguous_areas = farg.split(",,");
while let Some(contig) = contiguous_areas.next() {
if contig.contains(",") {
error!("unexpected single comma in block request");
return Err(Box::new(CommandError::InvalidCommand));
}
let rng = parse_range(contig)?;
for b in rng[0]..rng[1] {
ans.push(b);
if ans.len()>4*(u16::MAX as usize) {
error!("block request has too many blocks");
return Err(Box::new(CommandError::InvalidCommand));
}
}
}
Ok(ans)
}

#[test]
fn test_parse_sec_req() {
let single = "2,0,3";
let contig = "2..4,0,3..5";
let non_contig = "2..4,0,3..5,,32..34,0,0..2";
let single_list = parse_sector_request(single).expect("could not parse");
assert_eq!(single_list,vec![[2,0,3]]);
let contig_list = parse_sector_request(contig).expect("could not parse");
assert_eq!(contig_list,vec![[2,0,3],[2,0,4],[3,0,3],[3,0,4]]);
let non_contig_list = parse_sector_request(non_contig).expect("could not parse");
assert_eq!(non_contig_list,vec![[2,0,3],[2,0,4],[3,0,3],[3,0,4],[32,0,0],[32,0,1],[33,0,0],[33,0,1]]);
}

#[test]
fn test_parse_block_req() {
let single = "1";
let contig = "1..4";
let non_contig = "1..4,,6,,8..10";
let single_list = parse_block_request(single).expect("could not parse");
assert_eq!(single_list,vec![1]);
let contig_list = parse_block_request(contig).expect("could not parse");
assert_eq!(contig_list,vec![1,2,3]);
let non_contig_list = parse_block_request(non_contig).expect("could not parse");
assert_eq!(non_contig_list,vec![1,2,3,6,8,9]);
}
27 changes: 26 additions & 1 deletion src/commands/put.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use super::{ItemType,CommandError};
use crate::fs::{Records,FileImage};
use crate::{STDRESULT,DYNERR};

const RANGED_ACCESS: &str =
"Writing to multiple blocks is only allowed if the buffers match exactly";

pub fn put(cmd: &clap::ArgMatches) -> STDRESULT {
if atty::is(atty::Stream::Stdin) {
error!("cannot use `put` with console input, please pipe something in");
Expand Down Expand Up @@ -62,7 +65,29 @@ pub fn put(cmd: &clap::ArgMatches) -> STDRESULT {
}
},
Ok(ItemType::Raw) => disk.write_text(&dest_path,&file_data),
Ok(ItemType::Block) => disk.write_block(&dest_path,&file_data),
Ok(ItemType::Block) => {
let mut ptr = 0;
let blocks = super::parse_block_request(&dest_path)?;
for b in &blocks {
// read the block to get its length
let block_len = disk.read_block(&b.to_string())?.1.len();
if ptr + block_len > file_data.len() && block_len > 1 {
error!("{}",RANGED_ACCESS);
return Err(Box::new(CommandError::InvalidCommand));
}
if ptr >= file_data.len() {
error!("{}",RANGED_ACCESS);
return Err(Box::new(CommandError::InvalidCommand));
}
disk.write_block(&b.to_string(),&file_data[ptr..ptr+block_len])?;
ptr += block_len;
}
if blocks.len() > 1 && ptr != file_data.len() {
error!("{}",RANGED_ACCESS);
return Err(Box::new(CommandError::InvalidCommand));
}
Ok(ptr)
},
Ok(ItemType::Records) => match std::str::from_utf8(&file_data) {
Ok(s) => match Records::from_json(s) {
Ok(recs) => disk.write_records(&dest_path,&recs),
Expand Down

0 comments on commit 207cd04

Please sign in to comment.