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, as a subprogram whose caller passed `arguments`,
108    /// one per PROCEDURE DIVISION USING item: the bytes of the item passed, or None for OMITTED,
109    /// which passes a null address. Each argument is input to the run, and EXIT PROGRAM returns.
110    #[allow(clippy::too_many_arguments)]
111    fn execute_with_arguments<'w>(
112        &self,
113        library: unit::Library,
114        dds: files::Dds,
115        sysin: Option<Box<dyn BufRead + 'w>>,
116        clock: unit::Clock,
117        database: Option<&'w mut (dyn sql::Database + '_)>,
118        out: &'w mut dyn Write,
119        err: &'w mut dyn Write,
120        observer: Option<unit::Observer<'w>>,
121        arguments: &[Option<Vec<u8>>],
122    ) -> Result<(Ending, i16), Abend>;
123
124    /// Runs as [`Execute::execute_observed`] does, and puts what the run left in its run unit in
125    /// `kept`.
126    #[allow(clippy::too_many_arguments)]
127    fn execute_kept<'w>(
128        &self,
129        library: unit::Library,
130        dds: files::Dds,
131        sysin: Option<Box<dyn BufRead + 'w>>,
132        clock: unit::Clock,
133        database: Option<&'w mut (dyn sql::Database + '_)>,
134        out: &'w mut dyn Write,
135        err: &'w mut dyn Write,
136        observer: Option<unit::Observer<'w>>,
137        kept: &mut Option<unit::Remains>,
138    ) -> Result<(Ending, i16), Abend>;
139
140    /// Runs as the first program of a CICS task. `task` says who started it, what COMMAREA it
141    /// starts with, and what files and queues it has. Returns how the run ended and the task, with
142    /// RETURN TRANSID and COMMAREA if it ended that way.
143    fn execute_cics<'w>(
144        &self,
145        library: unit::Library,
146        dds: files::Dds,
147        task: cics::Task,
148        clock: unit::Clock,
149        out: &'w mut dyn Write,
150        err: &'w mut dyn Write,
151    ) -> Result<(Ending, cics::Task), Abend>;
152
153    /// A CICS task with a database: SYNCPOINT commits, and the end of the task commits, or rolls
154    /// back after an abend, and closes every cursor. The database outlives the task, so a region's
155    /// tasks can share one.
156    #[allow(clippy::too_many_arguments)]
157    fn execute_cics_with<'w>(
158        &self,
159        library: unit::Library,
160        dds: files::Dds,
161        task: cics::Task,
162        clock: unit::Clock,
163        database: Option<&'w mut (dyn sql::Database + '_)>,
164        out: &'w mut dyn Write,
165        err: &'w mut dyn Write,
166    ) -> Result<(Ending, cics::Task), Abend>;
167
168    /// Runs as [`Execute::execute_cics_with`] does, telling `observer` what the task opens, loads
169    /// and passes to an operation an input could steer.
170    #[allow(clippy::too_many_arguments)]
171    fn execute_cics_observed<'w>(
172        &self,
173        library: unit::Library,
174        dds: files::Dds,
175        task: cics::Task,
176        clock: unit::Clock,
177        database: Option<&'w mut (dyn sql::Database + '_)>,
178        out: &'w mut dyn Write,
179        err: &'w mut dyn Write,
180        observer: Option<unit::Observer<'w>>,
181    ) -> Result<(Ending, cics::Task), Abend>;
182}
183
184impl Execute for Compiled {
185    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
186        self.run_with(files::Dds::default(), out, err)
187    }
188
189    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
190        self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
191    }
192
193    fn execute<'w>(
194        &self,
195        library: unit::Library,
196        dds: files::Dds,
197        sysin: Option<Box<dyn BufRead + 'w>>,
198        clock: unit::Clock,
199        out: &'w mut dyn Write,
200        err: &'w mut dyn Write,
201    ) -> Result<(Ending, i16), Abend> {
202        self.execute_with(library, dds, sysin, clock, None, out, err)
203    }
204
205    fn execute_with<'w>(
206        &self,
207        library: unit::Library,
208        dds: files::Dds,
209        sysin: Option<Box<dyn BufRead + 'w>>,
210        clock: unit::Clock,
211        database: Option<&'w mut (dyn sql::Database + '_)>,
212        out: &'w mut dyn Write,
213        err: &'w mut dyn Write,
214    ) -> Result<(Ending, i16), Abend> {
215        self.execute_observed(library, dds, sysin, clock, database, out, err, None)
216    }
217
218    fn execute_observed<'w>(
219        &self,
220        library: unit::Library,
221        dds: files::Dds,
222        sysin: Option<Box<dyn BufRead + 'w>>,
223        clock: unit::Clock,
224        database: Option<&'w mut (dyn sql::Database + '_)>,
225        out: &'w mut dyn Write,
226        err: &'w mut dyn Write,
227        observer: Option<unit::Observer<'w>>,
228    ) -> Result<(Ending, i16), Abend> {
229        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, &mut None)
230    }
231
232    fn execute_main<'w>(
233        &self,
234        library: unit::Library,
235        dds: files::Dds,
236        sysin: Option<Box<dyn BufRead + 'w>>,
237        clock: unit::Clock,
238        database: Option<&'w mut (dyn sql::Database + '_)>,
239        out: &'w mut dyn Write,
240        err: &'w mut dyn Write,
241        observer: Option<unit::Observer<'w>>,
242        parm: &str,
243    ) -> Result<(Ending, i16), Abend> {
244        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Parm(parm), &mut None)
245    }
246
247    fn execute_with_arguments<'w>(
248        &self,
249        library: unit::Library,
250        dds: files::Dds,
251        sysin: Option<Box<dyn BufRead + 'w>>,
252        clock: unit::Clock,
253        database: Option<&'w mut (dyn sql::Database + '_)>,
254        out: &'w mut dyn Write,
255        err: &'w mut dyn Write,
256        observer: Option<unit::Observer<'w>>,
257        arguments: &[Option<Vec<u8>>],
258    ) -> Result<(Ending, i16), Abend> {
259        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Arguments(arguments), &mut None)
260    }
261
262    fn execute_kept<'w>(
263        &self,
264        library: unit::Library,
265        dds: files::Dds,
266        sysin: Option<Box<dyn BufRead + 'w>>,
267        clock: unit::Clock,
268        database: Option<&'w mut (dyn sql::Database + '_)>,
269        out: &'w mut dyn Write,
270        err: &'w mut dyn Write,
271        observer: Option<unit::Observer<'w>>,
272        kept: &mut Option<unit::Remains>,
273    ) -> Result<(Ending, i16), Abend> {
274        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, kept)
275    }
276
277    fn execute_cics<'w>(
278        &self,
279        library: unit::Library,
280        dds: files::Dds,
281        task: cics::Task,
282        clock: unit::Clock,
283        out: &'w mut dyn Write,
284        err: &'w mut dyn Write,
285    ) -> Result<(Ending, cics::Task), Abend> {
286        self.execute_cics_with(library, dds, task, clock, None, out, err)
287    }
288
289    fn execute_cics_with<'w>(
290        &self,
291        library: unit::Library,
292        dds: files::Dds,
293        task: cics::Task,
294        clock: unit::Clock,
295        database: Option<&'w mut (dyn sql::Database + '_)>,
296        out: &'w mut dyn Write,
297        err: &'w mut dyn Write,
298    ) -> Result<(Ending, cics::Task), Abend> {
299        self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
300    }
301
302    fn execute_cics_observed<'w>(
303        &self,
304        library: unit::Library,
305        dds: files::Dds,
306        task: cics::Task,
307        clock: unit::Clock,
308        database: Option<&'w mut (dyn sql::Database + '_)>,
309        out: &'w mut dyn Write,
310        err: &'w mut dyn Write,
311        observer: Option<unit::Observer<'w>>,
312    ) -> Result<(Ending, cics::Task), Abend> {
313        oo::refuse_to_run(&self.program)?;
314        let (ending, task) = execute_task(self, library, dds, task, clock, database, out, err, observer, &mut None);
315        ending.map(|e| (e, task))
316    }
317}
318
319/// Runs `compiled` on the interpreter as the first program of a CICS task, as
320/// [`Execute::execute_cics_observed`] does; `kept` takes what the run left in its run unit, and the
321/// task comes back however the run ended.
322#[allow(clippy::too_many_arguments)]
323pub(crate) fn execute_task<'w>(
324    compiled: &Compiled,
325    library: unit::Library,
326    dds: files::Dds,
327    task: cics::Task,
328    clock: unit::Clock,
329    database: Option<&'w mut (dyn sql::Database + '_)>,
330    out: &'w mut dyn Write,
331    err: &'w mut dyn Write,
332    observer: Option<unit::Observer<'w>>,
333    kept: &mut Option<unit::Remains>,
334) -> (Result<Ending, Abend>, cics::Task) {
335    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
336    let limit = library.statement_limit;
337    let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
338    run_unit.observer = observer;
339    run_unit.statements = statements;
340    run_unit.taint = taint;
341    run_unit.statement_limit = limit;
342    run_unit.sql = database.map(sql::Session::new);
343    let (ending, ended, task) = run_task(First::of(compiled), run_unit, task, kept, |unit, me, commarea, length| {
344        machine::Machine::activation(compiled, me, unit, true).and_then(|mut m| {
345            m.begin_task(commarea, length);
346            m.run_level()
347        })
348    });
349    (ending.map_err(asra).and_then(|e| ended.map(|()| e)), task)
350}
351
352/// A CICS task's run unit given the task, its EXEC interface block and its COMMAREA, and the task's
353/// first program added as `me` and run by `run` with the COMMAREA's address and length; then the
354/// task's unit of work ended, its files closed and its transient data written. Returns how the run
355/// ended, how ending the task went, and the task; `kept` takes what the run left in its run unit.
356pub(crate) fn run_task<'w, H: Clone, L: unit::Loader<H>, E>(
357    first: First<'_>,
358    mut run_unit: rt::unit::RunUnit<'w, H, L>,
359    mut task: cics::Task,
360    kept: &mut Option<unit::Remains>,
361    run: impl FnOnce(&mut rt::unit::RunUnit<'w, H, L>, usize, Option<usize>, usize) -> Result<Ending, E>,
362) -> (Result<Ending, E>, Result<(), Abend>, cics::Task) {
363    let me = run_unit.add_named(None, first.name, first.files, first.size);
364    run_unit.programs[me].source = first.source;
365    run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
366    let commarea = task.commarea.take();
367    let length = commarea.as_ref().map_or(0, Vec::len);
368    let commarea = commarea.map(|c| {
369        let at = run_unit.push_temporary(&c);
370        run_unit.mark_input(at, c.len(), true);
371        at
372    });
373    run_unit.cics = Some(task);
374    let ending = run(&mut run_unit, me, commarea, length);
375    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(first.id, ending.is_ok()).map(drop));
376    let mut closed = run_unit.close_all(false);
377    for (name, f) in run_unit.cics_files.drain() {
378        if let Err(e) = f.close() {
379            closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
380        }
381    }
382    *kept = Some(unit::Remains::of(&run_unit));
383    let mut task = run_unit.cics.take().unwrap_or_default();
384    if let Err(e) = task.flush_td(first.page) {
385        closed = closed.and(Err(format!("writing transient data: {e}")));
386    }
387    let settled = settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None });
388    let closed = closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None });
389    (ending, settled.and(closed), task)
390}
391
392/// What a CICS task's run unit needs of its first program besides its code: the PROGRAM-ID, which
393/// names the task's unit of work, the name the run unit holds it by, its shape, its code page, and
394/// the file a program library supplied it from.
395pub(crate) struct First<'a> {
396    pub id: &'a str,
397    pub name: String,
398    pub files: usize,
399    pub size: usize,
400    pub page: &'static zarch::ebcdic::CodePage,
401    pub source: Option<std::path::PathBuf>,
402}
403
404impl<'a> First<'a> {
405    pub(crate) fn of(compiled: &'a Compiled) -> Self {
406        let p = &compiled.program;
407        Self { id: &p.id, name: p.id.to_ascii_uppercase(), files: p.files.len(), size: compiled.layout.size as usize, page: compiled.options.code_page(), source: None }
408    }
409}
410
411/// A program check in a CICS task, which CICS reports as ASRA.
412pub(crate) fn asra(a: Abend) -> Abend {
413    match a.code {
414        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 },
415        _ => a,
416    }
417}
418
419/// What the first program of a batch run unit is given for its PROCEDURE DIVISION USING items.
420#[derive(Clone, Copy, Debug, Default)]
421pub enum Passed<'a> {
422    /// Nothing: a main program no one passes anything.
423    #[default]
424    Nothing,
425    /// A job step's PARM, as Language Environment builds its parameter list.
426    Parm(&'a str),
427    /// What a caller passes a subprogram, one per USING item: the bytes of the item passed, or
428    /// None for OMITTED.
429    Arguments(&'a [Option<Vec<u8>>]),
430}
431
432impl Passed<'_> {
433    /// The addresses the USING items are bound to, each argument pushed as input.
434    pub(crate) fn addresses<H: Clone, L: rt::unit::Loader<H>>(self, run_unit: &mut rt::unit::RunUnit<'_, H, L>, page: &zarch::ebcdic::CodePage) -> Vec<Option<usize>> {
435        match self {
436            Passed::Nothing => Vec::new(),
437            Passed::Parm(parm) => vec![Some(push_parm(run_unit, page, parm))],
438            Passed::Arguments(arguments) => arguments.iter().map(|a| a.as_deref().map(|bytes| push_input(run_unit, bytes))).collect(),
439        }
440    }
441
442    /// Whether the program runs as a run unit's main program, where EXIT PROGRAM does nothing,
443    /// rather than as one a caller passed arguments to.
444    pub(crate) fn main(self) -> bool {
445        !matches!(self, Passed::Arguments(_))
446    }
447}
448
449/// Runs `compiled` as the first program of a batch run unit, given `passed`: a main program, a
450/// job step's main program, or a subprogram as its caller would run it.
451#[allow(clippy::too_many_arguments)]
452fn run_main<'w>(
453    compiled: &Compiled,
454    library: unit::Library,
455    dds: files::Dds,
456    sysin: Option<Box<dyn BufRead + 'w>>,
457    clock: unit::Clock,
458    database: Option<&'w mut (dyn sql::Database + '_)>,
459    out: &'w mut dyn Write,
460    err: &'w mut dyn Write,
461    observer: Option<unit::Observer<'w>>,
462    passed: Passed<'_>,
463    kept: &mut Option<unit::Remains>,
464) -> Result<(Ending, i16), Abend> {
465    oo::refuse_to_run(&compiled.program)?;
466    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
467    let limit = library.statement_limit;
468    let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
469    run_unit.observer = observer;
470    run_unit.statements = statements;
471    run_unit.taint = taint;
472    run_unit.statement_limit = limit;
473    run_unit.sql = database.map(sql::Session::new);
474    let me = run_unit.add(None, &compiled.program, compiled.layout.size as usize);
475    let trap_off = matches!(passed, Passed::Parm(p) if rt::le::parm::trap_off(p));
476    let addresses = passed.addresses(&mut run_unit, compiled.options.code_page());
477    let ending = machine::Machine::activation(compiled, me, &mut run_unit, passed.main()).and_then(|mut m| {
478        if !addresses.is_empty() {
479            m.bind(&addresses);
480        }
481        m.run_procedure()
482    });
483    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&compiled.program.id, ending.is_ok()).map(drop));
484    let closed = run_unit.close_all(trap_off && ending.as_ref().is_err_and(|a| a.code.bypasses_trap_off()));
485    *kept = Some(unit::Remains::of(&run_unit));
486    let ending = ending?;
487    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
488    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
489    Ok((ending, run_unit.return_code()))
490}
491
492/// A job step's PARM as Language Environment passes it, at the end of memory: input.
493pub(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 {
494    let area = rt::le::parm::parameter_area(rt::le::parm::program_arguments(parm), page);
495    push_input(run_unit, &area)
496}
497
498/// `bytes` at the end of memory, marked as input.
499fn push_input<H: Clone, L: rt::unit::Loader<H>>(run_unit: &mut rt::unit::RunUnit<'_, H, L>, bytes: &[u8]) -> usize {
500    let at = run_unit.push_temporary(bytes);
501    run_unit.mark_input(at, bytes.len(), true);
502    at
503}
504
505#[cfg(test)]
506mod tests;