Skip to main content

ironwork_rt/cics/
program.rs

1//! Program control and the handler table: RETURN, LINK, XCTL, ABEND, HANDLE CONDITION, IGNORE
2//! CONDITION, PUSH and POP HANDLE, and HANDLE ABEND.
3
4use super::Condition;
5use super::command::{Datum, Transfer};
6use super::run::{AbendExit, At, CicsHost, EIBCALEN, EIBFN, EIBRSRCE, ExitTarget, Flow, Handler, Handlers, R};
7use super::run::{eib_bytes, eib_calen, eib_halfword, eib_text, int, kept_calen, normal, ok, page, raise, restore_calen, task, text};
8use crate::abend::{Abend, AbendCode, Ending};
9use crate::callee::{self, By, Callee};
10use crate::lir::ParaId;
11use crate::storage::Loc;
12use crate::store;
13use crate::unit::{LoadError, Loader, RunUnit};
14use crate::vocab::Pos;
15
16/// The longest COMMAREA LINK, XCTL and RETURN pass.
17const COMMAREA_LIMIT: i64 = 32_763;
18
19/// A COMMAREA option as the translator passes it, by reference, and its length: LENGTH, else the
20/// item's.
21struct Commarea {
22    area: Area,
23    length: i64,
24}
25
26enum Area {
27    Item(Loc),
28    /// An operand that is not a data item, passed BY CONTENT.
29    Bytes(Vec<u8>),
30    /// A LINKAGE item with no address, which CICS is passed as address zero.
31    Zero,
32}
33
34impl Commarea {
35    fn of<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, area: Option<&Datum<P, O, S>>, length: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<Self>> {
36        let (area, len) = match area {
37            Some(&Datum::Place(p)) => match x.unaddressed(p) {
38                Some(len) => (Area::Zero, len),
39                None => {
40                    let loc = x.locate(p, false)?;
41                    (Area::Item(loc), loc.len)
42                }
43            },
44            Some(Datum::Value(o)) => {
45                let bytes = x.content(o, pos)?;
46                let len = bytes.len();
47                (Area::Bytes(bytes), len)
48            }
49            _ => return Ok(None),
50        };
51        let length = int(x, length, pos)?.unwrap_or(len as i64);
52        Ok(Some(Commarea { area, length }))
53    }
54
55    /// The LENGERR RESP2 the COMMAREA raises: 11 for a length outside 0 to 32763, 26 for one that
56    /// is not zero at address zero (C103).
57    fn lengerr(&self) -> Option<i32> {
58        if !(0..=COMMAREA_LIMIT).contains(&self.length) {
59            Some(11)
60        } else if matches!(self.area, Area::Zero) && self.length != 0 {
61            Some(26)
62        } else {
63            None
64        }
65    }
66
67    /// A copy of the COMMAREA's bytes, cut to its length when that is shorter.
68    fn bytes<'w, P: Copy, O, S>(self, x: &mut impl CicsHost<'w, P, O, S>) -> Vec<u8> {
69        let mut bytes = match self.area {
70            Area::Item(loc) => store::bytes(x.mem(), loc).to_vec(),
71            Area::Bytes(bytes) => bytes,
72            Area::Zero => Vec::new(),
73        };
74        bytes.truncate(self.length.max(0) as usize);
75        bytes
76    }
77}
78
79/// RETURN ends this program's logical level, CALLed programs and all: the program that LINKed to
80/// it goes on, or the task ends (C233). TRANSID names the next task from any level. COMMAREA, what
81/// the next task starts with, and CHANNEL and IMMEDIATE (`to_cics`) belong to the RETURN to CICS
82/// and raise INVREQ with RESP2 2 below the task's first level (C143). A COMMAREA that raises
83/// LENGERR clears the next TRANSID (C128).
84pub(super) fn cics_return<'w, P: Copy, O, S>(
85    x: &mut impl CicsHost<'w, P, O, S>,
86    at: &At<P, O, S>,
87    transid: Option<&Datum<P, O, S>>,
88    commarea: Option<&Datum<P, O, S>>,
89    length: Option<&Datum<P, O, S>>,
90    to_cics: bool,
91) -> R<Flow> {
92    eib_bytes(x.unit(), EIBFN, &[0x0E, 0x08]);
93    let transid = text(x, transid, at.pos)?;
94    let commarea = Commarea::of(x, commarea, length, at.pos)?;
95    if task(x).links > 0 && (commarea.is_some() || to_cics) {
96        return raise(x, at, Condition::INVREQ, 2);
97    }
98    if let Some(resp2) = commarea.as_ref().and_then(Commarea::lengerr) {
99        task(x).next_transid = None;
100        return raise(x, at, Condition::LENGERR, resp2);
101    }
102    let commarea = commarea.map(|c| c.bytes(x));
103    let task = task(x);
104    if let Some(t) = transid {
105        task.next_transid = Some(t.to_ascii_uppercase());
106    }
107    if commarea.is_some() {
108        task.returned_commarea = commarea;
109    }
110    task.ending_level = true;
111    Ok(Flow::End(Ending::Goback))
112}
113
114/// RETURN or XCTL has ended the logical level: the program whose CALL has just come back ends too
115/// (C233).
116pub fn level_ended<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>) -> bool {
117    unit.cics.as_ref().is_some_and(|t| t.ending_level)
118}
119
120/// LINK runs a program and comes back, from STOP RUN too, which ends a level as RETURN does (C144);
121/// XCTL runs it in place of the program running this logical level, with the level's HANDLE ABEND
122/// exit (C239), and the level ends when it does (C233). A LINKed program gets the COMMAREA item
123/// itself; XCTL passes a copy, since this program's storage goes away. A COMMAREA that raises
124/// LENGERR does so before a missing program raises PGMIDERR (C103). Each LINK or XCTL starts a
125/// run unit of its own, where the program and those it CALLs start with fresh WORKING-STORAGE,
126/// and a LINK that comes back gives its RETURN-CODE as RESP2 (C105).
127pub(super) fn link<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, t: &Transfer<P, O, S>, xctl: bool) -> R<Flow> {
128    eib_bytes(x.unit(), EIBFN, if xctl { &[0x0E, 0x04] } else { &[0x0E, 0x02] });
129    let Some(name) = text(x, t.program.as_ref(), at.pos)?.map(|n| n.to_ascii_uppercase()) else {
130        return Err(Abend::ironwork(format!("EXEC CICS {} needs PROGRAM", at.name), at.pos));
131    };
132    let page = page(x);
133    eib_text(x.unit(), page, EIBRSRCE, 8, &name);
134    let commarea = Commarea::of(x, t.commarea.as_ref(), t.length.as_ref(), at.pos)?;
135    if let Some(resp2) = commarea.as_ref().and_then(Commarea::lengerr) {
136        return raise(x, at, Condition::LENGERR, resp2);
137    }
138    let index = match x.unit().load(&name) {
139        Ok(i) => i,
140        Err(LoadError::NotFound) => return raise(x, at, Condition::PGMIDERR, 1),
141        Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("EXEC CICS {} PROGRAM({name}): {m}", at.name), at.pos)),
142    };
143    let program = x.unit().programs[index].compiled.clone();
144    let mark = x.unit().mem.len();
145    let (area, length) = match commarea {
146        Some(Commarea { area: Area::Item(loc), length }) if !xctl => (Some(loc.offset), length as usize),
147        Some(c @ Commarea { area: Area::Item(_) | Area::Bytes(_), .. }) => {
148            let length = c.length as usize;
149            let bytes = c.bytes(x);
150            (Some(x.unit().push_temporary(&bytes)), length)
151        }
152        _ => (None, 0),
153    };
154    let saved = kept_calen(x.unit());
155    eib_halfword(x.unit(), EIBCALEN, length as i16);
156    let ending = enter(x, program, index, area, xctl, at.pos);
157    let unit = x.unit();
158    unit.release_temporaries(mark);
159    restore_calen(unit, saved);
160    match ending? {
161        (Ending::StopRun, _) if xctl => Ok(Flow::End(Ending::StopRun)),
162        _ if xctl => {
163            task(x).ending_level = true;
164            Ok(Flow::End(Ending::Goback))
165        }
166        (_, returned) => normal(x, at, i32::from(returned)),
167    }
168}
169
170/// Runs program `index` in a run unit of its own (C145) at the next logical level, or for XCTL in
171/// this program's place; the level it ran has ended when it comes back, with the RETURN-CODE it
172/// left.
173fn enter<'w, P: Copy, O, S, X: CicsHost<'w, P, O, S>>(x: &mut X, program: Option<X::Program>, index: usize, area: Option<usize>, xctl: bool, pos: Pos) -> R<(Ending, i16)> {
174    let below = u32::from(!xctl);
175    task(x).links += below;
176    x.unit().begin_cics_run_unit();
177    let callee = Callee { index, by: By::Link, mark: None, pos };
178    let ran = callee::run(x, &callee, |x| {
179        x.unit().enter(pos)?;
180        let ending = x.run_program(program, index, area, xctl);
181        x.unit().depth -= 1;
182        Ok::<_, Abend>((ending, ()))
183    });
184    let returned = x.unit().return_code();
185    let closed = x.unit().end_cics_run_unit(xctl);
186    let task = task(x);
187    task.links -= below;
188    task.ending_level = false;
189    match (ran.and_then(|(ending, ())| ending), closed) {
190        (Ok(_), Err(m)) => Err(Abend::ironwork(m, pos)),
191        (ending, _) => ending.map(|e| (e, returned)),
192    }
193}
194
195/// ABEND ends the task with ABCODE; a HANDLE ABEND exit can intercept it unless CANCEL is given.
196pub(super) fn abend<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, abcode: Option<&Datum<P, O, S>>, cancel: bool) -> R<Flow> {
197    let code = text(x, abcode, at.pos)?.unwrap_or_else(|| "????".into());
198    if cancel && let Some(task) = x.unit().cics.as_mut() {
199        task.cancelling = true;
200    }
201    Err(Abend { message: format!("EXEC CICS ABEND ABCODE({code})"), code: AbendCode::Cics(code), pos: at.pos, file: None })
202}
203
204/// The transaction abend code an abend is in a CICS task, when a HANDLE ABEND exit can intercept
205/// it: a program check is ASRA; ASPx and APSJ, and ironwork's own refusals, cannot be.
206fn interceptable(abend: &Abend) -> Option<String> {
207    match &abend.code {
208        AbendCode::Check(_) | AbendCode::Protection => Some("ASRA".into()),
209        AbendCode::Cics(code) if !code.starts_with("ASP") && code != "APSJ" => Some(code.clone()),
210        _ => None,
211    }
212}
213
214/// Where an abend that reaches activation `me` goes: to the level's HANDLE ABEND exit when it is
215/// active, which CICS deactivates on the way in (C142). A LABEL `me` set is a GO TO in it, and one
216/// another activation set abends APC2 (C238). A PROGRAM is entered only where `me` runs the level,
217/// as the task, LINK or XCTL started it; in a CALLed program the abend goes on with the exit still
218/// active. None passes the abend on.
219pub fn abend_exit<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, handlers: &mut Handlers, abend: &Abend, me: u64, runs_level: bool) -> R<Option<ExitTarget>> {
220    let Some(code) = interceptable(abend) else { return Ok(None) };
221    let Some(task) = unit.cics.as_mut().filter(|t| !t.cancelling) else { return Ok(None) };
222    let Some(exit) = handlers.abend.as_mut().filter(|e| e.active && (runs_level || matches!(e.target, ExitTarget::Label { .. }))) else {
223        return Ok(None);
224    };
225    exit.active = false;
226    task.abcode = Some(code);
227    task.ending_level = false;
228    match &exit.target {
229        ExitTarget::Label { owner, .. } if *owner != me => Err(Abend {
230            message: format!("{}; the HANDLE ABEND LABEL is in a program that is not running there, which CICS cannot branch to", abend.message),
231            code: AbendCode::Cics("APC2".into()),
232            pos: abend.pos,
233            file: abend.file.clone(),
234        }),
235        target => Ok(Some(target.clone())),
236    }
237}
238
239/// A HANDLE ABEND PROGRAM exit, entered as by LINK with the COMMAREA and EIBCALEN of the program
240/// that set it, else of the program running the level. One that cannot be loaded abends APCT,
241/// which passes to the next higher level.
242pub fn enter_exit_program<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, name: &str, commarea: Option<(usize, i16)>, pos: Pos) -> R<Ending> {
243    let apct = |message: String| Abend { code: AbendCode::Cics("APCT".into()), message, pos, file: None };
244    let index = match x.unit().load(name) {
245        Ok(i) => i,
246        Err(LoadError::NotFound) => return Err(apct(format!("HANDLE ABEND PROGRAM({name}): no program of the name"))),
247        Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("HANDLE ABEND PROGRAM({name}): {m}"), pos)),
248    };
249    let program = x.unit().programs[index].compiled.clone();
250    let page = page(x);
251    eib_text(x.unit(), page, EIBRSRCE, 8, name);
252    let saved = kept_calen(x.unit());
253    let (area, length) = match commarea {
254        Some((area, length)) => (Some(area), length),
255        None => (x.commarea(), saved.0),
256    };
257    eib_halfword(x.unit(), EIBCALEN, length);
258    let ending = enter(x, program, index, area, false, pos);
259    restore_calen(x.unit(), saved);
260    ending.map(|(e, _)| e)
261}
262
263pub(super) fn handle_condition<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, labels: &[(Condition, Option<ParaId>)]) -> R<Flow> {
264    let owner = x.activation();
265    let conditions = &mut x.handlers().conditions;
266    for &(condition, label) in labels {
267        match label {
268            Some(paragraph) => conditions.insert(condition, Handler::Label { paragraph, owner }),
269            None => conditions.remove(&condition),
270        };
271    }
272    ok(x, at)
273}
274
275pub(super) fn ignore_condition<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, ignored: &[Condition]) -> R<Flow> {
276    let conditions = &mut x.handlers().conditions;
277    for &condition in ignored {
278        conditions.insert(condition, Handler::Ignore);
279    }
280    ok(x, at)
281}
282
283/// PUSH HANDLE suspends HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND until POP HANDLE.
284pub(super) fn push_handle<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
285    x.handlers().push();
286    ok(x, at)
287}
288
289pub(super) fn pop_handle<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
290    if x.handlers().pop() { ok(x, at) } else { raise(x, at, Condition::INVREQ, 0) }
291}
292
293/// HANDLE ABEND PROGRAM or LABEL replaces the program level's exit, active; RESET reactivates it
294/// and CANCEL, the default, deactivates it (API Reference SC34-7402-00, pp. 314-315). PROGRAM
295/// names a program a LINK could find, else PGMIDERR, and keeps this program's COMMAREA for it.
296pub(super) fn handle_abend<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, program: Option<&Datum<P, O, S>>, label: Option<ParaId>, reset: bool) -> R<Flow> {
297    let target = match (text(x, program, at.pos)?, label) {
298        (Some(name), _) => {
299            let name = name.to_ascii_uppercase();
300            match x.unit().load(&name) {
301                Ok(_) => Some(ExitTarget::Program { name, commarea: x.commarea().map(|area| (area, eib_calen(x.unit()))) }),
302                Err(LoadError::NotFound) => return raise(x, at, Condition::PGMIDERR, 1),
303                Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("EXEC CICS HANDLE ABEND PROGRAM({name}): {m}"), at.pos)),
304            }
305        }
306        (None, Some(p)) => Some(ExitTarget::Label { paragraph: p, owner: x.activation(), at: at.pos }),
307        (None, None) => None,
308    };
309    let handlers = x.handlers();
310    match (target, &mut handlers.abend) {
311        (Some(target), exit) => *exit = Some(AbendExit { target, active: true }),
312        (None, Some(exit)) => exit.active = reset,
313        (None, None) => {}
314    }
315    ok(x, at)
316}