Skip to main content

ironwork_exec/
lib.rs

1//! ironwork for COBOL: an interpreter that runs a program `ironwork-compile` has checked and laid
2//! out, in EBCDIC with the numeric model of `ironwork-numeric`.
3
4pub mod abend;
5pub use rt::calendar;
6pub use rt::cics;
7pub use rt::digest;
8pub use rt::evidence;
9pub use compile::collating;
10pub use compile::declaratives;
11#[cfg(test)]
12mod edit;
13pub use rt::files;
14pub use compile::layout;
15pub mod le;
16pub use rt::lir;
17pub mod loader;
18pub mod lower;
19pub mod machine;
20pub use rt::module;
21pub mod oo;
22pub use compile::picture;
23pub mod printer;
24pub mod report;
25use compile::sort;
26pub mod sql;
27pub mod terminal;
28#[cfg(test)]
29mod testing;
30pub use rt::tn3270;
31pub mod unit;
32pub mod vm;
33
34pub use compile::{Compiled, compile, compile_at, compile_time, entry_points, read_lengths, variable_records};
35pub(crate) use compile::{procedure, procedure_from, section_end};
36pub use machine::{Abend, Ending};
37
38use abend::AbendCode;
39use unit::AddProgram;
40use std::io::{BufRead, Write};
41use syntax::Pos;
42
43/// Running a compiled program, in a run unit of its own or as a CICS task.
44pub trait Execute {
45    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
46
47    /// Runs with the DDs that ASSIGN names map to.
48    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
49
50    /// Runs as the first program of a run unit: CALL finds other programs in `library`, ACCEPT
51    /// reads `sysin`. Returns how the run ended and RETURN-CODE.
52    fn execute<'w>(
53        &self,
54        library: unit::Library,
55        dds: files::Dds,
56        sysin: Option<Box<dyn BufRead + 'w>>,
57        clock: unit::Clock,
58        out: &'w mut dyn Write,
59        err: &'w mut dyn Write,
60    ) -> Result<(Ending, i16), Abend>;
61
62    /// Runs as [`Execute::execute`] does, with EXEC SQL answered by `database`.
63    #[allow(clippy::too_many_arguments)]
64    fn execute_with<'w>(
65        &self,
66        library: unit::Library,
67        dds: files::Dds,
68        sysin: Option<Box<dyn BufRead + 'w>>,
69        clock: unit::Clock,
70        database: Option<&'w mut (dyn sql::Database + '_)>,
71        out: &'w mut dyn Write,
72        err: &'w mut dyn Write,
73    ) -> Result<(Ending, i16), Abend>;
74
75    /// Runs as [`Execute::execute_with`] does, telling `observer` what the run opens, closes and
76    /// loads.
77    #[allow(clippy::too_many_arguments)]
78    fn execute_observed<'w>(
79        &self,
80        library: unit::Library,
81        dds: files::Dds,
82        sysin: Option<Box<dyn BufRead + 'w>>,
83        clock: unit::Clock,
84        database: Option<&'w mut (dyn sql::Database + '_)>,
85        out: &'w mut dyn Write,
86        err: &'w mut dyn Write,
87        observer: Option<unit::Observer<'w>>,
88    ) -> Result<(Ending, i16), Abend>;
89
90    /// Runs as [`Execute::execute_observed`] does, as the main program of a job step that EXEC
91    /// PGM= started with `parm`: the first PROCEDURE DIVISION USING item addresses a halfword
92    /// length and the program arguments Language Environment finds in it.
93    #[allow(clippy::too_many_arguments)]
94    fn execute_main<'w>(
95        &self,
96        library: unit::Library,
97        dds: files::Dds,
98        sysin: Option<Box<dyn BufRead + 'w>>,
99        clock: unit::Clock,
100        database: Option<&'w mut (dyn sql::Database + '_)>,
101        out: &'w mut dyn Write,
102        err: &'w mut dyn Write,
103        observer: Option<unit::Observer<'w>>,
104        parm: &str,
105    ) -> Result<(Ending, i16), Abend>;
106
107    /// Runs as [`Execute::execute_observed`] does, and puts what the run left in its run unit in
108    /// `kept`.
109    #[allow(clippy::too_many_arguments)]
110    fn execute_kept<'w>(
111        &self,
112        library: unit::Library,
113        dds: files::Dds,
114        sysin: Option<Box<dyn BufRead + 'w>>,
115        clock: unit::Clock,
116        database: Option<&'w mut (dyn sql::Database + '_)>,
117        out: &'w mut dyn Write,
118        err: &'w mut dyn Write,
119        observer: Option<unit::Observer<'w>>,
120        kept: &mut Option<unit::Remains>,
121    ) -> Result<(Ending, i16), Abend>;
122
123    /// Runs as the first program of a CICS task. `task` says who started it, what COMMAREA it
124    /// starts with, and what files and queues it has. Returns how the run ended and the task, with
125    /// RETURN TRANSID and COMMAREA if it ended that way.
126    fn execute_cics<'w>(
127        &self,
128        library: unit::Library,
129        dds: files::Dds,
130        task: cics::Task,
131        clock: unit::Clock,
132        out: &'w mut dyn Write,
133        err: &'w mut dyn Write,
134    ) -> Result<(Ending, cics::Task), Abend>;
135
136    /// A CICS task with a database: SYNCPOINT commits, and the end of the task commits, or rolls
137    /// back after an abend, and closes every cursor. The database outlives the task, so a region's
138    /// tasks can share one.
139    #[allow(clippy::too_many_arguments)]
140    fn execute_cics_with<'w>(
141        &self,
142        library: unit::Library,
143        dds: files::Dds,
144        task: cics::Task,
145        clock: unit::Clock,
146        database: Option<&'w mut (dyn sql::Database + '_)>,
147        out: &'w mut dyn Write,
148        err: &'w mut dyn Write,
149    ) -> Result<(Ending, cics::Task), Abend>;
150
151    /// Runs as [`Execute::execute_cics_with`] does, telling `observer` what the task opens, loads
152    /// and passes to an operation an input could steer.
153    #[allow(clippy::too_many_arguments)]
154    fn execute_cics_observed<'w>(
155        &self,
156        library: unit::Library,
157        dds: files::Dds,
158        task: cics::Task,
159        clock: unit::Clock,
160        database: Option<&'w mut (dyn sql::Database + '_)>,
161        out: &'w mut dyn Write,
162        err: &'w mut dyn Write,
163        observer: Option<unit::Observer<'w>>,
164    ) -> Result<(Ending, cics::Task), Abend>;
165}
166
167impl Execute for Compiled {
168    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
169        self.run_with(files::Dds::default(), out, err)
170    }
171
172    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
173        self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
174    }
175
176    fn execute<'w>(
177        &self,
178        library: unit::Library,
179        dds: files::Dds,
180        sysin: Option<Box<dyn BufRead + 'w>>,
181        clock: unit::Clock,
182        out: &'w mut dyn Write,
183        err: &'w mut dyn Write,
184    ) -> Result<(Ending, i16), Abend> {
185        self.execute_with(library, dds, sysin, clock, None, out, err)
186    }
187
188    fn execute_with<'w>(
189        &self,
190        library: unit::Library,
191        dds: files::Dds,
192        sysin: Option<Box<dyn BufRead + 'w>>,
193        clock: unit::Clock,
194        database: Option<&'w mut (dyn sql::Database + '_)>,
195        out: &'w mut dyn Write,
196        err: &'w mut dyn Write,
197    ) -> Result<(Ending, i16), Abend> {
198        self.execute_observed(library, dds, sysin, clock, database, out, err, None)
199    }
200
201    fn execute_observed<'w>(
202        &self,
203        library: unit::Library,
204        dds: files::Dds,
205        sysin: Option<Box<dyn BufRead + 'w>>,
206        clock: unit::Clock,
207        database: Option<&'w mut (dyn sql::Database + '_)>,
208        out: &'w mut dyn Write,
209        err: &'w mut dyn Write,
210        observer: Option<unit::Observer<'w>>,
211    ) -> Result<(Ending, i16), Abend> {
212        run_main(self, library, dds, sysin, clock, database, out, err, observer, None, &mut None)
213    }
214
215    fn execute_main<'w>(
216        &self,
217        library: unit::Library,
218        dds: files::Dds,
219        sysin: Option<Box<dyn BufRead + 'w>>,
220        clock: unit::Clock,
221        database: Option<&'w mut (dyn sql::Database + '_)>,
222        out: &'w mut dyn Write,
223        err: &'w mut dyn Write,
224        observer: Option<unit::Observer<'w>>,
225        parm: &str,
226    ) -> Result<(Ending, i16), Abend> {
227        run_main(self, library, dds, sysin, clock, database, out, err, observer, Some(parm), &mut None)
228    }
229
230    fn execute_kept<'w>(
231        &self,
232        library: unit::Library,
233        dds: files::Dds,
234        sysin: Option<Box<dyn BufRead + 'w>>,
235        clock: unit::Clock,
236        database: Option<&'w mut (dyn sql::Database + '_)>,
237        out: &'w mut dyn Write,
238        err: &'w mut dyn Write,
239        observer: Option<unit::Observer<'w>>,
240        kept: &mut Option<unit::Remains>,
241    ) -> Result<(Ending, i16), Abend> {
242        run_main(self, library, dds, sysin, clock, database, out, err, observer, None, kept)
243    }
244
245    fn execute_cics<'w>(
246        &self,
247        library: unit::Library,
248        dds: files::Dds,
249        task: cics::Task,
250        clock: unit::Clock,
251        out: &'w mut dyn Write,
252        err: &'w mut dyn Write,
253    ) -> Result<(Ending, cics::Task), Abend> {
254        self.execute_cics_with(library, dds, task, clock, None, out, err)
255    }
256
257    fn execute_cics_with<'w>(
258        &self,
259        library: unit::Library,
260        dds: files::Dds,
261        task: cics::Task,
262        clock: unit::Clock,
263        database: Option<&'w mut (dyn sql::Database + '_)>,
264        out: &'w mut dyn Write,
265        err: &'w mut dyn Write,
266    ) -> Result<(Ending, cics::Task), Abend> {
267        self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
268    }
269
270    fn execute_cics_observed<'w>(
271        &self,
272        library: unit::Library,
273        dds: files::Dds,
274        task: cics::Task,
275        clock: unit::Clock,
276        database: Option<&'w mut (dyn sql::Database + '_)>,
277        out: &'w mut dyn Write,
278        err: &'w mut dyn Write,
279        observer: Option<unit::Observer<'w>>,
280    ) -> Result<(Ending, cics::Task), Abend> {
281        oo::refuse_to_run(&self.program)?;
282        let (ending, task) = execute_task(self, library, dds, task, clock, database, out, err, observer, &mut None);
283        ending.map(|e| (e, task))
284    }
285}
286
287/// Runs `compiled` on the interpreter as the first program of a CICS task, as
288/// [`Execute::execute_cics_observed`] does; `kept` takes what the run left in its run unit, and the
289/// task comes back however the run ended.
290#[allow(clippy::too_many_arguments)]
291pub(crate) fn execute_task<'w>(
292    compiled: &Compiled,
293    library: unit::Library,
294    dds: files::Dds,
295    task: cics::Task,
296    clock: unit::Clock,
297    database: Option<&'w mut (dyn sql::Database + '_)>,
298    out: &'w mut dyn Write,
299    err: &'w mut dyn Write,
300    observer: Option<unit::Observer<'w>>,
301    kept: &mut Option<unit::Remains>,
302) -> (Result<Ending, Abend>, cics::Task) {
303    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
304    let limit = library.statement_limit;
305    let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
306    run_unit.observer = observer;
307    run_unit.statements = statements;
308    run_unit.taint = taint;
309    run_unit.statement_limit = limit;
310    run_unit.sql = database.map(sql::Session::new);
311    let (ending, ended, task) = run_task(compiled, run_unit, task, kept, |unit, me, commarea, length| {
312        machine::Machine::activation(compiled, me, unit, true).and_then(|mut m| {
313            m.begin_task(commarea, length);
314            m.run_level()
315        })
316    });
317    (ending.map_err(asra).and_then(|e| ended.map(|()| e)), task)
318}
319
320/// A CICS task's run unit given the task, its EXEC interface block and its COMMAREA, and the task's
321/// first program added as `me` and run by `run` with the COMMAREA's address and length; then the
322/// task's unit of work ended, its files closed and its transient data written. Returns how the run
323/// ended, how ending the task went, and the task; `kept` takes what the run left in its run unit.
324pub(crate) fn run_task<'w, H: Clone, L: unit::Loader<H>, E>(
325    compiled: &Compiled,
326    mut run_unit: rt::unit::RunUnit<'w, H, L>,
327    mut task: cics::Task,
328    kept: &mut Option<unit::Remains>,
329    run: impl FnOnce(&mut rt::unit::RunUnit<'w, H, L>, usize, Option<usize>, usize) -> Result<Ending, E>,
330) -> (Result<Ending, E>, Result<(), Abend>, cics::Task) {
331    let me = run_unit.add_named(None, compiled.program.id.to_ascii_uppercase(), compiled.program.files.len(), compiled.layout.size as usize);
332    run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
333    let commarea = task.commarea.take();
334    let length = commarea.as_ref().map_or(0, Vec::len);
335    let commarea = commarea.map(|c| {
336        let at = run_unit.push_temporary(&c);
337        run_unit.mark_input(at, c.len(), true);
338        at
339    });
340    run_unit.cics = Some(task);
341    let ending = run(&mut run_unit, me, commarea, length);
342    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(&compiled.program.id, ending.is_ok()).map(drop));
343    let mut closed = run_unit.close_all(false);
344    for (name, f) in run_unit.cics_files.drain() {
345        if let Err(e) = f.close() {
346            closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
347        }
348    }
349    *kept = Some(unit::Remains::of(&run_unit));
350    let mut task = run_unit.cics.take().unwrap_or_default();
351    if let Err(e) = task.flush_td(compiled.options.code_page()) {
352        closed = closed.and(Err(format!("writing transient data: {e}")));
353    }
354    let settled = settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None });
355    let closed = closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None });
356    (ending, settled.and(closed), task)
357}
358
359/// A program check in a CICS task, which CICS reports as ASRA.
360pub(crate) fn asra(a: Abend) -> Abend {
361    match a.code {
362        AbendCode::Check(_) | AbendCode::Protection => Abend { message: format!("{} ({}, which CICS reports as ASRA)", a.message, a.code), code: AbendCode::Cics("ASRA".into()), pos: a.pos, file: a.file },
363        _ => a,
364    }
365}
366
367/// Runs `compiled` as the first program of a batch run unit; with `parm`, as a job step's main
368/// program.
369#[allow(clippy::too_many_arguments)]
370fn run_main<'w>(
371    compiled: &Compiled,
372    library: unit::Library,
373    dds: files::Dds,
374    sysin: Option<Box<dyn BufRead + 'w>>,
375    clock: unit::Clock,
376    database: Option<&'w mut (dyn sql::Database + '_)>,
377    out: &'w mut dyn Write,
378    err: &'w mut dyn Write,
379    observer: Option<unit::Observer<'w>>,
380    parm: Option<&str>,
381    kept: &mut Option<unit::Remains>,
382) -> Result<(Ending, i16), Abend> {
383    oo::refuse_to_run(&compiled.program)?;
384    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
385    let limit = library.statement_limit;
386    let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
387    run_unit.observer = observer;
388    run_unit.statements = statements;
389    run_unit.taint = taint;
390    run_unit.statement_limit = limit;
391    run_unit.sql = database.map(sql::Session::new);
392    let me = run_unit.add(None, &compiled.program, compiled.layout.size as usize);
393    let trap_off = parm.is_some_and(rt::le::parm::trap_off);
394    let parm = parm.map(|p| push_parm(&mut run_unit, compiled.options.code_page(), p));
395    let ending = machine::Machine::activation(compiled, me, &mut run_unit, true).and_then(|mut m| {
396        if parm.is_some() {
397            m.bind(&[parm]);
398        }
399        m.run_procedure()
400    });
401    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&compiled.program.id, ending.is_ok()).map(drop));
402    let closed = run_unit.close_all(trap_off && ending.as_ref().is_err_and(|a| a.code.bypasses_trap_off()));
403    *kept = Some(unit::Remains::of(&run_unit));
404    let ending = ending?;
405    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
406    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
407    Ok((ending, run_unit.return_code()))
408}
409
410/// A job step's PARM as Language Environment passes it, at the end of memory: input.
411pub(crate) fn push_parm<H: Clone, L: rt::unit::Loader<H>>(run_unit: &mut rt::unit::RunUnit<'_, H, L>, page: &zarch::ebcdic::CodePage, parm: &str) -> usize {
412    let area = rt::le::parm::parameter_area(rt::le::parm::program_arguments(parm), page);
413    let at = run_unit.push_temporary(&area);
414    run_unit.mark_input(at, area.len(), true);
415    at
416}
417
418#[cfg(test)]
419mod tests;