use super::cics::{EIBFN, has};
use super::*;
use crate::cics::Browse;
use crate::files::{self, Keyed, Keying};
fn find(keyed: &Keyed, key: &[u8], generic: bool, gteq: bool) -> Option<(Vec<u8>, Vec<u8>)> {
let (k, r) = keyed.seek(key, true, false)?;
let matches = if generic { k.starts_with(key) } else { k == key };
(matches || gteq).then_some((k, r))
}
fn condition_of(status: &str) -> &'static str {
match status {
"22" => "DUPREC",
"23" => "NOTFND",
_ => "INVREQ",
}
}
impl<'p> Machine<'p, '_, '_> {
pub(super) fn cics_file(&mut self, block: &'p ExecBlock) -> R<Flow> {
let named = match self.arg_text(block, "FILE")? {
Some(f) => Some(f),
None => self.arg_text(block, "DATASET")?,
};
let Some(file) = named.map(|f| f.to_ascii_uppercase()) else {
return Err(Abend::ironwork(format!("EXEC CICS {} needs FILE", block.command), block.pos));
};
self.eib_text(super::cics::EIBRSRCE, 8, &file);
match block.command.as_str() {
"READ" => self.file_read(block, &file),
"WRITE" => self.file_write(block, &file),
"REWRITE" => self.file_rewrite(block, &file),
"DELETE" => self.file_delete(block, &file),
"UNLOCK" => {
self.release_hold(&file);
self.cics_ok(block)
}
"STARTBR" => self.file_startbr(block, &file, false),
"RESETBR" => self.file_startbr(block, &file, true),
"READNEXT" => self.file_browse(block, &file, false),
"READPREV" => self.file_browse(block, &file, true),
"ENDBR" => {
let reqid = self.reqid(block)?;
match self.unit.cics.as_mut().and_then(|t| t.browses.remove(&(file, reqid))) {
Some(_) => self.cics_ok(block),
None => self.raise(block, "INVREQ", 0),
}
}
other => Err(Abend::ironwork(format!("EXEC CICS {other} is not supported yet"), block.pos)),
}
}
fn on_file<T>(&mut self, file: &str, op: impl FnOnce(&mut Self, &mut Keyed) -> R<T>) -> R<Result<T, &'static str>> {
let mut open = match self.unit.cics_files.remove(file) {
Some(open) => open,
None => {
let Some(def) = self.unit.cics.as_ref().and_then(|t| t.files.get(file)).cloned() else { return Ok(Err("FILENOTFOUND")) };
match files::open_keyed(Some(&def.dd), OpenMode::InputOutput, def.format(), def.keying(), def.record_len, self.page) {
Ok(open) => open,
Err(_) => return Ok(Err("NOTOPEN")),
}
}
};
let result = match open.keyed() {
Some(keyed) => op(self, keyed).map(Ok),
None => Ok(Err("INVREQ")),
};
self.unit.cics_files.insert(file.to_owned(), open);
result
}
fn ridfld(&mut self, block: &ExecBlock, keying: &Keying) -> R<(Vec<u8>, bool)> {
if has(block, "RRN") || *keying == Keying::Relative {
let n = self.arg_int(block, "RIDFLD")?.unwrap_or(0);
return Ok((files::record_number(n.max(0) as u64), false));
}
let mut key = self.arg_bytes(block, "RIDFLD")?.unwrap_or_default();
match (self.arg_int(block, "KEYLENGTH")?, keying) {
(Some(n), _) => key.truncate(n.max(0) as usize),
(None, Keying::Indexed { prime, .. }) => key.truncate(prime.len),
_ => {}
}
Ok((key, has(block, "GENERIC")))
}
fn set_ridfld(&mut self, block: &ExecBlock, key: &[u8], relative: bool) -> R<()> {
if relative {
self.store_int(block, "RIDFLD", files::number_of(key) as i64)
} else {
self.store_bytes(block, "RIDFLD", key)
}
}
fn release_hold(&mut self, file: &str) -> Option<Vec<u8>> {
self.unit.cics.as_mut().and_then(|t| t.held.remove(file))
}
fn reqid(&mut self, block: &ExecBlock) -> R<i64> {
Ok(self.arg_int(block, "REQID")?.unwrap_or(0))
}
fn file_read(&mut self, block: &ExecBlock, file: &str) -> R<Flow> {
self.eib_bytes(EIBFN, &[0x06, 0x02]);
let gteq = has(block, "GTEQ");
let found = self.on_file(file, |m, keyed| {
let (key, generic) = m.ridfld(block, &keyed.keying)?;
Ok((find(keyed, &key, generic, gteq), keyed.keying == Keying::Relative))
})?;
let (key, record, relative) = match found {
Err(c) => return self.raise(block, c, 0),
Ok((None, _)) => return self.raise(block, "NOTFND", 0),
Ok((Some((key, record)), relative)) => (key, record, relative),
};
if has(block, "UPDATE")
&& let Some(task) = self.unit.cics.as_mut()
{
task.held.insert(file.to_owned(), key.clone());
}
if has(block, "GENERIC") || gteq {
self.set_ridfld(block, &key, relative)?;
}
self.deliver_record(block, &record)
}
fn file_write(&mut self, block: &ExecBlock, file: &str) -> R<Flow> {
self.eib_bytes(EIBFN, &[0x06, 0x04]);
let record = self.sent_bytes(block, "FROM", "LENGTH")?;
let outcome = self.on_file(file, |m, keyed| {
let (key, _) = m.ridfld(block, &keyed.keying)?;
Ok(match keyed.prime_key(&record) {
Some(own) if own != key => Err("INVREQ"),
_ => keyed.insert(key, record).map(|_| ()).map_err(condition_of),
})
})?;
match outcome {
Err(c) | Ok(Err(c)) => self.raise(block, c, 0),
Ok(Ok(())) => self.cics_ok(block),
}
}
fn file_rewrite(&mut self, block: &ExecBlock, file: &str) -> R<Flow> {
let Some(key) = self.release_hold(file) else { return self.raise(block, "INVREQ", 0) };
let record = self.sent_bytes(block, "FROM", "LENGTH")?;
let outcome = self.on_file(file, |_, keyed| {
Ok(match keyed.prime_key(&record) {
Some(own) if own != key => Err("INVREQ"),
_ => keyed.replace(key, record).map(|_| ()).map_err(condition_of),
})
})?;
match outcome {
Err(c) | Ok(Err(c)) => self.raise(block, c, 0),
Ok(Ok(())) => self.cics_ok(block),
}
}
fn file_delete(&mut self, block: &ExecBlock, file: &str) -> R<Flow> {
let held = if has(block, "RIDFLD") { None } else { Some(self.release_hold(file)) };
if let Some(None) = held {
return self.raise(block, "INVREQ", 0);
}
let outcome = self.on_file(file, |m, keyed| {
if let Some(Some(key)) = held {
return Ok(keyed.remove(&key).map_or(0, |_| 1));
}
let (key, generic) = m.ridfld(block, &keyed.keying)?;
if !generic {
return Ok(keyed.remove(&key).map_or(0, |_| 1));
}
let mut count = 0;
while let Some((k, _)) = keyed.seek(&key, true, false).filter(|(k, _)| k.starts_with(&key)) {
keyed.remove(&k);
count += 1;
}
Ok(count)
})?;
match outcome {
Err(c) => self.raise(block, c, 0),
Ok(0) => self.raise(block, "NOTFND", 0),
Ok(count) => {
self.store_int(block, "NUMREC", count)?;
self.cics_ok(block)
}
}
}
fn file_startbr(&mut self, block: &ExecBlock, file: &str, reset: bool) -> R<Flow> {
let reqid = self.reqid(block)?;
let exists = self.unit.cics.as_ref().is_some_and(|t| t.browses.contains_key(&(file.to_owned(), reqid)));
if reset != exists {
return self.raise(block, "INVREQ", 0);
}
let gteq = !has(block, "EQUAL");
let found = self.on_file(file, |m, keyed| {
let (key, generic) = m.ridfld(block, &keyed.keying)?;
if !key.is_empty() && key.iter().all(|&b| b == 0xFF) {
return Ok(Some(Browse { at: key, inclusive: false }));
}
Ok(find(keyed, &key, generic, gteq).map(|(k, _)| Browse { at: k, inclusive: true }))
})?;
match found {
Err(c) => self.raise(block, c, 0),
Ok(None) => self.raise(block, "NOTFND", 0),
Ok(Some(browse)) => {
if let Some(task) = self.unit.cics.as_mut() {
task.browses.insert((file.to_owned(), reqid), browse);
}
self.cics_ok(block)
}
}
}
fn file_browse(&mut self, block: &ExecBlock, file: &str, backward: bool) -> R<Flow> {
let reqid = self.reqid(block)?;
let Some(browse) = self.unit.cics.as_ref().and_then(|t| t.browses.get(&(file.to_owned(), reqid))).cloned() else {
return self.raise(block, "INVREQ", 0);
};
let found = self.on_file(file, |m, keyed| {
let relative = keyed.keying == Keying::Relative;
let (rid, _) = m.ridfld(block, &keyed.keying)?;
let skip = !backward && !browse.inclusive && !rid.is_empty() && !browse.at.starts_with(&rid);
let (from, inclusive) = if skip { (rid, true) } else { (browse.at.clone(), browse.inclusive) };
Ok((keyed.seek(&from, inclusive, backward), relative))
})?;
let (key, record, relative) = match found {
Err(c) => return self.raise(block, c, 0),
Ok((None, _)) => return self.raise(block, "ENDFILE", 0),
Ok((Some((key, record)), relative)) => (key, record, relative),
};
if let Some(task) = self.unit.cics.as_mut() {
task.browses.insert((file.to_owned(), reqid), Browse { at: key.clone(), inclusive: false });
}
self.set_ridfld(block, &key, relative)?;
self.deliver_record(block, &record)
}
}