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::{bytes_cut, eib_bytes, eib_calen, eib_halfword, eib_text, int, ok, page, raise, text};
8use crate::abend::{Abend, AbendCode, Ending};
9use crate::lir::ParaId;
10use crate::unit::{LoadError, Loader, RunUnit};
11use crate::vocab::Pos;
12
13/// RETURN ends this program. At the task's top level TRANSID and COMMAREA name the next task and
14/// what it starts with; in a LINKed program they raise INVREQ.
15pub(super) fn cics_return<'w, P: Copy, O, S>(
16    x: &mut impl CicsHost<'w, P, O, S>,
17    at: &At<P, O, S>,
18    transid: Option<&Datum<P, O, S>>,
19    commarea: Option<&Datum<P, O, S>>,
20    length: Option<&Datum<P, O, S>>,
21) -> R<Flow> {
22    eib_bytes(x.unit(), EIBFN, &[0x0E, 0x08]);
23    let transid = text(x, transid, at.pos)?;
24    let commarea = bytes_cut(x, commarea, length, at.pos)?;
25    if !x.main() && (transid.is_some() || commarea.is_some()) {
26        return raise(x, at, Condition::INVREQ, 0);
27    }
28    if let Some(task) = x.unit().cics.as_mut() {
29        if let Some(t) = transid {
30            task.next_transid = Some(t.to_ascii_uppercase());
31        }
32        if commarea.is_some() {
33            task.returned_commarea = commarea;
34        }
35    }
36    Ok(Flow::End(Ending::Goback))
37}
38
39/// LINK runs a program and comes back; XCTL runs it in this program's place. A LINKed program gets
40/// the COMMAREA item itself; XCTL passes a copy, since this program's storage goes away. Each LINK
41/// or XCTL starts the program with fresh WORKING-STORAGE, as CICS gives it.
42pub(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> {
43    eib_bytes(x.unit(), EIBFN, if xctl { &[0x0E, 0x04] } else { &[0x0E, 0x02] });
44    let Some(name) = text(x, t.program.as_ref(), at.pos)?.map(|n| n.to_ascii_uppercase()) else {
45        return Err(Abend::ironwork(format!("EXEC CICS {} needs PROGRAM", at.name), at.pos));
46    };
47    let page = page(x);
48    eib_text(x.unit(), page, EIBRSRCE, 8, &name);
49    let index = match x.unit().load(&name) {
50        Ok(i) => i,
51        Err(LoadError::NotFound) => return raise(x, at, Condition::PGMIDERR, 0),
52        Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("EXEC CICS {} PROGRAM({name}): {m}", at.name), at.pos)),
53    };
54    let Some(program) = x.unit().programs[index].compiled.clone() else {
55        return Err(Abend::ironwork(format!("EXEC CICS {} PROGRAM({name}): the task's first program is already running", at.name), at.pos));
56    };
57    let mark = x.unit().mem.len();
58    let (area, item_len) = match &t.commarea {
59        Some(Datum::Place(p)) if !xctl => {
60            let loc = x.locate(*p, false)?;
61            (Some(loc.offset), loc.len)
62        }
63        Some(Datum::Place(_) | Datum::Value(_)) => {
64            let bytes = bytes_cut(x, t.commarea.as_ref(), t.length.as_ref(), at.pos)?.unwrap_or_default();
65            (Some(x.unit().push_temporary(&bytes)), bytes.len())
66        }
67        _ => (None, 0),
68    };
69    let length = int(x, t.length.as_ref(), at.pos)?.map_or(item_len, |n| n.max(0) as usize);
70    let saved = eib_calen(x.unit());
71    eib_halfword(x.unit(), EIBCALEN, length as i16);
72    if xctl {
73        x.handlers().abend = None;
74    }
75    let ending = enter(x, program, index, area, xctl, at.pos);
76    let unit = x.unit();
77    unit.release_temporaries(mark);
78    eib_halfword(unit, EIBCALEN, saved);
79    match ending? {
80        Ending::StopRun => Ok(Flow::End(Ending::StopRun)),
81        _ if xctl => Ok(Flow::End(Ending::Goback)),
82        _ => ok(x, at),
83    }
84}
85
86/// Runs program `index` with fresh WORKING-STORAGE at the next logical level, or for XCTL in this
87/// program's place.
88fn enter<'w, P: Copy, O, S, X: CicsHost<'w, P, O, S>>(x: &mut X, program: X::Program, index: usize, area: Option<usize>, xctl: bool, pos: Pos) -> R<Ending> {
89    x.unit().programs[index].initialized = false;
90    x.unit().enter(pos)?;
91    let ending = x.run_program(program, index, area, xctl);
92    let unit = x.unit();
93    unit.depth -= 1;
94    unit.programs[index].active = false;
95    ending
96}
97
98/// ABEND ends the task with ABCODE; a HANDLE ABEND exit can intercept it unless CANCEL is given.
99pub(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> {
100    let code = text(x, abcode, at.pos)?.unwrap_or_else(|| "????".into());
101    if cancel && let Some(task) = x.unit().cics.as_mut() {
102        task.cancelling = true;
103    }
104    Err(Abend { message: format!("EXEC CICS ABEND ABCODE({code})"), code: AbendCode::Cics(code), pos: at.pos, file: None })
105}
106
107/// The transaction abend code an abend is in a CICS task, when a HANDLE ABEND exit can intercept
108/// it: a program check is ASRA; ASPx and APSJ, and ironwork's own refusals, cannot be.
109fn interceptable(abend: &Abend) -> Option<String> {
110    match &abend.code {
111        AbendCode::Check(_) | AbendCode::Protection => Some("ASRA".into()),
112        AbendCode::Cics(code) if !code.starts_with("ASP") && code != "APSJ" => Some(code.clone()),
113        _ => None,
114    }
115}
116
117/// Where an abend that reaches a logical level goes: to the level's HANDLE ABEND exit when it is
118/// active, which CICS deactivates on the way in (C142). None passes the abend to the next higher
119/// level.
120pub fn abend_exit<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, handlers: &mut Handlers, abend: &Abend) -> Option<ExitTarget> {
121    let code = interceptable(abend)?;
122    let task = unit.cics.as_mut().filter(|t| !t.cancelling)?;
123    let exit = handlers.abend.as_mut().filter(|e| e.active)?;
124    exit.active = false;
125    task.abcode = Some(code);
126    Some(exit.target.clone())
127}
128
129/// A HANDLE ABEND PROGRAM exit, entered as by LINK with the COMMAREA of the program that set it.
130/// One that cannot be loaded abends APCT, which passes to the next higher level.
131pub fn enter_exit_program<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, name: &str, pos: Pos) -> R<Ending> {
132    let apct = |message: String| Abend { code: AbendCode::Cics("APCT".into()), message, pos, file: None };
133    let index = match x.unit().load(name) {
134        Ok(i) => i,
135        Err(LoadError::NotFound) => return Err(apct(format!("HANDLE ABEND PROGRAM({name}): no program of the name"))),
136        Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("HANDLE ABEND PROGRAM({name}): {m}"), pos)),
137    };
138    let Some(program) = x.unit().programs[index].compiled.clone() else {
139        return Err(apct(format!("HANDLE ABEND PROGRAM({name}): the task's first program is already running")));
140    };
141    let page = page(x);
142    eib_text(x.unit(), page, EIBRSRCE, 8, name);
143    let commarea = x.commarea();
144    enter(x, program, index, commarea, false, pos)
145}
146
147pub(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> {
148    let conditions = &mut x.handlers().conditions;
149    for &(condition, label) in labels {
150        match label {
151            Some(p) => conditions.insert(condition, Handler::Label(p)),
152            None => conditions.remove(&condition),
153        };
154    }
155    ok(x, at)
156}
157
158pub(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> {
159    let conditions = &mut x.handlers().conditions;
160    for &condition in ignored {
161        conditions.insert(condition, Handler::Ignore);
162    }
163    ok(x, at)
164}
165
166/// PUSH HANDLE suspends HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND until POP HANDLE.
167pub(super) fn push_handle<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
168    let handlers = x.handlers();
169    let saved = (std::mem::take(&mut handlers.conditions), handlers.abend.take());
170    handlers.stack.push(saved);
171    ok(x, at)
172}
173
174pub(super) fn pop_handle<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
175    let handlers = x.handlers();
176    match handlers.stack.pop() {
177        Some((conditions, abend)) => {
178            handlers.conditions = conditions;
179            handlers.abend = abend;
180            ok(x, at)
181        }
182        None => raise(x, at, Condition::INVREQ, 0),
183    }
184}
185
186/// HANDLE ABEND PROGRAM or LABEL replaces the program level's exit, active; RESET reactivates it
187/// and CANCEL, the default, deactivates it (API Reference SC34-7402-00, pp. 314-315). PROGRAM
188/// names a program a LINK could find, else PGMIDERR.
189pub(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> {
190    let target = match (text(x, program, at.pos)?, label) {
191        (Some(name), _) => {
192            let name = name.to_ascii_uppercase();
193            match x.unit().load(&name) {
194                Ok(_) => Some(ExitTarget::Program(name)),
195                Err(LoadError::NotFound) => return raise(x, at, Condition::PGMIDERR, 1),
196                Err(LoadError::Compile(m)) => return Err(Abend::ironwork(format!("EXEC CICS HANDLE ABEND PROGRAM({name}): {m}"), at.pos)),
197            }
198        }
199        (None, Some(p)) => Some(ExitTarget::Label(p)),
200        (None, None) => None,
201    };
202    let handlers = x.handlers();
203    match (target, &mut handlers.abend) {
204        (Some(target), exit) => *exit = Some(AbendExit { target, active: true }),
205        (None, Some(exit)) => exit.active = reset,
206        (None, None) => {}
207    }
208    ok(x, at)
209}