Skip to content

Commit

Permalink
Support SUBMIT.COM #16
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanizag committed Jul 13, 2024
1 parent 8ebd6c0 commit 55fbb16
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/bdos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub fn execute_bdos(bdos: &mut Bdos, bios: &mut Bios, console: &mut dyn ConsoleE
res16 = Some(get_version());
},
13 => { // DRV_ALLRESET - Reset disk system
env.state.reset();
res8 = Some(bdos_drive::all_reset(env));
},
14 => { // DRV_SET - Select disk
bdos_drive::select(env, arg8);
Expand Down
40 changes: 40 additions & 0 deletions src/bdos_drive.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
use std::fs;
use std::io;

use crate::bdos_environment::*;
use crate::constants::*;
use crate::fcb::name_to_8_3;
use iz80::Machine;

pub fn all_reset(env: &mut BdosEnvironment) -> u8{
// The Reset Disk function is used to programmatically restore the file
// system to a reset state where all disks are set to Read-Write. Only
// disk drive A is selected, and the default DMA address is reset to
// BOOT+0080H. This function can be used, for example, by an application
// program that requires a disk change without a system reboot.
// In versions 1 and 2, logs in drive A: and returns 0FFh if there is a file
// present whose name begins with a $, otherwise 0. Replacement BDOSses may
// modify this behaviour.
env.state.reset();

match has_dollar_file(env) {
Ok(true) => 0xff,
_ => 0,
}
}

fn has_dollar_file(env: &mut BdosEnvironment) -> io::Result<bool> {
let path = env.get_directory(0, false)
.ok_or(io::Error::new(io::ErrorKind::Other, "No directory assigned to drive"))?;
let dir = fs::read_dir(path)?;

for entry in dir {
let file = entry?;
if file.file_type()?.is_file() {
let os_name = file.file_name();
if let Some(cpm_name) = name_to_8_3(&os_name.to_string_lossy()) {
if cpm_name.starts_with("$") {
return Ok(true);
}
}
}
}
Ok(false)
}

pub fn select(env: &mut BdosEnvironment, selected: u8) {
// The Select Disk function designates the disk drive named in register E as
// the default disk for subsequent file operations, with E = 0 for drive A,
Expand Down
70 changes: 59 additions & 11 deletions src/bdos_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ pub fn open(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
Err(_) => FILE_NOT_FOUND, // Error or file not found
Ok(paths) => match fs::File::open(&paths[0]) {
Err(_) => FILE_NOT_FOUND,
Ok(_) => {
fcb.init(env);
DIRECTORY_CODE
Ok(os_file) => match os_file_size_records(os_file) {
Err(_) => FILE_NOT_FOUND,
Ok(record_count) => {
fcb.init(env, record_count);
DIRECTORY_CODE
}
}
}
}
Expand All @@ -96,7 +99,7 @@ pub fn make(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
match create_file(env, &fcb) {
Err(_) => FILE_NOT_FOUND, // Error or file not found
Ok(_) => {
fcb.init(env);
fcb.init(env, 0);
DIRECTORY_CODE
}
}
Expand All @@ -116,8 +119,31 @@ pub fn close(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
let fcb = Fcb::new(fcb_address);
match find_host_files(env, &fcb, false, false){
Err(_) => FILE_NOT_FOUND, // Error or file not found
Ok(_) => DIRECTORY_CODE
Ok(paths) => match truncate_if_needed(env, &fcb, &paths[0]) {
Err(_) => FILE_NOT_FOUND,
Ok(_) => DIRECTORY_CODE
}
}
}

fn truncate_if_needed(env: &mut BdosEnvironment, fcb: &Fcb, os_file_name: &OsString) -> io::Result<()> {
let os_file = fs::File::open(os_file_name)?;
let record_count = os_file_size_records(os_file)?;
let (extent_is_full, fcb_record_count) = fcb.get_record_count(env);
if extent_is_full {
return Ok(()); // No truncation needed and it could not be the last extent.
}
if record_count == fcb_record_count as u32 {
return Ok(());
}

if env.call_trace {
println!("Truncating file from {} to {}", record_count, fcb_record_count);
}

let file = fs::OpenOptions::new().write(true).open(os_file_name)?;
file.set_len(fcb_record_count as u64 * RECORD_SIZE as u64)?;
return Ok(());
}

pub fn delete(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
Expand Down Expand Up @@ -214,16 +240,30 @@ pub fn read(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
print!("[Read record {:x} into {:04x}]", record, env.state.dma);
}

fcb.inc_current_record(env);
let extent_changed = fcb.inc_current_record(env);

let mut buffer: Buffer = [0; RECORD_SIZE];
let res = read_record_in_buffer(env, &fcb, record as u16, &mut buffer).unwrap_or(NO_DATA);
if res == DIRECTORY_CODE {
env.store_buffer_to_dma(&buffer);
}

if extent_changed {
match update_record_count(env, &mut fcb) {
Err(_) => return NO_DATA,
Ok(_) => {}
}
}
res
}

fn update_record_count(env: &mut BdosEnvironment, fcb: &mut Fcb) -> io::Result<()> {
let record_count = compute_file_size_internal(env, &fcb)?;
fcb.update_record_count(env, record_count);
Ok(())
}


pub fn write(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
// Given that the FCB addressed by DE has been activated through an Open or
// Make function, the Write Sequential function writes the 128-byte data
Expand All @@ -241,10 +281,17 @@ pub fn write(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
if env.call_trace {
print!("[Write record {:x} from {:04x}]", record, env.state.dma);
}
fcb.inc_current_record(env);

let buffer = env.load_buffer_from_dma();
write_record_from_buffer(env, &fcb, record as u16, &buffer).unwrap_or(NO_DATA)
let result = write_record_from_buffer(env, &fcb, record as u16, &buffer).unwrap_or(NO_DATA);

fcb.inc_current_record(env);
match update_record_count(env, &mut fcb) {
Err(_) => return NO_DATA,
Ok(_) => {}
}

result
}

pub fn read_rand(env: &mut BdosEnvironment, fcb_address: u16) -> u8 {
Expand Down Expand Up @@ -499,7 +546,10 @@ pub fn compute_file_size(env: &mut BdosEnvironment, fcb_address: u16) {
fn compute_file_size_internal(env: &mut BdosEnvironment, fcb: &Fcb) -> io::Result<u32> {
let paths = find_host_files(env, fcb, false, false)?;
let os_file = fs::File::open(&paths[0])?;
os_file_size_records(os_file)
}

fn os_file_size_records(os_file: fs::File) -> io::Result<u32> {
let file_size = os_file.metadata()?.len();
let mut record = file_size / RECORD_SIZE as u64;
if file_size % RECORD_SIZE as u64 != 0 {
Expand Down Expand Up @@ -588,14 +638,12 @@ fn write_record_from_buffer(env: &mut BdosEnvironment, fcb: &Fcb, record: u16, b
Ok(0)
}



fn search_nth(env: &mut BdosEnvironment) -> io::Result<u8> {
// For search_first and search_next, I will store a global index for the
// position. I don't know if BDOS was storing the state on the FCB or
// globally. [Later] Yes, it does.
let path = env.get_directory(env.state.dir_drive, false)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No directory assigned to drive"))?;
.ok_or(io::Error::new(io::ErrorKind::Other, "No directory assigned to drive"))?;
let dir = fs::read_dir(path)?;

let mut i = 0;
Expand Down
38 changes: 34 additions & 4 deletions src/fcb.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::cmp::min;

use iz80::Machine;
use crate::bdos_environment::BdosEnvironment;

Expand Down Expand Up @@ -78,10 +80,26 @@ impl Fcb {
env.machine.poke(self.address + offset, v)
}

pub fn init(&mut self, env: &mut BdosEnvironment) {
pub fn init(&mut self, env: &mut BdosEnvironment, record_count: u32) {
self.set_byte(env, FCB_EXTENT_OFFSET, 0);
self.set_byte(env, FCB_CURRENT_RECORD_OFFSET, 0);
self.set_byte(env, FCB_RECORD_COUNT_OFFSET, 0);

let first_extent_record_count = min(record_count, EXTENT_SIZE as u32) as u8;
self.set_byte(env, FCB_RECORD_COUNT_OFFSET, first_extent_record_count);
}

pub fn update_record_count(&mut self, env: &mut BdosEnvironment, record_count: u32) {
/*
The record count must reflect the size of the current extent. For us
only the final extent can have a record count less than 128.
*/
let extent = self.get_byte(env, FCB_EXTENT_OFFSET);
if (extent as u32) * (EXTENT_SIZE as u32) < record_count {
// We are not at the last extent:
self.set_byte(env, FCB_RECORD_COUNT_OFFSET, EXTENT_SIZE);
} else {
self.set_byte(env, FCB_RECORD_COUNT_OFFSET, (record_count % EXTENT_SIZE as u32) as u8);
}
}

pub fn get_name(&self, env: &mut BdosEnvironment) -> String {
Expand Down Expand Up @@ -158,14 +176,16 @@ impl Fcb {
+ (self.get_byte(env, FCB_CURRENT_RECORD_OFFSET) as u16)
}

pub fn inc_current_record(&mut self, env: &mut BdosEnvironment) {
let cr = 1 + self.get_byte(env, FCB_CURRENT_RECORD_OFFSET);
pub fn inc_current_record(&mut self, env: &mut BdosEnvironment) -> bool {
let cr = 1 + (self.get_byte(env, FCB_CURRENT_RECORD_OFFSET) % EXTENT_SIZE);
if cr == EXTENT_SIZE {
self.set_byte(env, FCB_CURRENT_RECORD_OFFSET, 0);
let v = 1 + self.get_byte(env, FCB_EXTENT_OFFSET);
self.set_byte(env, FCB_EXTENT_OFFSET, v);
true // Extent changed
} else {
self.set_byte(env, FCB_CURRENT_RECORD_OFFSET, cr);
false // Extent not changed
}
}

Expand All @@ -180,6 +200,16 @@ impl Fcb {
self.set_byte(env, FCB_RANDOM_RECORD_OFFSET + 1, (record >> 8) as u8);
self.set_byte(env, FCB_RANDOM_RECORD_OFFSET + 2, (record >> 16) as u8);
}

pub fn get_record_count(&self, env: &mut BdosEnvironment) -> (bool, u16) {
if self.get_byte(env, FCB_RECORD_COUNT_OFFSET) == EXTENT_SIZE {
(true, EXTENT_SIZE as u16)
} else {
let record_count = (EXTENT_SIZE as u16) * (self.get_byte(env, FCB_EXTENT_OFFSET) as u16)
+ (self.get_byte(env, FCB_RECORD_COUNT_OFFSET) as u16);
(false, record_count)
}
}
}

/*
Expand Down
Binary file added tests/artifacts/slash.com
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/artifacts/slash.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
File slash.com downloaded from https://github.com/skx/cpm-dist/tree/master/A
The source code is also there.
16 changes: 16 additions & 0 deletions tests/issue16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mod common;
use common::*;
use izcpm::Step;

// Integration tests for issue https://github.com/ivanizag/iz-cpm/issues/16
#[test]
fn test_issue16() {
run_script_with_args(vec!(
Step::Expect("A>"),
Step::Input("SLASH DIR;XYZ\r"),
Step::Expect("A$DIR"),
Step::Expect("A$XYZ"),
Step::Expect("A>"),
), vec!("-b", "tests/artifacts")
);
}

0 comments on commit 55fbb16

Please sign in to comment.