1use crate::abend::Abend;
7use crate::files::{self, Dd, FileStatus, Format, KeySpan, Keyed, Keying, Move, Open, Record};
8use crate::host::{self, Host};
9use crate::linage::{Geometry, Motion, Page};
10use crate::lir::{Access, Carriage, Organization, Spacing, StartRel};
11use crate::printer::{self, Controls};
12use crate::storage::{Loc, Val};
13use crate::store::{self, ProgramFacts};
14use crate::unit::Event;
15use crate::vocab::{Closing, OpenMode, Pos};
16use numeric::VsamOpenFs;
17use numeric::precision::{Fixed, Places};
18use std::cmp::Ordering;
19use zarch::ebcdic;
20
21type R<T> = Result<T, Abend>;
22
23#[derive(Clone, Copy)]
27pub struct File<'a, P, X> {
28 pub index: usize,
29 pub name: &'a str,
30 pub assign: &'a str,
32 pub assign_item: Option<(P, Pos)>,
35 pub organization: Organization,
36 pub access: Access,
37 pub optional: bool,
38 pub format: Format,
40 pub status: Option<P>,
41 pub relative: Option<P>,
42 pub linage: Option<Linage<X>>,
43 pub carriage: Option<Carriage>,
44 pub area: (usize, usize),
46 pub read_lengths: (usize, usize),
49 pub depending: Option<Depending<P>>,
50}
51
52#[derive(Clone, Copy)]
55pub struct Depending<P> {
56 pub item: P,
57 pub lengths: (usize, usize),
58}
59
60#[derive(Clone, Copy)]
63pub struct Linage<X> {
64 pub lines: X,
65 pub footing: Option<X>,
66 pub top: Option<X>,
67 pub bottom: Option<X>,
68 pub counter: Option<Loc>,
69}
70
71#[derive(Clone, Copy)]
73pub struct Read<P> {
74 pub sequential: bool,
75 pub previous: bool,
76 pub into: Option<P>,
77 pub key: Option<P>,
78}
79
80#[derive(Clone, Copy)]
83pub enum Advance<'a, X> {
84 Lines { before: bool, count: X },
85 Page { before: bool },
86 Mnemonic { before: bool, space: Option<Spacing>, name: &'a str, environment: &'a str },
87}
88
89pub enum Outcome {
91 Done,
93 Status { status: FileStatus, at_end: bool },
96 Page { end_of_page: bool },
98 Failed(Failure),
100}
101
102pub struct Failure {
104 pub status: FileStatus,
105 pub mode: Option<OpenMode>,
106 pub message: String,
107}
108
109pub trait Files<P: Copy, X: Copy>: Host<P> {
111 fn slot(&mut self, k: usize) -> &mut Option<Open>;
113 fn locked(&mut self, k: usize) -> &mut bool;
115 fn dd(&self, assign: &str) -> Option<Dd>;
116 fn notify(&mut self, event: Event<'_>);
117 fn sink(&mut self, kind: &'static str, pos: Pos, operand: &str);
119 fn int(&mut self, value: X, pos: Pos) -> R<i64>;
120 fn keying(&mut self, k: usize, pos: Pos) -> R<Keying>;
122 fn key_value(&mut self, k: usize, keying: &Keying, key: P, partial: bool, pos: Pos) -> R<(usize, Vec<u8>)>;
125}
126
127pub fn set_status<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, status: FileStatus, pos: Pos) -> R<()> {
128 if let Some(p) = file.status {
129 let loc = receiver(x, p)?;
130 let bytes = x.facts().page().encode(status.as_str()).map_err(|e| Abend::ironwork(e.to_string(), pos))?;
131 x.assign(loc, Val::Bytes(bytes), None, pos)?;
132 }
133 Ok(())
134}
135
136fn receiver<P: Copy>(x: &mut impl Host<P>, p: P) -> R<Loc> {
139 let was = x.taint().is_some_and(|t| t.writing(true));
140 let loc = x.locate(p, false);
141 if let Some(t) = x.taint() {
142 t.writing(was);
143 }
144 loc
145}
146
147fn failed<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, status: FileStatus, message: String) -> Outcome {
149 let mode = x.slot(file.index).as_ref().map(|f| f.mode);
150 Outcome::Failed(Failure { status, mode, message })
151}
152
153pub fn sequential<P, X>(file: &File<'_, P, X>) -> bool {
154 file.access == Access::Sequential || file.organization == Organization::Sequential
155}
156
157pub fn adds_control_byte<P, X>(file: &File<'_, P, X>, format: Format) -> bool {
160 file.carriage.is_some_and(|c| !c.reserved) && format != Format::Text
161}
162
163pub fn dd_format<P: Copy, X: Copy>(x: &impl Files<P, X>, file: &File<'_, P, X>) -> Format {
165 x.dd(file.assign).and_then(|d| d.format).unwrap_or(file.format)
166}
167
168fn record_area<P: Copy>(x: &mut impl Host<P>, (offset, size): (usize, usize)) -> &[u8] {
169 &x.mem()[offset..offset + size]
170}
171
172fn read_prime_key<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, prime: &KeySpan) -> Vec<u8> {
174 if let Some(taint) = x.taint() {
175 taint.read(file.area.0 + prime.offset, prime.len);
176 }
177 prime.of(record_area(x, file.area))
178}
179
180fn relative_value<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<i64> {
181 let r = file.relative.ok_or_else(|| Abend::ironwork(format!("{} has no RELATIVE KEY", file.name), pos))?;
182 x.integer(r, pos)
183}
184
185fn relative_number<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<Option<Vec<u8>>> {
187 let n = relative_value(x, file, pos)?;
188 Ok((n >= 1).then(|| files::record_number(n as u64)))
189}
190
191fn relative_fits<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, n: u64) -> R<bool> {
194 let Some(r) = file.relative else { return Ok(true) };
195 let loc = x.locate(r, false)?;
196 Ok(match loc.kind.digits_scale() {
197 Some((digits, scale)) => digits.saturating_sub(scale) >= 19 || n < 10u64.pow(digits.saturating_sub(scale)),
198 None => true,
199 })
200}
201
202fn held<P: Copy, X: Copy, F: Files<P, X>, T>(x: &mut F, k: usize, op: impl FnOnce(&mut F, OpenMode, Format, &mut Keyed) -> R<T>) -> R<Option<T>> {
204 let Some(mut f) = x.slot(k).take() else { return Ok(None) };
205 let (mode, format) = (f.mode, f.format);
206 let result = match f.keyed() {
207 Some(keyed) => op(x, mode, format, keyed).map(Some),
208 None => Ok(None),
209 };
210 *x.slot(k) = Some(f);
211 result
212}
213
214fn is_held<P: Copy, X: Copy>(x: &mut impl Files<P, X>, k: usize) -> bool {
215 x.slot(k).as_ref().is_some_and(|f| f.is_keyed())
216}
217
218fn held_control_byte<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>) -> bool {
221 x.slot(file.index).as_ref().is_some_and(|f| f.is_keyed() && adds_control_byte(file, f.format))
222}
223
224fn record_bytes<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, format: Format) -> Vec<u8> {
226 let mut bytes = store::bytes(x.mem(), loc).to_vec();
227 if format != Format::Variable {
228 bytes.resize(file.area.1.max(bytes.len()), ebcdic::SPACE);
229 }
230 bytes
231}
232
233pub fn record_length<P: Copy, X: Copy>(x: &mut impl Host<P>, file: &File<'_, P, X>, loc: Loc, pos: Pos) -> R<Result<Loc, FileStatus>> {
237 let Some(d) = file.depending else { return Ok(Ok(loc)) };
238 let n = x.integer(d.item, pos)?;
239 let (shortest, longest) = d.lengths;
240 if n < shortest as i64 || n > longest.min(file.area.1) as i64 {
241 return Ok(Err(FileStatus::RecordLengthChanged));
242 }
243 Ok(Ok(Loc { offset: file.area.0, len: n as usize, ..loc }))
244}
245
246fn deliver<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, record: &[u8], variable: bool, into: Option<P>, pos: Pos) -> R<bool> {
250 let (offset, size) = file.area;
251 let n = record.len().min(size);
252 let mem = x.mem();
253 mem[offset..offset + n].copy_from_slice(&record[..n]);
254 if !variable {
255 mem[offset + n..offset + size].fill(ebcdic::SPACE);
256 }
257 if let Some(t) = x.taint() {
258 t.set(offset, n, true);
259 t.set(offset + n, if variable { 0 } else { size - n }, false);
260 }
261 if let Some(d) = file.depending {
262 host::set_integer(x, d.item, n as i64, pos)?;
263 }
264 if let Some(r) = into {
265 let dest = x.locate(r, true)?;
266 let moved = if variable { n } else { size };
267 if let Some(t) = x.taint() {
268 t.read(offset, moved);
269 }
270 let bytes = x.mem()[offset..offset + moved].to_vec();
271 x.assign(dest, Val::Bytes(bytes), None, pos)?;
272 }
273 Ok(record.len() > size)
274}
275
276fn length_conflict<P, X>(file: &File<'_, P, X>, len: usize, variable: bool, long: bool) -> bool {
280 let (shortest, longest) = file.read_lengths;
281 long || variable && !(shortest..=longest).contains(&len)
282}
283
284pub fn open<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, mode: OpenMode, pos: Pos) -> R<Outcome> {
289 let assigned = match file.assign_item {
290 Some((item, select)) => Some(assigned_dd(x, item, select)?),
291 None => None,
292 };
293 let closed = x.slot(file.index).is_none();
294 let outcome = open_on(x, file, mode, pos, assigned.as_ref())?;
295 if let Some((dd, _)) = assigned
296 && closed
297 && let Some(f) = x.slot(file.index).as_mut()
298 {
299 f.assigned = Some(dd);
300 }
301 Ok(outcome)
302}
303
304fn assigned_dd<P: Copy, X: Copy>(x: &mut impl Files<P, X>, item: P, select: Pos) -> R<(String, String)> {
311 let read_before = x.taint().map(|t| {
312 let pending = t.pending();
313 t.start_statement();
314 pending
315 });
316 let loc = x.locate(item, false)?;
317 if let Some(taint) = x.taint() {
318 taint.read(loc.offset, loc.len);
319 }
320 let bytes = x.mem()[loc.offset..loc.offset + loc.len].to_vec();
321 let value = x.facts().page().decode(&bytes).trim().to_string();
322 x.sink("dynamic-file-path", select, &value);
323 if let (Some(before), Some(taint)) = (read_before, x.taint()) {
324 taint.resume_statement(before);
325 }
326 let named = value.len() <= 8 && value.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || "@#$".contains(c)) && value.chars().all(|c| c.is_ascii_alphanumeric() || "@#$".contains(c));
327 Ok((if named { value.to_ascii_uppercase() } else { String::new() }, value))
328}
329
330fn open_on<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, mode: OpenMode, pos: Pos, assigned: Option<&(String, String)>) -> R<Outcome> {
331 let (k, name) = (file.index, file.name);
332 let assign = assigned.map_or(file.assign, |(dd, _)| dd.as_str());
333 let failure = |status, message| Ok(Outcome::Failed(Failure { status, mode: Some(mode), message }));
334 if *x.locked(k) {
335 return failure(FileStatus::ClosedWithLock, format!("{name} was closed WITH LOCK"));
336 }
337 if x.slot(k).is_some() {
338 return failure(FileStatus::AlreadyOpen, format!("{name} is already open"));
339 }
340 let page = match (&file.linage, mode) {
341 (Some(_), OpenMode::Output | OpenMode::Extend) => Some(Page::opened(geometry(x, file, pos)?)),
342 _ => None,
343 };
344 let default = file.format;
345 let dd = if assign.is_empty() { None } else { x.dd(assign) };
346 if let Some(d) = &dd {
347 x.notify(Event::Open { dd: assign, mode, path: &d.path });
348 }
349 let no_dd = match assigned {
350 Some((dd, value)) if dd.is_empty() => format!("{name}: ASSIGN's data item holds {value:?}, which is not a DD name; a run opens only the files its DDs give"),
351 _ => format!("{name}: no DD {assign} was given (--dd {assign}=path)"),
352 };
353 let held = match file.organization {
354 Organization::Indexed | Organization::Relative => true,
355 Organization::Sequential => mode == OpenMode::InputOutput,
356 Organization::LineSequential => false,
357 };
358 if held {
359 let status = match &dd {
360 Some(d) if mode == OpenMode::Output || d.path.exists() => FileStatus::Success,
361 _ if file.optional && mode != OpenMode::Output => FileStatus::SuccessOptional,
362 None => return failure(FileStatus::FileNotFound, no_dd),
363 Some(d) => return failure(FileStatus::FileNotFound, format!("{name}: {}: no such file", d.path.display())),
364 };
365 let keying = x.keying(k, pos)?;
366 let format = dd.as_ref().and_then(|d| d.format).unwrap_or(default);
367 let record_len = file.area.1 + usize::from(adds_control_byte(file, format));
368 let vsam = dd.as_ref().filter(|_| matches!(file.organization, Organization::Indexed | Organization::Relative));
369 let verified = vsam.is_some_and(|d| files::left_open(&d.path));
370 let mut f = match files::open_keyed(dd.as_ref(), mode, format, keying, record_len, x.facts().page()) {
371 Ok(f) => f,
372 Err(e) => return failure(FileStatus::PermanentError, format!("{name}: {e}")),
373 };
374 if let Some(d) = vsam.filter(|_| mode != OpenMode::Input)
375 && let Err(e) = f.mark_open(&d.path)
376 {
377 return failure(FileStatus::PermanentError, format!("{name}: {}: {e}", files::open_mark(&d.path).display()));
378 }
379 let status = match x.facts().options().vsamopenfs {
380 _ if !verified => status,
381 VsamOpenFs::Compat => FileStatus::SuccessVerified,
382 VsamOpenFs::Succ => FileStatus::Success,
383 };
384 opened(x, file, f, status, pos)?;
385 return Ok(match (status, vsam) {
386 (FileStatus::SuccessVerified, Some(d)) => failed(x, file, status, format!("{name}: {} was left open for output, and OPEN verified it", d.path.display())),
387 _ => Outcome::Done,
388 });
389 }
390 match dd {
391 _ if file.optional && mode == OpenMode::Input && dd.as_ref().is_none_or(|d| !d.path.exists()) => opened(x, file, files::absent(), FileStatus::SuccessOptional, pos),
392 None => failure(FileStatus::FileNotFound, no_dd),
393 Some(dd) if mode == OpenMode::Extend && !file.optional && !dd.path.exists() => failure(FileStatus::FileNotFound, format!("{name}: {}: no such file", dd.path.display())),
394 Some(dd) => {
395 let created = mode == OpenMode::Extend && !dd.path.exists();
396 match files::open(&dd, mode, dd.format.unwrap_or(default)) {
397 Ok(mut f) => {
398 f.page = page;
399 opened(x, file, f, if created { FileStatus::SuccessOptional } else { FileStatus::Success }, pos)
400 }
401 Err(e) => {
402 let status = match e.kind() {
403 std::io::ErrorKind::NotFound => FileStatus::FileNotFound,
404 std::io::ErrorKind::Unsupported => FileStatus::OpenModeUnsupported,
405 _ => FileStatus::PermanentError,
406 };
407 failure(status, format!("{name}: {}: {e}", dd.path.display()))
408 }
409 }
410 }
411 }
412}
413
414fn opened<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, f: Open, status: FileStatus, pos: Pos) -> R<Outcome> {
417 *x.slot(file.index) = Some(f);
418 set_linage_counter(x, file, 1, pos)?;
419 set_status(x, file, status, pos)?;
420 Ok(Outcome::Done)
421}
422
423fn geometry<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<Geometry> {
425 let Some(linage) = file.linage else { return Err(Abend::ironwork(format!("{} has no LINAGE clause", file.name), pos)) };
426 let body = x.int(linage.lines, pos)?;
427 let footing = match linage.footing {
428 Some(v) => Some(x.int(v, pos)?),
429 None => None,
430 };
431 let top = match linage.top {
432 Some(v) => x.int(v, pos)?,
433 None => 0,
434 };
435 let bottom = match linage.bottom {
436 Some(v) => x.int(v, pos)?,
437 None => 0,
438 };
439 Geometry::new(body, footing, top, bottom).map_err(|why| Abend::ironwork(format!("{}: {why} ({})", file.name, numeric::assumptions::LINAGE_VALUES), pos))
440}
441
442fn set_linage_counter<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, value: u64, pos: Pos) -> R<()> {
443 let Some(loc) = file.linage.and_then(|l| l.counter) else { return Ok(()) };
444 x.store_fixed(loc, &Fixed::new(value as i128, Places::new(19, 0)), pos)
445}
446
447pub fn close<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, closing: Option<Closing>, pos: Pos) -> R<Outcome> {
452 let name = file.name;
453 if closing == Some(Closing::Volume) && x.slot(file.index).is_some() {
454 set_status(x, file, FileStatus::SuccessNonReel, pos)?;
455 return Ok(Outcome::Done);
456 }
457 match x.slot(file.index).take() {
458 None => Ok(failed(x, file, FileStatus::NotOpen, format!("{name} is not open"))),
459 Some(f) => {
460 let (mode, assigned) = (Some(f.mode), f.assigned.clone());
461 match f.close() {
462 Ok(()) => {
463 let assign = assigned.as_deref().unwrap_or(file.assign);
464 if !assign.is_empty()
465 && let Some(d) = x.dd(assign)
466 {
467 x.notify(Event::Close { dd: assign, path: &d.path });
468 }
469 *x.locked(file.index) |= closing == Some(Closing::Lock);
470 let status = if closing == Some(Closing::NoRewind) { FileStatus::SuccessNonReel } else { FileStatus::Success };
471 set_status(x, file, status, pos)?;
472 Ok(Outcome::Done)
473 }
474 Err(e) => Ok(Outcome::Failed(Failure { status: FileStatus::PermanentError, mode, message: format!("{name}: {e}") })),
475 }
476 }
477 }
478}
479
480pub fn read<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, r: Read<P>, pos: Pos) -> R<Outcome> {
482 let k = file.index;
483 if !is_held(x, k) {
484 return read_stream(x, file, r.into, pos);
485 }
486 let sequential = r.sequential;
487 let added = held_control_byte(x, file);
488 let (status, found, variable) = held(x, k, |x, mode, format, keyed| {
489 let variable = format == Format::Variable;
490 if !matches!(mode, OpenMode::Input | OpenMode::InputOutput) {
491 return Ok((FileStatus::NotOpenInput, None, variable));
492 }
493 if sequential {
494 return Ok(match keyed.read_next(r.previous) {
495 Err(status) => (status, None, variable),
496 Ok(None) => (FileStatus::AtEnd, None, variable),
497 Ok(Some(found)) if keyed.keying == Keying::Relative && !relative_fits(x, file, files::number_of(&found.key))? => (FileStatus::RelativeKeyOverflow, None, variable),
498 Ok(Some(found)) => (if found.duplicate { FileStatus::SuccessDuplicate } else { FileStatus::Success }, Some(found), variable),
499 });
500 }
501 let keying = keyed.keying.clone();
502 let (which, value) = match (&keying, r.key) {
503 (Keying::Indexed { .. }, Some(key)) => x.key_value(k, &keying, key, false, pos)?,
504 (Keying::Indexed { prime, .. }, None) => (0, read_prime_key(x, file, prime)),
505 _ => match relative_number(x, file, pos)? {
506 Some(key) => (0, key),
507 None => {
508 keyed.lose_position();
509 return Ok((FileStatus::NotFound, None, variable));
510 }
511 },
512 };
513 Ok(match keyed.get(which, &value) {
514 None => {
515 keyed.lose_position();
516 (FileStatus::NotFound, None, variable)
517 }
518 Some(found) => {
519 keyed.read_at(which, &found.key);
520 (if found.duplicate { FileStatus::SuccessDuplicate } else { FileStatus::Success }, Some(found), variable)
521 }
522 })
523 })?
524 .unwrap_or((FileStatus::NotOpenInput, None, false));
525 let mut status = status;
526 if let Some(found) = found {
527 if sequential && file.organization == Organization::Relative
528 && let Some(rk) = file.relative
529 {
530 host::set_integer(x, rk, files::number_of(&found.key) as i64, pos)?;
531 }
532 let record = found.record.get(usize::from(added)..).unwrap_or_default();
533 let long = deliver(x, file, record, variable, r.into, pos)?;
534 if length_conflict(file, record.len(), variable, long) && status == FileStatus::Success {
535 status = FileStatus::SuccessWrongLength;
536 }
537 }
538 Ok(Outcome::Status { status, at_end: sequential })
539}
540
541fn read_stream<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, into: Option<P>, pos: Pos) -> R<Outcome> {
542 let k = file.index;
543 let at_end = |status| Ok(Outcome::Status { status, at_end: true });
544 let size = file.area.1;
545 let Some(mut f) = x.slot(k).take() else {
546 return at_end(FileStatus::NotOpenInput);
547 };
548 let added = adds_control_byte(file, f.format);
549 let read = f.read(size + usize::from(added));
550 let format = f.format;
551 let input = f.mode == OpenMode::Input;
552 *x.slot(k) = Some(f);
553 if !input {
554 return at_end(FileStatus::NotOpenInput);
555 }
556 let (record, wrong_length) = match read {
557 Err(e) => return Ok(failed(x, file, FileStatus::PermanentError, format!("READ {}: {e}", file.name))),
558 Ok(Record::End) => return at_end(FileStatus::AtEnd),
559 Ok(Record::Data(bytes)) => (bytes, false),
560 Ok(Record::WrongLength(bytes)) => (bytes, true),
561 };
562 let record = if added { record.get(1..).unwrap_or_default().to_vec() } else { record };
563 let record = if format == Format::Text {
564 let page = x.facts().page();
565 let unknown = page.encode_char('?').unwrap_or(0x6F);
566 String::from_utf8_lossy(&record).chars().map(|c| page.encode_char(c).unwrap_or(unknown)).collect()
567 } else {
568 record
569 };
570 let variable = format == Format::Variable;
571 let long = deliver(x, file, &record, variable, into, pos)?;
572 at_end(if wrong_length || length_conflict(file, record.len(), variable, long) { FileStatus::SuccessWrongLength } else { FileStatus::Success })
573}
574
575pub fn write<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, advancing: Option<Advance<'_, X>>, pos: Pos) -> R<Outcome> {
577 let k = file.index;
578 let loc = match record_length(x, file, loc, pos)? {
579 Ok(loc) => loc,
580 Err(status) => return Ok(Outcome::Status { status, at_end: false }),
581 };
582 if paged(x, k) {
583 return write_page(x, file, loc, advancing, pos);
584 }
585 if !is_held(x, k) {
586 let (before, space) = match advancing {
587 Some(a) => advance(x, a, pos)?,
588 None => (false, Spacing::Lines(1)),
589 };
590 return write_stream(x, file, loc, before, space, pos);
591 }
592 let sequential = sequential(file);
593 let status = held(x, k, |x, mode, format, keyed| {
594 let allowed = match mode {
595 OpenMode::Output => true,
596 OpenMode::Extend => sequential,
597 OpenMode::InputOutput => !sequential,
598 OpenMode::Input => false,
599 };
600 if !allowed || keyed.keying == Keying::Position {
601 return Ok(FileStatus::NotOpenOutput);
602 }
603 keyed.last_read = None;
604 let bytes = record_bytes(x, file, loc, format);
605 let key = match &keyed.keying {
606 Keying::Indexed { prime, .. } => {
607 let key = prime.of(&bytes);
608 if sequential && keyed.highest_key().is_some_and(|h| key <= *h) {
609 return Ok(FileStatus::SequenceError);
610 }
611 key
612 }
613 _ if sequential => {
614 let n = keyed.highest_key().map_or(0, |h| files::number_of(h)) + 1;
615 if n > files::MAX_RELATIVE || !relative_fits(x, file, n)? {
616 return Ok(FileStatus::BoundaryViolation);
617 }
618 if let Some(rk) = file.relative {
619 host::set_integer(x, rk, n as i64, pos)?;
620 }
621 files::record_number(n)
622 }
623 _ => match relative_number(x, file, pos)? {
624 Some(key) if files::number_of(&key) <= files::MAX_RELATIVE => key,
625 _ => return Ok(FileStatus::BoundaryViolation),
626 },
627 };
628 Ok(match keyed.insert(key, bytes) {
629 Err(status) => status,
630 Ok(true) => FileStatus::SuccessDuplicate,
631 Ok(false) => FileStatus::Success,
632 })
633 })?
634 .unwrap_or(FileStatus::NotOpenOutput);
635 Ok(Outcome::Status { status, at_end: false })
636}
637
638fn paged<P: Copy, X: Copy>(x: &mut impl Files<P, X>, k: usize) -> bool {
639 x.slot(k).as_ref().is_some_and(|f| f.page.is_some())
640}
641
642fn write_page<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, advancing: Option<Advance<'_, X>>, pos: Pos) -> R<Outcome> {
646 let k = file.index;
647 let (before, motion) = match advancing {
648 None => (false, Motion::Lines(1)),
649 Some(Advance::Lines { before, count }) => (before, Motion::Lines(x.int(count, pos)?.max(0) as u64)),
650 Some(Advance::Page { before }) => (before, Motion::Page),
651 Some(Advance::Mnemonic { name, .. }) => {
652 return Err(Abend::ironwork(format!("ADVANCING {name} on {}, whose FD has LINAGE, is not supported yet", file.name), pos));
653 }
654 };
655 let Some(mut page) = x.slot(k).as_ref().and_then(|f| f.page) else { return Ok(Outcome::Done) };
656 let step = page.write(before, motion, || geometry(x, file, pos))?;
657 let controls = file.carriage.map(|c| printer::moving(c.machine, step.ahead, step.behind));
658 let text = (Some(Move::Lines(step.ahead)), before.then_some(Move::Lines(step.behind)));
659 if let failure @ Outcome::Failed(_) = put_line(x, file, loc, controls, text, pos)? {
660 return Ok(failure);
661 }
662 if let Some(f) = x.slot(k).as_mut() {
663 f.page = Some(page);
664 }
665 set_linage_counter(x, file, page.counter, pos)?;
666 Ok(Outcome::Page { end_of_page: step.end_of_page })
667}
668
669fn advance<P: Copy, X: Copy>(x: &mut impl Files<P, X>, a: Advance<'_, X>, pos: Pos) -> R<(bool, Spacing)> {
672 Ok(match a {
673 Advance::Lines { before, count } => (before, Spacing::Lines(x.int(count, pos)?.max(0) as u64)),
674 Advance::Page { before } => (before, Spacing::Channel(1)),
675 Advance::Mnemonic { before, space, name, environment } => {
676 let space = space.ok_or_else(|| Abend::ironwork(format!("ADVANCING {name}: {environment} is not a printer channel"), pos))?;
677 (before, space)
678 }
679 })
680}
681
682pub fn write_stream<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, before: bool, space: Spacing, pos: Pos) -> R<Outcome> {
685 let controls = file.carriage.map(|c| printer::controls(c.machine, before, space));
686 put_line(x, file, loc, controls, printer::text_motion(before, space), pos)
687}
688
689fn put_line<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, controls: Option<Controls>, text: (Option<Move>, Option<Move>), pos: Pos) -> R<Outcome> {
692 let (k, name) = (file.index, file.name);
693 let Some(mut f) = x.slot(k).take() else {
694 return Ok(failed(x, file, FileStatus::NotOpenOutput, format!("WRITE {name}: {}", FileStatus::NotOpenOutput.meaning())));
695 };
696 if f.mode == OpenMode::Input {
697 *x.slot(k) = Some(f);
698 return Ok(failed(x, file, FileStatus::NotOpenOutput, format!("WRITE {name}: {}", FileStatus::NotOpenOutput.meaning())));
699 }
700 let reserved = usize::from(file.carriage.is_some_and(|c| c.reserved));
701 if let Some(c) = controls.filter(|_| reserved == 1 && loc.len > 0) {
702 x.mem()[loc.offset] = c.data;
703 host::mark(x, loc.offset, 1);
704 }
705 let bytes = record_bytes(x, file, loc, f.format);
706 let written = match (f.format, controls) {
707 (Format::Text, _) => {
708 let line = x.facts().page().decode(bytes.get(reserved..).unwrap_or_default()).trim_end().to_owned();
709 f.print(text.0, &line, text.1)
710 }
711 (_, None) => f.write(&bytes),
712 (_, Some(c)) => c.records().try_for_each(|(control, line)| {
713 let mut record = Vec::with_capacity(bytes.len() + 1);
714 record.push(control);
715 if line {
716 record.extend_from_slice(&bytes[reserved.min(bytes.len())..]);
717 } else {
718 record.resize(bytes.len() + 1 - reserved.min(bytes.len()), ebcdic::SPACE);
719 }
720 f.write(&record)
721 }),
722 };
723 *x.slot(k) = Some(f);
724 match written {
725 Ok(()) => set_status(x, file, FileStatus::Success, pos).map(|()| Outcome::Done),
726 Err(e) => Ok(failed(x, file, FileStatus::PermanentError, format!("WRITE {name}: {e}"))),
727 }
728}
729
730pub fn rewrite<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, pos: Pos) -> R<FileStatus> {
732 let loc = match record_length(x, file, loc, pos)? {
733 Ok(loc) => loc,
734 Err(status) => return Ok(status),
735 };
736 let sequential = sequential(file);
737 let added = held_control_byte(x, file);
738 Ok(held(x, file.index, |x, mode, format, keyed| {
739 if mode != OpenMode::InputOutput {
740 return Ok(FileStatus::NotOpenInputOutput);
741 }
742 let mut bytes = record_bytes(x, file, loc, format);
743 if added {
744 let control = keyed.last_read.as_ref().and_then(|key| keyed.record(key)).and_then(|old| old.first().copied()).unwrap_or(ebcdic::SPACE);
745 bytes.insert(0, control);
746 }
747 let prior = keyed.last_read.take();
748 let key = if sequential {
749 let Some(prior) = prior else { return Ok(FileStatus::NoPriorRead) };
750 if keyed.prime_key(&bytes).is_some_and(|key| key != prior) {
751 return Ok(FileStatus::SequenceError);
752 }
753 prior
754 } else {
755 match keyed.prime_key(&bytes) {
756 Some(key) => key,
757 None => match relative_number(x, file, pos)? {
758 Some(key) => key,
759 None => return Ok(FileStatus::NotFound),
760 },
761 }
762 };
763 if keyed.keying == Keying::Position && keyed.record(&key).is_some_and(|old| old.len() != bytes.len()) {
764 return Ok(FileStatus::RecordLengthChanged);
765 }
766 Ok(match keyed.replace(key, bytes) {
767 Err(status) => status,
768 Ok(true) => FileStatus::SuccessDuplicate,
769 Ok(false) => FileStatus::Success,
770 })
771 })?
772 .unwrap_or(FileStatus::NotOpenInputOutput))
773}
774
775pub fn delete<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<FileStatus> {
777 let sequential = sequential(file);
778 Ok(held(x, file.index, |x, mode, _, keyed| {
779 if mode != OpenMode::InputOutput || keyed.keying == Keying::Position {
780 return Ok(FileStatus::NotOpenInputOutput);
781 }
782 let prior = keyed.last_read.take();
783 let key = match (&keyed.keying, sequential) {
784 (_, true) => match prior {
785 Some(key) => key,
786 None => return Ok(FileStatus::NoPriorRead),
787 },
788 (Keying::Indexed { prime, .. }, false) => read_prime_key(x, file, prime),
789 _ => match relative_number(x, file, pos)? {
790 Some(key) => key,
791 None => return Ok(FileStatus::NotFound),
792 },
793 };
794 Ok(if keyed.remove(&key).is_some() { FileStatus::Success } else { FileStatus::NotFound })
795 })?
796 .unwrap_or(FileStatus::NotOpenInputOutput))
797}
798
799pub fn start<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, rel: StartRel, key: Option<P>, pos: Pos) -> R<FileStatus> {
801 let (wanted, or_equal) = match rel {
802 StartRel::Equal => (Ordering::Equal, false),
803 StartRel::Greater => (Ordering::Greater, false),
804 StartRel::NotLess => (Ordering::Greater, true),
805 };
806 let k = file.index;
807 Ok(held(x, k, |x, mode, _, keyed| {
808 if !matches!(mode, OpenMode::Input | OpenMode::InputOutput) || keyed.keying == Keying::Position {
809 return Ok(FileStatus::NotOpenInput);
810 }
811 let keying = keyed.keying.clone();
812 let (which, value) = match (&keying, key) {
813 (Keying::Indexed { .. }, Some(r)) => x.key_value(k, &keying, r, true, pos)?,
814 (Keying::Indexed { prime, .. }, None) => (0, read_prime_key(x, file, prime)),
815 (_, Some(r)) => (0, files::record_number(x.integer(r, pos)?.max(0) as u64)),
816 (_, None) => (0, files::record_number(relative_value(x, file, pos)?.max(0) as u64)),
817 };
818 Ok(if keyed.start(which, wanted, or_equal, &value) { FileStatus::Success } else { FileStatus::NotFound })
819 })?
820 .unwrap_or(FileStatus::NotOpenInput))
821}