1use crate::abend::Abend;
10use crate::files::{Dds, Open};
11use crate::oo::ClassCode;
12use crate::storage::Loc;
13use crate::taint::Taint;
14use crate::vocab::{OpenMode, Pos};
15use numeric::Dialect;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::io::{BufRead, Write};
18use std::path::{Path, PathBuf};
19use std::time::{Duration, Instant};
20use std::rc::Rc;
21
22pub const ADDRESS_BASE: u32 = 0x0001_0000;
25pub const RETURN_CODE: usize = 0;
27const RESERVED: usize = 8;
28const ALIGNMENT: usize = 8;
29
30pub struct Loaded<H> {
31 pub compiled: Option<H>,
33 pub name: String,
34 pub base: usize,
35 pub size: usize,
36 pub placed: bool,
39 pub files: Vec<Option<Open>>,
40 pub locked: Vec<bool>,
42 pub initialized: bool,
43 pub active: bool,
44 pub dynamic: bool,
46 pub entry: Option<usize>,
49 pub altered: Vec<Option<usize>>,
51 pub source: Option<PathBuf>,
53}
54
55impl<H> Loaded<H> {
56 pub fn new(compiled: Option<H>, name: String, base: usize, size: usize, files: usize) -> Self {
58 let (locked, files) = (vec![false; files], (0..files).map(|_| None).collect());
59 Self { compiled, name, base, size, placed: true, files, locked, initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None }
60 }
61
62 fn set_aside(&mut self) -> Held {
65 let files = self.files.iter_mut().map(Option::take).collect();
66 let locked = std::mem::replace(&mut self.locked, vec![false; self.files.len()]);
67 let held = Held { base: self.base, placed: self.placed, files, locked, initialized: self.initialized, active: self.active, dynamic: self.dynamic, altered: std::mem::take(&mut self.altered) };
68 (self.placed, self.initialized, self.active, self.dynamic) = (false, false, false, false);
69 held
70 }
71
72 fn restore(&mut self, held: Held) {
73 (self.base, self.placed, self.files, self.locked, self.altered) = (held.base, held.placed, held.files, held.locked, held.altered);
74 (self.initialized, self.active, self.dynamic) = (held.initialized, held.active, held.dynamic);
75 }
76}
77
78struct Enclave {
83 programs: Vec<Held>,
84 externals: Externals,
85 connectors: HashMap<(usize, usize), Connector>,
86 heap: Vec<(usize, usize, bool)>,
87 random: Option<u32>,
88 return_code: ([u8; 2], bool),
90}
91
92struct Held {
94 base: usize,
95 placed: bool,
96 files: Vec<Option<Open>>,
97 locked: Vec<bool>,
98 initialized: bool,
99 active: bool,
100 dynamic: bool,
101 altered: Vec<Option<usize>>,
102}
103
104pub enum LoadError {
105 NotFound,
106 Compile(String),
108}
109
110pub struct LoadedProgram<H> {
112 pub compiled: H,
113 pub name: String,
115 pub files: usize,
116 pub size: usize,
117 pub source: Option<PathBuf>,
119 pub recorded: Vec<(String, Option<crate::module::SourceFile>)>,
122}
123
124pub struct FoundClass<C> {
127 pub code: C,
128 pub sources: Vec<String>,
129}
130
131pub trait Loader<H> {
133 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
135
136 fn holder(&self, entry: &str) -> Option<String>;
138
139 fn entry(program: &H, name: &str) -> Option<usize>;
141
142 fn shape(program: &H) -> (usize, usize);
144
145 fn facts(program: &H) -> numeric::governs::Facts;
147
148 fn nested(program: &H) -> &[String];
150
151 fn source(program: &H, file: usize) -> Option<String>;
153
154 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode<H>>>>, String>;
157
158 fn mapset(&mut self, name: &str) -> Option<Result<crate::bms::Mapset, String>>;
161}
162
163pub trait UnitHost<'w> {
166 type Program: Clone;
167 type Loader: Loader<Self::Program>;
168 fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
169}
170
171#[derive(Clone, Copy, Debug)]
172pub enum Clock {
173 System,
174 Fixed(i64, u32),
176}
177
178pub enum Event<'a> {
182 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
183 Close { dd: &'a str, path: &'a Path },
184 Load { program: &'a str, source: Option<&'a Path>, recorded: &'a [(String, Option<crate::module::SourceFile>)], facts: numeric::governs::Facts },
187 Class { class: &'a str, facts: numeric::governs::Facts },
189 Paragraph { program: &'a str, name: &'a str, index: usize },
191 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str, input: Option<bool> },
196 Statement { file: &'a str, line: u32 },
198}
199
200pub const SINK_KINDS: [&str; 16] = [
203 "cics-dynamic-transfer",
204 "cics-sysid",
205 "connection-target",
206 "dynamic-file-path",
207 "dynamic-program-load",
208 "dynamic-sql",
209 "http-header",
210 "log",
211 "os-command",
212 "outbound-host",
213 "outbound-http",
214 "queue-name",
215 "record-key",
216 "record-update",
217 "screen",
218 "web-response",
219];
220
221#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum StatementFilter {
225 All,
226 Lines(HashSet<u32>),
227}
228
229pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
230
231pub const MAX_DEPTH: usize = 100;
233
234#[derive(Default)]
238pub struct Externals {
239 storage: HashMap<(bool, String), (usize, usize)>,
242 files: Vec<Option<Open>>,
243 locked: Vec<bool>,
245 file_names: HashMap<String, usize>,
246}
247
248pub fn switch_record(n: u8) -> String {
252 format!("UPSI-{n} SWITCH")
253}
254
255pub fn switch_of_record(name: &str) -> Option<u8> {
257 match name.strip_prefix("UPSI-")?.strip_suffix(" SWITCH")?.as_bytes() {
258 [d @ b'0'..=b'7'] => Some(d - b'0'),
259 _ => None,
260 }
261}
262
263#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub enum Connector {
266 External(usize),
268 Program(usize, usize),
270}
271
272pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
275
276pub struct RunUnit<'w, H, L: Loader<H>> {
277 pub mem: Vec<u8>,
278 pub depth: usize,
280 pub programs: Vec<Loaded<H>>,
281 names: HashMap<String, usize>,
282 pub library: L,
283 pub dds: Dds,
284 pub sysin: Option<Box<dyn BufRead + 'w>>,
285 pub clock: Clock,
286 pub out: &'w mut dyn Write,
287 pub err: &'w mut dyn Write,
288 pub cics: Option<crate::cics::Task>,
290 pub eib: usize,
291 pub cics_files: HashMap<String, Open>,
292 pub sql: Option<crate::sql::Session<'w>>,
294 pub le: crate::le::State,
296 pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
298 pub observer: Option<Observer<'w>>,
300 pub random: Option<u32>,
302 pub main: Option<usize>,
304 pub calls: Vec<usize>,
306 pub argument_lengths: Vec<usize>,
309 pub argument_starts: Vec<usize>,
310 pub arguments: crate::le::parm::Arguments,
313 pub crt: Option<Rc<std::cell::RefCell<crate::crt::Crt>>>,
316 pub environment: crate::environment::Environment,
318 externals: Externals,
319 connectors: HashMap<(usize, usize), Connector>,
321 pub entries: Vec<crate::set::Entry>,
323 pub statements: Option<StatementFilter>,
325 pub taint: Option<Taint>,
327 pub statement_limit: Option<u64>,
329 deadline: Option<(Instant, u64)>,
331 unclocked: u32,
333 storage_limit: Option<usize>,
335 recent: VecDeque<Started>,
338 overrun: Option<Overrun>,
339 pub(crate) sysin_ended: HashSet<(u16, u32, u32)>,
342 set_aside: Vec<Enclave>,
344}
345
346fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
347 if unclosed { f.abandon() } else { f.close() }
348}
349
350const CLOCK_EVERY: u32 = 256;
352
353const LOOP_WINDOW: usize = 4096;
356
357#[derive(Clone, Copy, PartialEq, Eq)]
359struct Started {
360 program: usize,
361 depth: usize,
362 pos: Pos,
363}
364
365struct Overrun {
368 head: Started,
369 lines: Vec<u32>,
370 grace: usize,
371}
372
373fn loop_of(recent: &VecDeque<Started>, now: Started) -> Overrun {
379 let key = |s: &Started| (s.program, s.depth, s.pos.file, s.pos.line, s.pos.col);
380 let mut seen: HashMap<_, usize> = HashMap::new();
381 for s in recent {
382 *seen.entry(key(s)).or_default() += 1;
383 }
384 let recurring: Vec<&Started> = recent.iter().filter(|s| seen[&key(s)] > 1).collect();
385 let Some(outer) = recurring.iter().map(|s| s.depth).min() else { return Overrun { head: now, lines: Vec::new(), grace: 0 } };
386 let program = recurring.iter().rev().find(|s| s.depth == outer).map_or(now.program, |s| s.program);
387 let frame: Vec<&Started> = recurring.into_iter().filter(|s| s.depth == outer && s.program == program).collect();
388 let file = frame.iter().map(|s| s.pos.file).min().unwrap_or(now.pos.file);
389 let head = frame.iter().filter(|s| s.pos.file == file).min_by_key(|s| (s.pos.line, s.pos.col)).map_or(now, |s| **s);
390 let mut lines: Vec<u32> = frame.iter().filter(|s| s.pos.file == file).map(|s| s.pos.line).collect();
391 lines.sort_unstable();
392 lines.dedup();
393 Overrun { head, lines, grace: LOOP_WINDOW }
394}
395
396pub fn step_return_code(return_code: i16) -> u16 {
399 i32::from(return_code).rem_euclid(4096) as u16
400}
401
402impl<H, L: Loader<H>> RunUnit<'_, H, L> {
403 pub fn write(&mut self, offset: usize, bytes: &[u8]) {
407 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
408 self.mark(offset, bytes.len());
409 }
410
411 pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
413 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
414 self.mark_input(offset, bytes.len(), true);
415 }
416
417 pub fn mark(&mut self, offset: usize, len: usize) {
419 if let Some(t) = self.taint.as_mut() {
420 let pending = t.pending();
421 t.set(offset, len, pending);
422 }
423 }
424
425 pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
427 if let Some(t) = self.taint.as_mut() {
428 t.set(offset, len, input);
429 }
430 }
431
432 pub fn taint_read(&mut self, loc: Loc) {
434 if let Some(t) = self.taint.as_mut() {
435 t.read(loc.offset, loc.len);
436 }
437 }
438
439 pub fn writing(&mut self, on: bool) -> bool {
441 self.taint.as_mut().is_some_and(|t| t.writing(on))
442 }
443
444 pub fn statement_starts(&mut self) {
446 if let Some(t) = self.taint.as_mut() {
447 t.start_statement();
448 }
449 }
450
451 pub fn take_input(&mut self) {
453 if let Some(t) = self.taint.as_mut() {
454 t.take_input();
455 }
456 }
457
458 pub fn holds_input(&self, offset: usize, len: usize) -> bool {
460 self.taint.as_ref().is_some_and(|t| t.any(offset, len))
461 }
462
463 pub fn pending(&self) -> bool {
465 self.taint.as_ref().is_some_and(Taint::pending)
466 }
467
468 pub fn resume_statement(&mut self, read_before: bool) {
470 if let Some(t) = self.taint.as_mut() {
471 t.resume_statement(read_before);
472 }
473 }
474
475 pub fn unfollowed(&mut self, what: &'static str) {
477 if let Some(t) = self.taint.as_mut() {
478 t.unfollowed(what);
479 }
480 }
481
482 pub fn input_at_sink(&self) -> Option<bool> {
484 self.taint.as_ref().and_then(Taint::at_sink)
485 }
486}
487
488impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
489 pub fn new(library: L, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
490 Self {
491 mem: vec![0; RESERVED],
492 depth: 0,
493 programs: Vec::new(),
494 names: HashMap::new(),
495 library,
496 dds,
497 sysin,
498 clock,
499 out,
500 err,
501 cics: None,
502 eib: 0,
503 cics_files: HashMap::new(),
504 sql: None,
505 le: crate::le::State::default(),
506 oo: Default::default(),
507 observer: None,
508 random: None,
509 main: None,
510 calls: Vec::new(),
511 argument_lengths: Vec::new(),
512 argument_starts: Vec::new(),
513 arguments: Default::default(),
514 crt: None,
515 environment: Default::default(),
516 externals: Externals::default(),
517 connectors: HashMap::new(),
518 entries: Vec::new(),
519 statements: None,
520 taint: None,
521 statement_limit: None,
522 deadline: None,
523 unclocked: 0,
524 storage_limit: None,
525 recent: VecDeque::new(),
526 overrun: None,
527 sysin_ended: HashSet::new(),
528 set_aside: Vec::new(),
529 }
530 }
531
532 fn allocate(&mut self, size: usize) -> usize {
533 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
534 self.mem.resize(base + size, 0);
535 base
536 }
537
538 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
540 let base = self.allocate(size);
541 let index = self.programs.len();
542 self.names.insert(name.clone(), index);
543 self.programs.push(Loaded::new(compiled, name, base, size, files));
544 index
545 }
546
547 pub fn begin_cics_run_unit(&mut self) {
552 let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
553 let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
554 let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
555 self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
556 self.mark_input(RETURN_CODE, 2, false);
557 let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
558 self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
559 }
560
561 pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
567 let mut closed = Ok(());
568 for program in &mut self.programs {
569 for f in program.files.iter_mut().filter_map(Option::take) {
570 if let Err(e) = f.close() {
571 closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
572 }
573 }
574 drop(program.set_aside());
575 }
576 closed = closed.and(self.close_external_files(false));
577 let Some(enclave) = self.set_aside.pop() else { return closed };
578 for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
579 program.restore(held);
580 }
581 (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
582 if !xctl {
583 let (bytes, input) = enclave.return_code;
584 self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
585 self.mark_input(RETURN_CODE, 2, input);
586 }
587 closed
588 }
589
590 pub fn load_entry(&mut self, name: &str, copy: bool) -> Result<(usize, Option<usize>), LoadError> {
594 if let Some(i) = self.find(name) {
595 return Ok((i, self.programs[i].entry));
596 }
597 let name = name.to_ascii_uppercase();
598 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
599 Some(i) => i,
600 None => {
601 let holder = self.library.holder(&name);
602 self.load(holder.as_deref().unwrap_or(&name))?
603 }
604 };
605 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
606 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
607 if !copy {
608 return Ok((index, Some(entry)));
609 }
610 let (files, size) = L::shape(&compiled);
611 let copy = self.add_named(Some(compiled), name, files, size);
612 self.programs[copy].entry = Some(entry);
613 self.programs[copy].source = self.programs[index].source.clone();
614 Ok((copy, Some(entry)))
615 }
616
617 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
620 let at = self.allocate(bytes.len());
621 self.write(at, bytes);
622 at
623 }
624
625 pub fn release_temporaries(&mut self, mark: usize) {
628 if self.mem.len() <= mark {
629 return;
630 }
631 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
632 if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
633 self.mem.truncate(mark.max(RESERVED));
634 if let Some(t) = self.taint.as_mut() {
635 t.truncate(self.mem.len());
636 }
637 }
638 }
639
640 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
642 if self.depth >= MAX_DEPTH {
643 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
644 }
645 self.depth += 1;
646 Ok(())
647 }
648
649 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
653 if !self.programs[me].placed {
654 let base = self.allocate(self.programs[me].size);
655 (self.programs[me].base, self.programs[me].placed) = (base, true);
656 }
657 self.main.get_or_insert(me);
658 let program = &mut self.programs[me];
659 program.active = true;
660 (program.base, !program.initialized || initial)
661 }
662
663 pub fn caller_of(&self, me: usize) -> Option<usize> {
666 match self.calls.iter().rposition(|&p| p == me)? {
667 0 => self.main,
668 k => Some(self.calls[k - 1]),
669 }
670 }
671
672 pub fn heap_allocate(&mut self, size: usize) -> u32 {
675 if size == 0 || size > crate::le::HEAP_LIMIT {
676 return 0;
677 }
678 let at = self.push_temporary(&vec![0; size]);
679 self.le.heap.push((at, size, false));
680 ADDRESS_BASE + at as u32
681 }
682
683 pub fn heap_free(&mut self, address: u32) -> u32 {
686 let offset = address.checked_sub(ADDRESS_BASE).map(|o| o as usize);
687 match self.le.heap.iter_mut().find(|(start, _, freed)| Some(*start) == offset && !*freed) {
688 Some(block) => {
689 block.2 = true;
690 0
691 }
692 None => address,
693 }
694 }
695
696 pub fn argument_length_of(&self, me: usize, position: usize) -> usize {
699 let Some(k) = self.calls.iter().rposition(|&p| p == me) else { return 0 };
700 let end = self.argument_starts.get(k + 1).copied().unwrap_or(self.argument_lengths.len());
701 position.checked_sub(1).and_then(|i| self.argument_lengths[self.argument_starts[k]..end].get(i)).copied().unwrap_or(0)
702 }
703
704 pub fn initialized(&mut self, me: usize) {
706 self.programs[me].initialized = true;
707 self.programs[me].altered.clear();
708 }
709
710 pub fn find(&self, name: &str) -> Option<usize> {
711 if name.bytes().any(|b| b.is_ascii_lowercase()) {
712 return self.names.get(&name.to_ascii_uppercase()).copied();
713 }
714 self.names.get(name).copied()
715 }
716
717 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
719 let name = name.to_ascii_uppercase();
720 if let Some(i) = self.find(&name) {
721 return Ok(i);
722 }
723 let loaded = self.library.program(&name)?;
724 self.notify(Event::Load { program: &name, source: loaded.source.as_deref(), recorded: &loaded.recorded, facts: L::facts(&loaded.compiled) });
725 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
726 self.programs[index].source = loaded.source;
727 Ok(index)
728 }
729
730 pub const fn observed(&self) -> bool {
731 self.observer.is_some()
732 }
733
734 pub fn limit(&mut self, statements: Option<u64>, seconds: Option<u64>, storage: Option<u64>) {
739 self.statement_limit = statements;
740 self.deadline = seconds.and_then(|s| Some((Instant::now().checked_add(Duration::from_secs(s))?, s)));
741 self.storage_limit = storage.map(|b| usize::try_from(b).unwrap_or(usize::MAX));
742 }
743
744 pub const fn limited(&self) -> bool {
746 self.statement_limit.is_some() || self.deadline.is_some() || self.storage_limit.is_some()
747 }
748
749 pub fn start_statement(&mut self, program: usize, pos: Pos) -> Result<(), Abend> {
755 if let Some(limit) = self.storage_limit
756 && self.mem.len() > limit
757 {
758 return Err(Abend::ironwork(format!("the run unit's storage reached {} bytes, past its storage limit of {limit}", self.mem.len()), pos));
759 }
760 if let Some((deadline, seconds)) = self.deadline {
761 self.unclocked += 1;
762 if self.unclocked == CLOCK_EVERY {
763 self.unclocked = 0;
764 if Instant::now() >= deadline {
765 let unit = if seconds == 1 { "second" } else { "seconds" };
766 let message = format!("the run reached its time limit of {seconds} {unit}, as a step past its TIME= ends");
767 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message, pos, file: None });
768 }
769 }
770 }
771 let Some(left) = self.statement_limit.as_mut() else { return Ok(()) };
772 let now = Started { program, depth: self.depth, pos };
773 if *left > 0 {
774 *left -= 1;
775 if self.recent.len() == LOOP_WINDOW {
776 self.recent.pop_front();
777 }
778 self.recent.push_back(now);
779 return Ok(());
780 }
781 let overrun = self.overrun.get_or_insert_with(|| loop_of(&self.recent, now));
782 let message = "the run reached its statement limit, as a step past its TIME= ends";
783 if now == overrun.head && !overrun.lines.is_empty() {
784 const SHOWN: usize = 24;
785 let mut lines = overrun.lines.iter().take(SHOWN).map(u32::to_string).collect::<Vec<_>>().join(", ");
786 if overrun.lines.len() > SHOWN {
787 lines += &format!(" and {} more", overrun.lines.len() - SHOWN);
788 }
789 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: format!("{message}, in the loop over lines {lines}"), pos, file: None });
790 }
791 if overrun.grace == 0 {
792 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: message.into(), pos, file: None });
793 }
794 overrun.grace -= 1;
795 Ok(())
796 }
797
798 pub fn traces(&self, line: u32) -> bool {
799 match &self.statements {
800 None => false,
801 Some(StatementFilter::All) => self.observer.is_some(),
802 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
803 }
804 }
805
806 pub fn notify(&mut self, event: Event<'_>) {
807 if let Event::Sink { kind, .. } = &event {
808 debug_assert!(SINK_KINDS.contains(kind), "the sink kind {kind} is not in rt::unit::SINK_KINDS");
809 }
810 if let Some(observer) = self.observer.as_mut() {
811 observer(event);
812 }
813 }
814
815 pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
820 for program in &mut self.programs {
821 for f in program.files.iter_mut().filter_map(Option::take) {
822 end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
823 }
824 }
825 self.close_external_files(unclosed)
826 }
827
828 fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
830 let mut closed = Ok(());
831 for (name, &k) in &self.externals.file_names {
832 if let Some(f) = self.externals.files[k].take()
833 && let Err(e) = end_file(f, unclosed)
834 {
835 closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
836 }
837 }
838 closed
839 }
840
841 pub fn external(&mut self, name: &str, file: bool, size: usize, dialect: Dialect, program: &str) -> Result<usize, Abend> {
846 let key = (file, name.to_owned());
847 if let Some(&(at, had)) = self.externals.storage.get(&key) {
848 return if had == size {
849 Ok(at)
850 } else if size < had && !file && dialect == Dialect::Gnucobol {
851 let _ = writeln!(self.err, "ironwork: EXTERNAL record {name} has {had} bytes in the run unit, and this program describes {size}");
852 Ok(at)
853 } else {
854 let message = if file {
855 format!("IGZ0075S Inconsistencies were found in EXTERNAL file {name} in program {program}. The following file attributes did not match those of the established external file: the record length. ({had} bytes in the run unit, {size} here)")
856 } else {
857 format!("IGZ0066S The length of external data record {name} in program {program} did not match the existing length of the record. ({had} bytes in the run unit, {size} here)")
858 };
859 Err(Abend { code: crate::abend::AbendCode::user(4038), message, pos: Pos::default(), file: None })
860 };
861 }
862 let at = self.allocate(size);
863 self.externals.storage.insert(key, (at, size));
864 Ok(at)
865 }
866
867 pub fn set_switches(&mut self, on: [bool; 8]) {
870 for (n, on) in (0..).zip(on) {
871 let at = self.push_temporary(&[u8::from(on)]);
872 self.externals.storage.insert((false, switch_record(n)), (at, 1));
873 self.mark_input(at, 1, true);
874 }
875 }
876
877 pub fn external_file(&mut self, name: &str) -> Connector {
879 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
880 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
881 files.push(None);
882 locked.push(false);
883 files.len() - 1
884 });
885 Connector::External(k)
886 }
887
888 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
890 self.connectors.insert((me, k), to);
891 }
892
893 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
894 let mut to = None;
895 while let Some(&c) = self.connectors.get(&(me, k)) {
896 to = Some(c);
897 match c {
898 Connector::External(_) => break,
899 Connector::Program(p, j) => (me, k) = (p, j),
900 }
901 }
902 to
903 }
904
905 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
907 match self.connector(me, k) {
908 None => &mut self.programs[me].files[k],
909 Some(Connector::External(e)) => &mut self.externals.files[e],
910 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
911 }
912 }
913
914 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
916 match self.connector(me, k) {
917 None => &mut self.programs[me].locked[k],
918 Some(Connector::External(e)) => &mut self.externals.locked[e],
919 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
920 }
921 }
922
923 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
924 match self.connector(me, k) {
925 None => &self.programs[me].files[k],
926 Some(Connector::External(e)) => &self.externals.files[e],
927 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
928 }
929 }
930
931 pub fn return_code(&self) -> i16 {
932 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
933 }
934
935 pub fn now(&self) -> (i64, u32) {
937 match self.clock {
938 Clock::Fixed(s, h) => (s, h),
939 Clock::System => {
940 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
941 (d.as_secs() as i64, d.subsec_millis() / 10)
942 }
943 }
944 }
945}