1use 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
14const HEAP_LIMIT: usize = 1 << 28;
16const MSGFILE: &str = "SYSOUT";
18const CESE: &str = "CESE";
20
21type R<T> = Result<T, Abend>;
22type Outcome = R<Option<Condition>>;
23
24pub trait LeHost<'w>: UnitHost<'w> {
27 fn page(&self) -> &'static CodePage;
28 fn method_name(program: &Self::Program) -> Option<String>;
30}
31
32pub 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 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 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 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 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}