Skip to main content

ironwork_rt/le/
services.rs

1//! A CALL that reaches a Language Environment callable service. The service gets its arguments as
2//! addresses and reads or writes each at its documented length, whatever the argument's own; its
3//! feedback code goes to fc, or, with fc OMITTED, a failure ends the run as an unhandled condition.
4
5use super::{Condition, Reading, Stamp};
6use crate::abend::{Abend, AbendCode};
7use crate::calendar;
8use crate::lir::LeService;
9use crate::unit::{ADDRESS_BASE, Loader, RunUnit, UnitHost};
10use crate::vocab::Pos;
11use std::io::Write;
12use zarch::ebcdic::CodePage;
13
14/// The largest CEEGTST request the run grants, as GETMAIN's limit is for CICS.
15const HEAP_LIMIT: usize = 1 << 28;
16/// MSGFILE's default ddname, where CEEMOUT writes.
17const MSGFILE: &str = "SYSOUT";
18/// The transient data queue that takes a CICS task's LE messages and dumps in place of any DD.
19const CESE: &str = "CESE";
20
21type R<T> = Result<T, Abend>;
22type Outcome = R<Option<Condition>>;
23
24/// What a service asks of the executor running it beyond the run unit: the program's code page, and
25/// the name a dump lists a loaded program by.
26pub trait LeHost<'w>: UnitHost<'w> {
27    fn page(&self) -> &'static CodePage;
28    /// `Class.method` for a loaded COBOL method; None for a program, which a dump lists by name.
29    fn method_name(program: &Self::Program) -> Option<String>;
30}
31
32/// Runs `service` with each argument's address in run-unit memory, as a CALL to a program passes it.
33pub fn call<'w, X: LeHost<'w>>(x: &mut X, service: LeService, args: &[Option<usize>], pos: Pos) -> R<()> {
34    x.unit().unfollowed("Language Environment callable services");
35    let page = x.page();
36    Services { unit: x.unit(), page, method_name: X::method_name }.le_service(service, args, pos)
37}
38
39struct Services<'u, 'w, H, L: Loader<H>> {
40    unit: &'u mut RunUnit<'w, H, L>,
41    page: &'static CodePage,
42    method_name: fn(&H) -> Option<String>,
43}
44
45struct LeCall<'a> {
46    name: &'a str,
47    args: &'a [Option<usize>],
48    pos: Pos,
49}
50
51impl<H: Clone, L: Loader<H>> Services<'_, '_, H, L> {
52    fn le_service(&mut self, service: LeService, args: &[Option<usize>], pos: Pos) -> R<()> {
53        let name = super::name(service);
54        let params = super::parameters(name).unwrap_or_default();
55        let required = params.iter().filter(|p| **p != "fc").count();
56        if args.len() < params.len() {
57            let passed = match args.len() {
58                1 => "1 argument".to_owned(),
59                n => format!("{n} arguments"),
60            };
61            return Err(Abend::ironwork(format!("CALL {name} passes {passed}; {name} takes {}, and with fewer z/OS is unpredictable", params.join(", ")), pos));
62        }
63        let call = LeCall { name, args, pos };
64        let failed = match service {
65            LeService::Cee3abd => return Err(self.cee3abd(&call)),
66            LeService::Cee3dmp => self.cee3dmp(&call)?,
67            LeService::Ceedate => self.ceedate(&call)?,
68            LeService::Ceedatm => self.ceedatm(&call)?,
69            LeService::Ceedays => self.ceedays_or_secs(&call, Reading::Days)?,
70            LeService::Ceedywk => self.ceedywk(&call)?,
71            LeService::Ceefrst => self.ceefrst(&call)?,
72            LeService::Ceegmt | LeService::Ceeutc => self.ceegmt(&call)?,
73            LeService::Ceegmto => self.ceegmto(&call)?,
74            LeService::Ceegtst => self.ceegtst(&call)?,
75            LeService::Ceeloct => self.ceeloct(&call)?,
76            LeService::Ceemout => self.ceemout(&call)?,
77            LeService::Ceesecs => self.ceedays_or_secs(&call, Reading::Seconds)?,
78        };
79        match (args.get(required).copied().flatten(), failed) {
80            (Some(fc), failed) => {
81                self.le_store(fc, &failed.map_or([0; 12], Condition::token));
82                Ok(())
83            }
84            (None, Some(c)) if c.severity >= 2 => Err(Abend {
85                code: AbendCode::user(4038),
86                message: format!("CALL {name}: {} {} With its feedback code omitted the condition was signaled, and nothing handled it", c.symbol(), c.text()),
87                pos,
88                file: None,
89            }),
90            (None, _) => Ok(()),
91        }
92    }
93
94    /// The address of parameter `i`; an OMITTED one is a null address the service stores through.
95    fn le_at(&self, call: &LeCall, i: usize) -> R<usize> {
96        call.args.get(i).copied().flatten().ok_or_else(|| {
97            let param = super::parameters(call.name).and_then(|p| p.get(i)).copied().unwrap_or("argument");
98            Abend { code: AbendCode::Protection, message: format!("CALL {}: {param} is OMITTED, and the service addresses it", call.name), pos: call.pos, file: None }
99        })
100    }
101
102    fn le_load(&self, at: usize, len: usize, pos: Pos) -> R<Vec<u8>> {
103        let bytes = at.checked_add(len).and_then(|end| self.unit.mem.get(at..end));
104        bytes.map(<[u8]>::to_vec).ok_or_else(|| Abend { code: AbendCode::Protection, message: "a callable service's argument reaches outside the run unit's storage".into(), pos, file: None })
105    }
106
107    /// Stores at an argument's address; what would fall past the run unit's storage is not kept.
108    fn le_store(&mut self, at: usize, bytes: &[u8]) {
109        let end = (at + bytes.len()).min(self.unit.mem.len());
110        if at < end {
111            self.unit.mem[at..end].copy_from_slice(&bytes[..end - at]);
112        }
113    }
114
115    fn le_fullword(&self, call: &LeCall, i: usize) -> R<i32> {
116        let at = self.le_at(call, i)?;
117        let b = self.le_load(at, 4, call.pos)?;
118        Ok(i32::from_be_bytes([b[0], b[1], b[2], b[3]]))
119    }
120
121    /// A halfword length-prefixed string; a negative length is a null string.
122    fn le_vstring(&self, call: &LeCall, i: usize) -> R<Vec<u8>> {
123        let at = self.le_at(call, i)?;
124        let len = self.le_load(at, 2, call.pos)?;
125        let len = i16::from_be_bytes([len[0], len[1]]).max(0) as usize;
126        self.le_load(at + 2, len, call.pos)
127    }
128
129    fn le_output(&mut self, call: &LeCall, i: usize, bytes: &[u8]) -> R<()> {
130        let at = self.le_at(call, i)?;
131        self.le_store(at, bytes);
132        Ok(())
133    }
134
135    fn le_now(&self) -> Stamp {
136        let (seconds, hundredths) = self.unit.now();
137        Stamp::from_unix(seconds, hundredths)
138    }
139
140    fn cee3abd(&mut self, call: &LeCall) -> Abend {
141        let code = match self.le_fullword(call, 0) {
142            Ok(c) => c,
143            Err(a) => return a,
144        };
145        let clean_up = self.le_fullword(call, 1).ok().filter(|t| (0..=5).contains(t));
146        let how = match clean_up {
147            Some(1) => "with normal enclave termination",
148            Some(2..=5) => "with enclave termination and its dumps as clean-up asks",
149            _ => "without clean-up",
150        };
151        let user = ((code as u32) & 0xFFF) as u16;
152        if self.unit.cics.is_some() {
153            return Abend { code: AbendCode::Cics(format!("{user:04}")), message: format!("CALL CEE3ABD: transaction abend {user:04}"), pos: call.pos, file: None };
154        }
155        Abend { code: AbendCode::user(user), message: format!("CALL CEE3ABD: user abend {user} {how}"), pos: call.pos, file: None }
156    }
157
158    fn ceedays_or_secs(&mut self, call: &LeCall, reading: Reading) -> Outcome {
159        let input = self.le_vstring(call, 0)?;
160        let picture = self.le_vstring(call, 1)?;
161        let window = self.le_now().fields().year - 80;
162        let result = super::read(&input, &picture, reading, window, self.page);
163        let failed = result.err();
164        let bytes = match (reading, result) {
165            (Reading::Days, r) => (r.map_or(0, |s| s.lilian) as i32).to_be_bytes().to_vec(),
166            (Reading::Seconds, r) => r.map_or([0; 8], |s| super::seconds_hfp(s.total_millis())).to_vec(),
167        };
168        self.le_output(call, 2, &bytes)?;
169        Ok(failed)
170    }
171
172    fn ceedate(&mut self, call: &LeCall) -> Outcome {
173        let lilian = self.le_fullword(call, 0)?;
174        let picture = self.le_vstring(call, 1)?;
175        let (text, failed) = super::date(i64::from(lilian), &picture, self.page);
176        self.le_output(call, 2, &text)?;
177        Ok(failed)
178    }
179
180    fn ceedatm(&mut self, call: &LeCall) -> Outcome {
181        let at = self.le_at(call, 0)?;
182        let seconds = self.le_load(at, 8, call.pos)?;
183        let picture = self.le_vstring(call, 1)?;
184        let (text, failed) = super::timestamp(seconds.try_into().unwrap_or([0; 8]), &picture, self.page);
185        self.le_output(call, 2, &text)?;
186        Ok(failed)
187    }
188
189    fn ceedywk(&mut self, call: &LeCall) -> Outcome {
190        let lilian = i64::from(self.le_fullword(call, 0)?);
191        let (day, failed) = if (1..=calendar::LAST_LILIAN).contains(&lilian) { (calendar::weekday(lilian) as i32, None) } else { (0, Some(super::LILIAN_RANGE)) };
192        self.le_output(call, 1, &day.to_be_bytes())?;
193        Ok(failed)
194    }
195
196    fn ceegmt(&mut self, call: &LeCall) -> Outcome {
197        let now = self.le_now();
198        self.le_output(call, 0, &(now.lilian as i32).to_be_bytes())?;
199        self.le_output(call, 1, &super::seconds_hfp(now.total_millis()))?;
200        Ok(None)
201    }
202
203    fn ceeloct(&mut self, call: &LeCall) -> Outcome {
204        let now = self.le_now();
205        self.le_output(call, 0, &(now.lilian as i32).to_be_bytes())?;
206        self.le_output(call, 1, &super::seconds_hfp(now.total_millis()))?;
207        let text = self.page.encode(&now.gregorian()).unwrap_or_default();
208        self.le_output(call, 2, &text)?;
209        Ok(None)
210    }
211
212    fn ceegmto(&mut self, call: &LeCall) -> Outcome {
213        self.le_output(call, 0, &[0; 4])?;
214        self.le_output(call, 1, &[0; 4])?;
215        self.le_output(call, 2, &[0; 8])?;
216        Ok(None)
217    }
218
219    fn ceemout(&mut self, call: &LeCall) -> Outcome {
220        let message = self.le_vstring(call, 0)?;
221        if self.le_fullword(call, 1)? != 2 {
222            return Ok(Some(super::DESTINATION));
223        }
224        let text = self.page.decode(&message);
225        self.le_write(MSGFILE, &[text], call.pos)?;
226        Ok(None)
227    }
228
229    fn cee3dmp(&mut self, call: &LeCall) -> Outcome {
230        let at = self.le_at(call, 0)?;
231        let title = self.page.decode(&self.le_load(at, 80, call.pos)?);
232        let at = self.le_at(call, 1)?;
233        let options = self.page.decode(&self.le_load(at, 255, call.pos)?);
234        let (dd, invalid) = super::dump_options(&options);
235        let now = self.le_now().fields();
236        let method_name = self.method_name;
237        let active: Vec<String> = self.unit.programs.iter().filter(|p| p.active).map(|p| format!("  {}", p.compiled.as_ref().and_then(method_name).unwrap_or_else(|| p.name.clone()))).collect();
238        let mut lines = vec![
239            format!("CEE3DMP: {:<60}  {:04}-{:02}-{:02} {:02}:{:02}:{:02}", title.chars().take(60).collect::<String>().trim_end(), now.year, now.month, now.day, now.hour, now.minute, now.second),
240            format!("Options: {}", options.trim_end()),
241            "Active programs, in the order the run unit loaded them:".to_owned(),
242        ];
243        lines.extend(active);
244        self.le_write(&dd, &lines, call.pos)?;
245        Ok(invalid.then_some(super::DUMP_OPTIONS))
246    }
247
248    fn ceegtst(&mut self, call: &LeCall) -> Outcome {
249        let heap = self.le_fullword(call, 0)?;
250        let size = self.le_fullword(call, 1)?;
251        let address = self.le_at(call, 2)?;
252        let failed = match (heap, size) {
253            (0, s) if s > 0 && s as usize <= HEAP_LIMIT => None,
254            (0, s) if s > 0 => Some(super::HEAP_SHORT),
255            (0, _) => Some(super::HEAP_SIZE),
256            _ => Some(super::HEAP_ID),
257        };
258        if failed.is_none() {
259            let at = self.unit.push_temporary(&vec![0; size as usize]);
260            self.unit.le.heap.push((at, size as usize, false));
261            self.le_store(address, &(ADDRESS_BASE + at as u32).to_be_bytes());
262        }
263        Ok(failed)
264    }
265
266    fn ceefrst(&mut self, call: &LeCall) -> Outcome {
267        let at = self.le_at(call, 0)?;
268        let b = self.le_load(at, 4, call.pos)?;
269        let address = u32::from_be_bytes([b[0], b[1], b[2], b[3]]);
270        let offset = address.checked_sub(ADDRESS_BASE).map(|o| o as usize);
271        match self.unit.le.heap.iter_mut().find(|(start, _, freed)| Some(*start) == offset && !*freed) {
272            Some(block) => {
273                block.2 = true;
274                Ok(None)
275            }
276            None => Ok(Some(super::FREE_ADDRESS)),
277        }
278    }
279
280    /// Writes lines to a DD as UTF-8 text, the run's first write replacing the file, or to standard
281    /// error when the DD is not given; in a CICS task, each line is an item on TD queue CESE.
282    fn le_write(&mut self, dd: &str, lines: &[String], pos: Pos) -> R<()> {
283        if let Some(task) = self.unit.cics.as_mut() {
284            for line in lines {
285                task.writeq_td(CESE, &self.page.encode(line).unwrap_or_default());
286            }
287            return Ok(());
288        }
289        let text: String = lines.iter().map(|l| format!("{l}\n")).collect();
290        let Some(file) = self.unit.dds.get(dd) else {
291            return self.unit.err.write_all(text.as_bytes()).map_err(|e| Abend::ironwork(format!("writing to standard error: {e}"), pos));
292        };
293        let first = !self.unit.le.written.iter().any(|w| w == dd);
294        let opened = std::fs::OpenOptions::new().create(true).write(true).append(!first).truncate(first).open(&file.path);
295        opened
296            .and_then(|mut f| f.write_all(text.as_bytes()))
297            .map_err(|e| Abend::ironwork(format!("DD {dd} {}: {e}", file.path.display()), pos))?;
298        if first {
299            self.unit.le.written.push(dd.to_owned());
300        }
301        Ok(())
302    }
303}