Skip to main content

ironwork_rt/cics/
run.rs

1//! Running a command against the task: what it asks of the executor (`CicsHost`), reading and
2//! storing its options, the EXEC interface block, raising conditions, and dispatch to the services.
3
4use super::command::{Cics, CicsCommand, Datum, Record, Resp};
5use super::{Condition, Task, file_control, maps, program, services};
6use crate::abend::{Abend, AbendCode, Ending};
7use crate::bms::Mapset;
8use crate::host::{self, Host};
9use crate::lir::{ParaId, Step};
10use crate::storage::{Loc, Val};
11use crate::store::{self, ProgramFacts};
12use crate::unit::{ADDRESS_BASE, Loader, RunUnit};
13use crate::vocab::Pos;
14use std::collections::HashMap;
15use zarch::decimal::{self, Decimal};
16use zarch::ebcdic::{self, CodePage};
17
18pub(super) type R<T> = Result<T, Abend>;
19
20pub const EIBTIME: usize = 0x00;
21pub const EIBDATE: usize = 0x04;
22pub const EIBTRNID: usize = 0x08;
23pub const EIBTASKN: usize = 0x0C;
24pub const EIBTRMID: usize = 0x10;
25pub const EIBCPOSN: usize = 0x16;
26pub const EIBCALEN: usize = 0x18;
27pub const EIBAID: usize = 0x1A;
28pub const EIBFN: usize = 0x1B;
29pub const EIBRSRCE: usize = 0x33;
30pub const EIBRESP: usize = 0x4C;
31pub const EIBRESP2: usize = 0x50;
32
33/// What a command asks of the executor running it beyond `Host`: the run unit, the program level's
34/// handlers, operands that are not data items, names only the executor can resolve, and running a
35/// program for LINK and XCTL.
36pub trait CicsHost<'w, P: Copy, O, S>: Host<P> {
37    type Program: Clone;
38    type Loader: Loader<Self::Program>;
39    fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
40    fn handlers(&mut self) -> &mut Handlers;
41    /// An operand's bytes as CALL BY CONTENT passes them.
42    fn content(&mut self, operand: &O, pos: Pos) -> R<Vec<u8>>;
43    /// An operand's value as a subscript takes it.
44    fn integer_of(&mut self, operand: &O, pos: Pos) -> R<i64>;
45    fn text(&self, text: &S) -> String;
46    /// The run unit's first program, from which RETURN TRANSID and COMMAREA end the task.
47    fn main(&self) -> bool;
48    fn program_id(&self) -> String;
49    /// Where DFHCOMMAREA is, when the program has one with an address.
50    fn commarea(&self) -> Option<usize>;
51    /// A mapset from the copy libraries; None when no library holds it.
52    fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>>;
53    /// The data item a name alone refers to, when it names one.
54    fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>>;
55    /// The data item a name alone refers to, or the abend a reference to it gives.
56    fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc>;
57    /// Runs program `index` from its start with DFHEIBLK and `commarea` as its USING items; for
58    /// XCTL, in this program's place.
59    fn run_program(&mut self, program: Self::Program, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending>;
60}
61
62/// HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND, which belong to the program level.
63#[derive(Clone, Debug, Default)]
64pub struct Handlers {
65    pub conditions: HashMap<Condition, Handler>,
66    /// What each PUSH HANDLE suspended.
67    pub stack: Vec<(HashMap<Condition, Handler>, Option<AbendExit>)>,
68    pub abend: Option<AbendExit>,
69}
70
71/// The program level's HANDLE ABEND exit, which CANCEL and entering it deactivate and RESET
72/// reactivates.
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct AbendExit {
75    pub target: ExitTarget,
76    pub active: bool,
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub enum ExitTarget {
81    /// A paragraph of the program that set the exit.
82    Label(ParaId),
83    /// A program, entered as by LINK.
84    Program(String),
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Handler {
89    Label(ParaId),
90    Ignore,
91}
92
93/// Where control goes after a command.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum Flow {
96    Next,
97    /// A handled condition.
98    GoTo(ParaId),
99    /// RETURN, XCTL, or a LINKed program's STOP RUN.
100    End(Ending),
101}
102
103impl From<Flow> for Step {
104    fn from(flow: Flow) -> Self {
105        match flow {
106            Flow::Next => Step::Next,
107            Flow::GoTo(p) => Step::GoTo(p),
108            Flow::End(e) => Step::End(e),
109        }
110    }
111}
112
113/// A command's name as written, which messages give, its RESP, RESP2 and NOHANDLE, and where it is.
114pub struct At<'c, P, O, S> {
115    pub name: &'c str,
116    pub resp: &'c Resp<P, O, S>,
117    pub pos: Pos,
118}
119
120/// EXEC CICS is refused outside a CICS task.
121pub fn in_task<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>, name: &str, pos: Pos) -> R<()> {
122    match unit.cics {
123        Some(_) => Ok(()),
124        None => Err(Abend::ironwork(format!("EXEC CICS {name} was reached outside a CICS task: run the program with `ironwork cics`"), pos)),
125    }
126}
127
128/// Every command starts with EIBRESP and EIBRESP2 zero.
129pub fn begin_command<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>) {
130    eib_fullword(unit, EIBRESP, 0);
131    eib_fullword(unit, EIBRESP2, 0);
132}
133
134/// Runs a command.
135pub fn run<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, command: &CicsCommand<P, O, S>, pos: Pos) -> R<Flow> {
136    let name = x.text(&command.name);
137    in_task(x.unit(), &name, pos)?;
138    begin_command(x.unit());
139    let at = At { name: &name, resp: &command.resp, pos };
140    match &command.command {
141        Cics::File { verb, file, options } => file_control::run(x, &at, *verb, file.as_ref(), options),
142        Cics::Return { transid, commarea, length } => program::cics_return(x, &at, transid.as_ref(), commarea.as_ref(), length.as_ref()),
143        Cics::Link(t) => program::link(x, &at, t, false),
144        Cics::Xctl(t) => program::link(x, &at, t, true),
145        Cics::Abend { abcode, cancel } => program::abend(x, &at, abcode.as_ref(), *cancel),
146        Cics::HandleCondition(labels) => program::handle_condition(x, &at, labels),
147        Cics::IgnoreCondition(conditions) => program::ignore_condition(x, &at, conditions),
148        Cics::PushHandle => program::push_handle(x, &at),
149        Cics::PopHandle => program::pop_handle(x, &at),
150        Cics::HandleAbend { program, label, reset } => program::handle_abend(x, &at, program.as_ref(), *label, *reset),
151        Cics::HandleAid | Cics::Freemain | Cics::Enq | Cics::Deq | Cics::Delay => ok(x, &at),
152        Cics::SendMap { map, mapset, from, maponly, dataonly, cursor, control } => {
153            maps::send_map(x, &at, maps::MapNames { map: map.as_ref(), mapset: mapset.as_ref() }, from.as_ref(), (*maponly, *dataonly), cursor.as_ref(), *control)
154        }
155        Cics::ReceiveMap { map, mapset, into, set } => maps::receive_map(x, &at, maps::MapNames { map: map.as_ref(), mapset: mapset.as_ref() }, into.as_ref(), set.as_ref()),
156        Cics::SendControl { cursor, control } => maps::send_control(x, &at, cursor.as_ref(), *control),
157        Cics::Receive(record) => maps::receive_raw(x, &at, record),
158        Cics::Asktime { abstime } => services::asktime(x, &at, abstime.as_ref()),
159        Cics::Formattime { abstime, datesep, timesep, outputs } => services::formattime(x, &at, abstime.as_ref(), datesep.as_ref(), timesep.as_ref(), outputs),
160        Cics::Assign(assign) => services::assign(x, &at, assign),
161        Cics::Getmain { flength, length, initimg, set } => services::getmain(x, &at, flength.as_ref(), length.as_ref(), initimg.as_ref(), set.as_ref()),
162        Cics::Syncpoint { rollback } => services::syncpoint(x, &at, *rollback),
163        Cics::Address { eib, commarea, cwa, twa } => services::address(x, &at, eib.as_ref(), commarea.as_ref(), cwa.as_ref(), twa.as_ref()),
164        Cics::SendText { from, length } => services::send_text(x, &at, from.as_ref(), length.as_ref()),
165        Cics::WriteOperator { text, textlength } => services::write_operator(x, &at, text.as_ref(), textlength.as_ref()),
166        Cics::WriteqTs { queue, from, length, rewrite, item, numitems } => {
167            services::writeq_ts(x, &at, queue.as_ref(), (from.as_ref(), length.as_ref()), *rewrite, item.as_ref(), numitems.as_ref())
168        }
169        Cics::ReadqTs { queue, next, item, numitems, record } => services::readq_ts(x, &at, queue.as_ref(), *next, item.as_ref(), numitems.as_ref(), record),
170        Cics::DeleteqTs { queue } => services::deleteq_ts(x, &at, queue.as_ref()),
171        Cics::WriteqTd { queue, from, length } => services::writeq_td(x, &at, queue.as_ref(), from.as_ref(), length.as_ref()),
172        Cics::ReadqTd { queue, record } => services::readq_td(x, &at, queue.as_ref(), record),
173        Cics::DeleteqTd { queue } => services::deleteq_td(x, &at, queue.as_ref()),
174        Cics::Unsupported => Err(Abend::ironwork(unsupported(&name), pos)),
175    }
176}
177
178/// The abend message for a command ironwork does not carry out.
179pub fn unsupported(name: &str) -> String {
180    format!("EXEC CICS {name} is not supported yet")
181}
182
183/// The task; `run` has already refused to run a command outside one.
184pub(super) fn task<'a, 'w: 'a, P: Copy + 'a, O: 'a, S: 'a>(x: &'a mut impl CicsHost<'w, P, O, S>) -> &'a mut Task {
185    x.unit().cics.as_mut().expect("EXEC CICS runs only in a task")
186}
187
188pub(super) fn page<'w, P: Copy, O, S>(x: &impl CicsHost<'w, P, O, S>) -> &'static CodePage {
189    x.facts().page()
190}
191
192/// Text in the program's code page, a character it cannot encode as a space.
193pub(super) fn encoded(page: &CodePage, text: &str) -> Vec<u8> {
194    text.chars().map(|c| page.encode_char(c).unwrap_or(ebcdic::SPACE)).collect()
195}
196
197/// An option's bytes: a data item's storage, or another operand's as CALL BY CONTENT passes it.
198pub fn bytes<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<Vec<u8>>> {
199    Ok(match d {
200        Some(Datum::Place(p)) => {
201            let loc = x.locate(*p, false)?;
202            Some(store::bytes(x.mem(), loc).to_vec())
203        }
204        Some(Datum::Value(o)) => Some(x.content(o, pos)?),
205        _ => None,
206    })
207}
208
209/// An option's bytes, cut to a length option when that is shorter.
210pub(super) fn bytes_cut<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, data: Option<&Datum<P, O, S>>, length: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<Vec<u8>>> {
211    let Some(mut bytes) = bytes(x, data, pos)? else { return Ok(None) };
212    if let Some(n) = int(x, length, pos)? {
213        bytes.truncate(n.max(0) as usize);
214    }
215    Ok(Some(bytes))
216}
217
218/// FROM's bytes cut to LENGTH (or TEXT and TEXTLENGTH for WRITE OPERATOR); `from` names it.
219pub(super) fn sent<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, data: Option<&Datum<P, O, S>>, length: Option<&Datum<P, O, S>>, from: &str) -> R<Vec<u8>> {
220    bytes_cut(x, data, length, at.pos)?.ok_or_else(|| Abend::ironwork(format!("EXEC CICS {} needs {from}", at.name), at.pos))
221}
222
223pub(super) fn int<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<i64>> {
224    match d {
225        Some(Datum::Place(p)) => x.integer(*p, pos).map(Some),
226        Some(Datum::Value(o)) => x.integer_of(o, pos).map(Some),
227        _ => Ok(None),
228    }
229}
230
231/// A resource name (PROGRAM, TRANSID, QUEUE, FILE, ABCODE) with trailing spaces removed; text as
232/// written loses its quotes.
233pub(super) fn text<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<String>> {
234    if let Some(Datum::Text(t)) = d {
235        return Ok(Some(x.text(t).trim().trim_matches(|c| c == '\'' || c == '"').to_owned()));
236    }
237    let page = page(x);
238    Ok(bytes(x, d, pos)?.map(|b| page.decode(&b).trim_end().to_owned()))
239}
240
241/// MOVEs bytes into the data item an option names, as INTO and the like receive them; `name` is
242/// the option's.
243pub(super) fn store_bytes<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, d: Option<&Datum<P, O, S>>, name: &str, bytes: &[u8]) -> R<()> {
244    match d {
245        Some(Datum::Place(p)) => {
246            let loc = x.locate(*p, false)?;
247            x.assign(loc, Val::Bytes(bytes.to_vec()), None, at.pos)
248        }
249        Some(Datum::Value(_)) => Err(Abend::ironwork(format!("EXEC CICS {}: {name} must name a data item", at.name), at.pos)),
250        _ => Ok(()),
251    }
252}
253
254pub(super) fn store_int<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, value: i64, pos: Pos) -> R<()> {
255    match d {
256        Some(Datum::Place(p)) => host::set_integer(x, *p, value, pos),
257        _ => Ok(()),
258    }
259}
260
261/// Stores an address, or NULL for None, into the POINTER an option names.
262pub(super) fn store_pointer<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, offset: Option<usize>, pos: Pos) -> R<()> {
263    if let Some(Datum::Place(p)) = d {
264        let loc = x.locate(*p, false)?;
265        let address = offset.map_or(0, |o| ADDRESS_BASE + o as u32);
266        x.assign(loc, Val::Address(address), None, pos)?;
267    }
268    Ok(())
269}
270
271pub(super) fn eib_bytes<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, bytes: &[u8]) {
272    let at = unit.eib + offset;
273    unit.mem[at..at + bytes.len()].copy_from_slice(bytes);
274}
275
276pub(super) fn eib_halfword<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i16) {
277    eib_bytes(unit, offset, &value.to_be_bytes());
278}
279
280pub(super) fn eib_fullword<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i32) {
281    eib_bytes(unit, offset, &value.to_be_bytes());
282}
283
284/// A 4-byte packed EIB field: seven digits and a sign.
285pub(super) fn eib_packed<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i64) {
286    let mut field = [0u8; 4];
287    let _ = decimal::encode(&mut field, Decimal { negative: value < 0, magnitude: value.unsigned_abs() as u128 });
288    eib_bytes(unit, offset, &field);
289}
290
291/// An EIB text field, in EBCDIC, padded with spaces or cut to `len`.
292pub(super) fn eib_text<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, page: &CodePage, offset: usize, len: usize, text: &str) {
293    let mut bytes = encoded(page, text);
294    bytes.resize(len, ebcdic::SPACE);
295    eib_bytes(unit, offset, &bytes);
296}
297
298pub(super) fn eib_calen<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>) -> i16 {
299    let at = unit.eib + EIBCALEN;
300    i16::from_be_bytes([unit.mem[at], unit.mem[at + 1]])
301}
302
303/// Fills the EXEC interface block for the task's first program: time, date, transaction, task
304/// number, terminal, COMMAREA length and the AID that started it.
305pub fn begin_task<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, page: &CodePage, length: usize) {
306    let (seconds, hundredths) = unit.now();
307    let abstime = super::abstime(seconds, hundredths);
308    let (transid, termid, number) = unit.cics.as_ref().map(|t| (t.transid.clone(), t.termid.clone(), t.number)).unwrap_or_default();
309    eib_packed(unit, EIBTIME, super::eib_time(abstime));
310    eib_packed(unit, EIBDATE, super::eib_date(abstime));
311    eib_text(unit, page, EIBTRNID, 4, &transid);
312    eib_packed(unit, EIBTASKN, i64::from(number));
313    eib_text(unit, page, EIBTRMID, 4, &termid);
314    eib_halfword(unit, EIBCALEN, length as i16);
315    if let Some(aid) = unit.cics.as_ref().and_then(|t| t.initial_aid) {
316        eib_bytes(unit, EIBAID, &[aid]);
317    }
318}
319
320/// Gives a record to INTO or SET and its length to LENGTH. A record longer than LENGTH (or the
321/// INTO item) arrives cut short, with LENGERR.
322pub(super) fn deliver<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, record: &Record<P, O, S>, data: &[u8]) -> R<Flow> {
323    let limit = match int(x, record.length.as_ref(), at.pos)? {
324        Some(n) => n.max(0) as usize,
325        None => match &record.into {
326            Some(Datum::Place(p)) => x.locate(*p, false)?.len,
327            _ => data.len(),
328        },
329    };
330    if record.set.is_some() {
331        let area = x.unit().push_temporary(data);
332        store_pointer(x, record.set.as_ref(), Some(area), at.pos)?;
333    } else {
334        store_bytes(x, at, record.into.as_ref(), "INTO", &data[..data.len().min(limit)])?;
335    }
336    store_int(x, record.length.as_ref(), data.len() as i64, at.pos)?;
337    if data.len() > limit && record.set.is_none() {
338        return raise(x, at, Condition::LENGERR, 0);
339    }
340    ok(x, at)
341}
342
343/// A command that succeeded: RESP and RESP2, when asked for, are zero.
344pub fn ok<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
345    store_int(x, at.resp.resp.as_ref(), 0, at.pos)?;
346    store_int(x, at.resp.resp2.as_ref(), 0, at.pos)?;
347    Ok(Flow::Next)
348}
349
350/// Raises a condition. RESP or NOHANDLE take it; otherwise HANDLE CONDITION (the condition's own
351/// entry, else ERROR) or IGNORE CONDITION decides; otherwise the task abends with the condition's
352/// AEIx code, which a HANDLE ABEND exit can intercept.
353pub fn raise<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, condition: Condition, resp2: i32) -> R<Flow> {
354    let resp = condition.resp();
355    eib_fullword(x.unit(), EIBRESP, resp);
356    eib_fullword(x.unit(), EIBRESP2, resp2);
357    if at.resp.resp.is_some() {
358        store_int(x, at.resp.resp.as_ref(), i64::from(resp), at.pos)?;
359        store_int(x, at.resp.resp2.as_ref(), i64::from(resp2), at.pos)?;
360        return Ok(Flow::Next);
361    }
362    if at.resp.nohandle {
363        return Ok(Flow::Next);
364    }
365    let handlers = x.handlers();
366    match handlers.conditions.get(&condition).or_else(|| handlers.conditions.get(&Condition::ERROR)).copied() {
367        Some(Handler::Ignore) => Ok(Flow::Next),
368        Some(Handler::Label(p)) => Ok(Flow::GoTo(p)),
369        None => Err(Abend {
370            code: AbendCode::Cics(condition.default_abend().into()),
371            message: format!("EXEC CICS {}: {} was raised with no RESP, HANDLE CONDITION or IGNORE CONDITION", at.name, condition.name()),
372            pos: at.pos,
373            file: None,
374        }),
375    }
376}