1
0
mirror of https://github.com/simh/simh.git synced 2026-01-11 23:52:58 +00:00
simh.simh/sim_tape.c
J. David Bryan 13c9ca5bec TAPE: Added extended SIMH format support
- Improved tape_erase_fwd corrupt image error checking
- Added sim_tape_erase global, tape_erase internal functions
2022-06-16 21:05:23 -07:00

2276 lines
111 KiB
C

/* sim_tape.c: simulator tape support library
Copyright (c) 1993-2021, Robert M Supnik
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Robert M Supnik shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Robert M Supnik.
Ultimately, this will be a place to hide processing of various tape formats,
as well as OS-specific direct hardware access.
15-Dec-21 JDB Added extended SIMH format support
10-Oct-21 JDB Improved tape_erase_fwd corrupt image error checking
06-Oct-21 JDB Added sim_tape_erase global, tape_erase internal functions
27-Dec-18 JDB Added missing fall through comment in sim_tape_wrrecf
03-May-17 JDB Added support for erasing tape marks to sim_tape_errec[fr]
02-May-17 JDB Added error checks to sim_fseek calls
18-Jul-16 JDB Added sim_tape_errecf, sim_tape_errecr functions
12-Oct-15 JDB Fixed bug in sim_tape_rdlntf if gap buffer read ends at EOM
15-Dec-14 JDB Changed sim_tape_set_dens to check validity of density change
04-Nov-14 JDB Restrict sim_tape_set_fmt to unit unattached
31-Oct-14 JDB Fixed gap skip on reverse read
Fixed write EOM bug (should not update position)
Added set/show density functions, changed sim_tape_wrgap API
Buffered forward/reverse gap skip to improve execution time
22-Sep-14 JDB Added tape runaway support
08-Jun-08 JDB Fixed signed/unsigned warning in sim_tape_set_fmt
23-Jan-07 JDB Fixed backspace over gap at BOT
22-Jan-07 RMS Fixed bug in P7B format read reclnt rev (found by Rich Cornwell)
15-Dec-06 RMS Added support for small capacity tapes
30-Aug-06 JDB Added erase gap support
14-Feb-06 RMS Added variable tape capacity
23-Jan-06 JDB Fixed odd-byte-write problem in sim_tape_wrrecf
17-Dec-05 RMS Added write support for Paul Pierce 7b format
16-Aug-05 RMS Fixed C++ declaration and cast problems
02-May-05 RMS Added support for Pierce 7b format
28-Jul-04 RMS Fixed bug in writing error records (found by Dave Bryan)
RMS Fixed incorrect error codes (found by Dave Bryan)
05-Jan-04 RMS Revised for file I/O library
25-Apr-03 RMS Added extended file support
28-Mar-03 RMS Added E11 and TPC format support
The tape formats and functions implemented herein are described in separate
monographs titled, "SIMH Magtape Representation and Handling" and "Writing a
Simulator for the SIMH System," respectively.
Public routines:
sim_tape_attach attach tape unit
sim_tape_detach detach tape unit
sim_tape_rdrecf read tape record forward
sim_tape_rdrecr read tape record reverse
sim_tape_wrrecf write tape record forward
sim_tape_sprecf space tape record forward
sim_tape_sprecr space tape record reverse
sim_tape_wrmrk write private marker
sim_tape_wrtmk write tape mark
sim_tape_wreom erase remainder of tape
sim_tape_wrgap write erase gap
sim_tape_errecf erase record forward
sim_tape_errecr erase record reverse
sim_tape_erase erase a specified number of bytes
sim_tape_rewind rewind
sim_tape_reset reset unit
sim_tape_bot TRUE if at beginning of tape
sim_tape_eot TRUE if at or beyond end of tape
sim_tape_wrp TRUE if write protected
sim_tape_set_fmt set tape format
sim_tape_show_fmt show tape format
sim_tape_set_capac set tape capacity
sim_tape_show_capac show tape capacity
sim_tape_set_dens set tape density
sim_tape_show_dens show tape density
*/
#include "sim_defs.h"
#include "sim_tape.h"
struct sim_tape_fmt {
char *name; /* name */
int32 uflags; /* unit flags */
t_addr bot; /* bot test */
};
static const struct sim_tape_fmt fmts [] = { /* format table, indexed by MTUF_F number */
/* name uflags bot */
/* ------- ------------------- --------------------- */
{ "SIMH", MT_F_STD, sizeof (t_mtrlnt) - 1 }, /* 0 = MTUF_F_STD */
{ "E11", MT_F_E11, sizeof (t_mtrlnt) - 1 }, /* 1 = MTUF_F_E11 */
{ "TPC", MT_F_TPC | UNIT_RO, sizeof (t_tpclnt) - 1 }, /* 2 = MTUF_F_TPC */
{ "P7B", MT_F_P7B, 0 }, /* 3 = MTUF_F_P7B */
{ " ", MT_F_TDF | UNIT_RO, 0 }, /* 4 = MTUF_F_TDF (not implemented) */
{ "SIMH", MT_F_EXT, sizeof (t_mtrlnt) - 1 } /* 5 = MTUF_F_EXT */
};
#define FMT_COUNT (sizeof fmts / sizeof fmts [0]) /* count of format table entries */
static const uint32 bpi [] = { /* tape density table, indexed by MT_DENS constants */
0, /* 0 = MT_DENS_NONE -- density not set */
200, /* 1 = MT_DENS_200 -- 200 bpi NRZI */
556, /* 2 = MT_DENS_556 -- 556 bpi NRZI */
800, /* 3 = MT_DENS_800 -- 800 bpi NRZI */
1600, /* 4 = MT_DENS_1600 -- 1600 bpi PE */
6250 /* 5 = MT_DENS_6250 -- 6250 bpi GCR */
};
#define BPI_COUNT (sizeof (bpi) / sizeof (bpi [0])) /* count of density table entries */
static t_stat sim_tape_ioerr (UNIT *uptr);
static t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat);
static uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map);
static t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map);
static t_stat tape_read (UNIT *uptr, uint8 *buffer, t_mtrlnt *class_count, t_mtrlnt bufsize, t_bool reverse);
static t_stat tape_erase (UNIT *uptr, t_mtrlnt byte_count);
static t_stat tape_erase_fwd (UNIT *uptr, t_mtrlnt gap_size);
static t_stat tape_erase_rev (UNIT *uptr, t_mtrlnt gap_size);
/* Attach tape unit */
t_stat sim_tape_attach (UNIT *uptr, char *cptr)
{
uint32 objc;
char gbuf[CBUFSIZE];
t_stat r;
if (sim_switches & SWMASK ('F')) { /* format spec? */
cptr = get_glyph (cptr, gbuf, 0); /* get spec */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
if (sim_tape_set_fmt (uptr, 0, gbuf, NULL) != SCPE_OK)
return SCPE_ARG;
}
r = attach_unit (uptr, cptr); /* attach unit */
if (r != SCPE_OK) /* error? */
return r;
switch (MT_GET_FMT (uptr)) { /* case on format */
case MTUF_F_TPC: /* TPC */
objc = sim_tape_tpc_map (uptr, NULL); /* get # objects */
if (objc == 0) { /* tape empty? */
sim_tape_detach (uptr);
return SCPE_FMT; /* yes, complain */
}
uptr->filebuf = calloc (objc + 1, sizeof (t_addr));
if (uptr->filebuf == NULL) { /* map allocated? */
sim_tape_detach (uptr);
return SCPE_MEM; /* no, complain */
}
uptr->hwmark = objc + 1; /* save map size */
sim_tape_tpc_map (uptr, (t_addr *) uptr->filebuf); /* fill map */
break;
default:
break;
}
sim_tape_rewind (uptr);
return SCPE_OK;
}
/* Detach tape unit */
t_stat sim_tape_detach (UNIT *uptr)
{
uint32 f = MT_GET_FMT (uptr);
t_stat r;
r = detach_unit (uptr); /* detach unit */
if (r != SCPE_OK)
return r;
switch (f) { /* case on format */
case MTUF_F_TPC: /* TPC */
if (uptr->filebuf) /* free map */
free (uptr->filebuf);
uptr->filebuf = NULL;
uptr->hwmark = 0;
break;
default:
break;
}
sim_tape_rewind (uptr);
return SCPE_OK;
}
/* Read record length forward (internal routine).
Inputs:
uptr = pointer to tape unit
bc = pointer to returned record length
Outputs:
status = operation status
exit condition tape position
------------------ -----------------------------------------------------
unit unattached unchanged
read error unchanged, PNU set if initial read
end of file/medium updated if a gap precedes, else unchanged and PNU set
tape mark updated
other marker updated
tape runaway updated
data record updated, sim_fread will read record forward
This routine is called to set up a record read or spacing in the forward
direction. On return, status is MTSE_OK if a data record, private marker, or
reserved marker was read, or an MTSE error code if a standard marker (e.g.
tape mark) was read, or an error occurred. The file is positioned at the
first byte of a data record, after a tape mark or private marker, or
otherwise as indicated above. In all cases, the successfully read marker or
data record length word is returned via the "bc" pointer.
When the extended SIMH format is enabled, then the variable addressed by the
"bc" parameter must be set on entry to a bitmap of the object classes to
return. Each of the classes is represented by its corresponding bit, i.e.,
bit 0 represents class 0, bit 1 for class 1, etc. The routine will return
only objects from the selected classes. Unselected class objects will be
ignored by skipping over them until the first selected class object is seen.
This allows a simulator to declare those classes it understands (e.g.,
standard classes 0 and 8, plus private classes 2 and 7) and those classes it
wishes to ignore. Erase gap markers are always skipped, and standard markers
are always returned, so specifying an empty bitmap will perform the
equivalent of a "space file forward," returning only when a tape mark or
EOM/EOF is encountered.
When standard SIMH format is enabled, standard classes 0 and 8 are
automatically selected, and the entry value addressed by "bc" is ignored.
The ANSI standards for magnetic tape recording (X3.22, X3.39, and X3.54) and
the equivalent ECMA standard (ECMA-62) specify a maximum erase gap length of
25 feet (7.6 meters). While gaps of any length may be written, gaps longer
than this are non-standard and may indicate that an unrecorded or erased tape
is being read.
If the tape density has been set via a previous "sim_tape_set_dens" call,
then the length is monitored when skipping over erase gaps. If the length
reaches 25 feet, motion is terminated, and MTSE_RUNAWAY status is returned.
Runaway status is also returned if an end-of-medium marker or the physical
end of file is encountered while spacing over a gap; however, MTSE_EOM is
returned if the tape is positioned at the EOM or EOF on entry.
If the density has not been set, then a gap of any length is skipped, and
MTSE_RUNAWAY status is never returned. In effect, erase gaps present in the
tape image file will be transparent to the caller.
Erase gaps are currently supported only in standard and extended SIMH tape
formats. Because gaps may be partially overwritten with data records, gap
metadata must be examined marker-by-marker. To reduce the number of file
read calls, a buffer of metadata elements is used. The buffer size is
initially established at 256 elements but may be set to any size desired. To
avoid a large read for the typical case where an erase gap is not present,
the first read is of a single metadatum marker. If that is a gap marker,
then additional buffered reads are performed.
The permissibility of data record lengths that are not multiples of the
metadatum size presents a difficulty when reading through gaps. If such an
"odd length" record is written over a gap, half of a gap marker will exist
immediately after the trailing record length.
This condition is detected when reading forward by the appearance of a
"reversed" marker. The value appears reversed because the value is made up
of half of one marker and half of the next. This is handled by seeking
forward two bytes to resync (it is illegal to overwrite and leave only two
bytes of gap, so at least one "whole" metadata marker will follow the
half-gap).
Implementation notes:
1. For programming convenience, erase gap processing is performed for both
SIMH and E11 tape formats, although the latter will never contain erase
gaps, as the "tape_erase_fwd" call takes no action for the E11 format.
2. The "runaway_counter" cannot decrement to zero (or below) in the presence
of an error that terminates the gap-search loop. Therefore, the test
after the loop exit need not check for error status, except to check
whether an EOM occurred while reading a gap.
3. The dynamic start/stop test of the HP 3000 magnetic tape diagnostic
heavily exercises the erase gap scanning code. Sample test execution
times for various buffer sizes on a 2 GHz host platform are:
buffer size execution time
(elements) (CPU seconds)
----------- --------------
1 7200
32 783
128 237
256 203
512 186
1024 171
4. Because an erase gap may precede the logical end-of-medium, represented
either by the physical end-of-file or by an EOM marker, the "position not
updated" flag is set only if the tape is positioned at the EOM when the
routine is entered. If at least one gap marker precedes the EOM, then
the PNU flag is not set. This ensures that a backspace-and-retry
sequence will work correctly in both cases.
5. When a data record length word is seen, a check is made to see if the
word is the last word in the metadata buffer. If it is, then the file
stream is correctly positioned to read the data, i.e., is positioned
immediately after the length word. If the word is somewhere within the
buffer, then the stream is repositioned to the location of the start of
the data.
6. A skipped data record may reside entirely within the metadata buffer.
However, the buffer consists of four-byte elements, and a data record may
not end on an element boundary. Rather than testing for this and
succeeding only half of the time, we unilaterally reposition the file
stream and invalidate the buffer to force a read.
7. A partial buffer read without a host I/O error occurs when the physical
EOF is reached. If the buffer contains only erase gap markers, i.e., the
tape image ends with a gap, then the next read will return zero because
the EOF flag is set. In this case, we could avoid this read by calling
the "feof" function first. We do not, because the common case -- entry
with the file positioned at EOF -- will not have the EOF flag set, as the
preceding "fseek" resets it, so the read call would be made anyway.
Thus, we would incur a small overhead on every call to save some overhead
on a rare corner-case.
*/
static t_stat sim_tape_rdlntf (UNIT *uptr, t_mtrlnt *bc)
{
const uint32 f = MT_GET_FMT (uptr); /* the tape format */
uint8 c;
t_bool all_eof;
t_mtrlnt sbc;
t_tpclnt tpcbc;
t_mtrlnt buffer [256]; /* local tape buffer */
uint32 bufcntr, bufcap; /* buffer counter and capacity */
uint32 classbit; /* bit representing the object class */
int32 runaway_counter, max_gap, sizeof_gap; /* bytes remaining before runaway and bytes per gap */
t_addr next_pos; /* next record position */
t_stat status = MTSE_OK; /* preset status return */
uint32 accept = MTB_STANDARD; /* preset the standard class acceptance set */
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then quit with an error */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the initial tape position; if it fails */
MT_SET_PNU (uptr); /* then set position not updated */
status = sim_tape_ioerr (uptr); /* and quit with I/O error status */
}
else switch (f) { /* otherwise the read method depends on the tape format */
case MTUF_F_EXT:
accept = (uint32) (*bc); /* get the set of acceptable classes */
/* fall through into the standard SIMH and E11 handler */
case MTUF_F_STD:
case MTUF_F_E11:
max_gap = 25 * 12 /* set the largest legal gap size in bytes */
* bpi [MT_DENS (uptr->dynflags)]; /* corresponding to 25 feet of tape */
if (max_gap == 0) { /* if tape density has not been not set */
sizeof_gap = 0; /* then disable runaway detection */
max_gap = INT_MAX; /* and allow gaps of any size */
}
else /* otherwise */
sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */
runaway_counter = max_gap; /* initialize the runaway counter */
bufcntr = 0; /* force an initial read */
bufcap = 0; /* but of just one metadata marker */
do { /* loop until an object is accepted or an error occurs */
if (bufcntr == bufcap) { /* if the buffer is empty */
if (bufcap == 0) /* then if this is the initial read */
bufcap = 1; /* then start with just one marker */
else /* otherwise reset the capacity */
bufcap = sizeof (buffer) /* to the full size of the buffer */
/ sizeof (buffer [0]);
bufcap = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */
bufcap, uptr->fileref); /* with tape metadata */
if (ferror (uptr->fileref)) { /* if a file I/O error occurred */
if (bufcntr == 0) /* then if this is the initial read */
MT_SET_PNU (uptr); /* then set position-not-updated */
status = sim_tape_ioerr (uptr); /* report the error and quit */
break;
}
else if (bufcap == 0 /* otherwise if positioned at the physical EOF */
|| buffer [0] == MTR_EOM) { /* or at the logical EOM */
if (bufcntr == 0) /* then if this is the initial read */
MT_SET_PNU (uptr); /* then set position not updated */
if (bufcap == 0) /* if an EOM marker was not read */
*bc = 0; /* then zero the marker value */
else /* otherwise */
*bc = MTR_EOM; /* store the EOM value */
status = MTSE_EOM; /* report the end-of-medium */
break; /* and quit */
}
else /* otherwise reset the index */
bufcntr = 0; /* to the start of the buffer */
}
*bc = buffer [bufcntr++]; /* store the metadata marker value */
if (*bc == MTR_EOM) { /* if an end-of-medium marker is seen */
status = MTSE_EOM; /* then report the end-of-medium */
break; /* and quit */
}
uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* space over the marker */
if (*bc == MTR_TMK) { /* if the marker is a tape mark */
status = MTSE_TMK; /* then quit with tape mark status */
break;
}
else if (*bc == MTR_GAP) /* otherwise if the marker is a full gap */
runaway_counter -= sizeof_gap; /* then decrement the gap counter */
else if (*bc == MTR_FHGAP) { /* otherwise if the marker if a half gap */
uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2; /* then back up to resync */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the tape position; if it fails */
status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
break;
}
bufcntr = bufcap; /* mark the buffer as invalid to force a read */
runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter by half */
}
else { /* otherwise it is not a known marker */
classbit = MTR_FB (*bc); /* so get the bit corresponding to the class */
next_pos = uptr->pos + MTR_RL (*bc) /* it it's a data record */
+ sizeof (t_mtrlnt); /* then preset to the end of the record */
if (f != MTUF_F_E11) /* if the format is not E11 */
next_pos += MTR_RL (*bc) & 1; /* then record sizes are an even number of bytes */
if (classbit & accept) { /* if the class is accepted */
if (classbit == MTB_SMARK) /* then if it's a SIMH-reserved marker */
status = MTSE_RESERVED; /* then return reserved status */
else if (classbit == MTB_PMARK) /* otherwise if it's a private marker */
status = MTSE_OK; /* then return successful status */
else if (bufcntr == bufcap /* otherwise if the record starts after the buffer */
|| sim_fseek (uptr->fileref, /* or repositioning to the start */
uptr->pos, SEEK_SET) == 0) /* of the data area succeeds */
uptr->pos = next_pos; /* then position past the record */
else /* otherwise the seek failed */
status = sim_tape_ioerr (uptr); /* so quit with I/O error status */
break; /* acceptance terminates the search */
}
else if (classbit & MTB_RECORDSET) { /* otherwise if ignoring a data record */
uptr->pos = next_pos; /* then position past the record */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the new position; if it fails */
status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
break;
}
bufcntr = bufcap; /* mark the buffer as invalid to force a read */
}
runaway_counter = max_gap; /* ignoring a marker or record resets the counter */
}
}
while (runaway_counter > 0); /* continue searching until runaway occurs */
if (sizeof_gap > 0 /* if gap detection is enabled */
&& (runaway_counter <= 0 /* and a tape runaway occurred */
|| status == MTSE_EOM /* or EOM/EOF was seen */
&& runaway_counter < max_gap)) /* while a gap was being skipped */
status = MTSE_RUNAWAY; /* then report it */
break; /* end of case */
case MTUF_F_TPC:
sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref);
*bc = tpcbc; /* save rec lnt */
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr); /* pos not upd */
status = sim_tape_ioerr (uptr);
}
else if (feof (uptr->fileref)) { /* eof? */
MT_SET_PNU (uptr); /* pos not upd */
status = MTSE_EOM;
}
else {
uptr->pos = uptr->pos + sizeof (t_tpclnt); /* spc over reclnt */
if (tpcbc == TPC_TMK) /* tape mark? */
status = MTSE_TMK;
else
uptr->pos = uptr->pos + ((tpcbc + 1) & ~1); /* spc over record */
}
break;
case MTUF_F_P7B:
for (sbc = 0, all_eof = 1; ; sbc++) { /* loop thru record */
sim_fread (&c, sizeof (uint8), 1, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr); /* pos not upd */
status = sim_tape_ioerr (uptr);
break;
}
else if (feof (uptr->fileref)) { /* eof? */
if (sbc == 0) /* no data? eom */
status = MTSE_EOM;
break; /* treat like eor */
}
else if ((sbc != 0) && (c & P7B_SOR)) /* next record? */
break;
else if ((c & P7B_DPAR) != P7B_EOF)
all_eof = 0;
}
if (status == MTSE_OK) {
*bc = sbc; /* save rec lnt */
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
uptr->pos = uptr->pos + sbc; /* spc over record */
if (all_eof) /* tape mark? */
status = MTSE_TMK;
}
break;
default:
status = MTSE_FMT;
}
return status;
}
/* Read record length reverse (internal routine).
Inputs:
uptr = pointer to tape unit
bc = pointer to returned record length
Outputs:
status = operation status
exit condition tape position
------------------ -------------------------------------------
unit unattached unchanged
read error unchanged, PNU set if initial read
beginning of tape unchanged
tape mark updated
other marker updated
tape runaway updated
data record updated, sim_fread will read record forward
This routine is called to set up a record read or spacing in the reverse
direction. On return, status is MTSE_OK if a data record, private marker, or
reserved marker was read, or an MTSE error code if a standard marker (e.g.
tape mark) was read, or an error occurred. The file is positioned at the
first byte of a data record, before a tape mark or private marker, or
otherwise as indicated above. In all cases, the successfully read marker or
data record length word is returned via the "bc" pointer.
When the extended SIMH format is enabled, then the variable addressed by the
"bc" parameter must be set on entry to a bitmap of the object classes to
return. Each of the classes is represented by its corresponding bit, i.e.,
bit 0 represents class 0, bit 1 for class 1, etc. The routine will return
only objects from the selected classes. Unselected class objects will be
ignored by skipping over them until the first selected class object is seen.
This allows a simulator to declare those classes it understands (e.g.,
standard classes 0 and 8, plus private classes 2 and 7) and those classes it
wishes to ignore. Erase gap markers are always skipped, and standard markers
are always returned, so specifying an empty bitmap will perform the
equivalent of a "space file forward," returning only when a tape mark or
EOM/EOF is encountered.
When standard SIMH format is enabled, standard classes 0 and 8 are
automatically selected, and the entry value addressed by "bc" is ignored.
The permissibility of data record lengths that are not multiples of the
metadatum size presents a difficulty when reading through gaps. If such an
"odd length" record is written over a gap, half of a gap marker will exist
immediately after the trailing record length.
Reading in reverse presents a more complex problem than reading forward
through gaps, because half of the marker is from the preceding trailing
record length marker and therefore could be any of a range of values.
However, that range is restricted by the SIMH tape specification to permit
unambiguous detection of the condition. The Class F assignments are:
F0000000 - FFFDFFFF Reserved for future use (available)
FFFE0000 - FFFFFFFD Reserved for erase gap interpretation
FFFFFFFE Erase gap (primary value)
FFFFFFFF End of medium
Values within the reserved erase-gap interpretation subrange are as follows:
FFFE0000 - FFFEFFFE Illegal (would be seen as full gap in reverse reads)
FFFEFFFF Interpret as half-gap in forward reads
FFFF0000 - FFFFFFFD Interpret as half-gap in reverse reads
A conforming writer will never write the illegal marker values, so that a
conforming reader will be able to recognize the half-gap marker values.
Implementation notes:
1. The "sim_fread" call cannot return 0 in the absence of an error
condition. The preceding "sim_tape_bot" test ensures that "pos" >= 4, so
"sim_fseek" will back up at least that far, so "sim_fread" will read at
least one element. If the call returns zero, an error must have
occurred, so the "ferror" call must succeed.
2. The "runaway_counter" cannot decrement to zero (or below) in the presence
of an error that terminates the gap-search loop. Therefore, the test
after the loop exit need not check for error status.
3. See the notes at "sim_tape_rdlntf" regarding the implementation of tape
runaway detection.
*/
static t_stat sim_tape_rdlntr (UNIT *uptr, t_mtrlnt *bc)
{
const uint32 f = MT_GET_FMT (uptr); /* the tape format */
uint8 c;
t_bool all_eof;
t_addr ppos;
t_mtrlnt sbc;
t_tpclnt tpcbc;
t_mtrlnt buffer [256]; /* local tape buffer */
uint32 bufcntr, bufcap; /* buffer counter and capacity */
uint32 classbit; /* bit representing the object class */
int32 runaway_counter, max_gap, sizeof_gap; /* bytes remaining before runaway and bytes per gap */
t_addr next_pos; /* next record position */
t_stat status = MTSE_OK; /* preset status return */
uint32 accept = MTB_STANDARD; /* preset the standard class acceptance set */
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then quit with an error */
else if (sim_tape_bot (uptr)) /* otherwise if the unit is positioned at the BOT */
status = MTSE_BOT; /* then reading backward is not possible */
else switch (f) { /* otherwise the read method depends on the tape format */
case MTUF_F_EXT:
accept = (uint32) (*bc); /* get the set of acceptable classes */
/* fall through into the standard SIMH and E11 handler */
case MTUF_F_STD:
case MTUF_F_E11:
max_gap = 25 * 12 /* set the largest legal gap size in bytes */
* bpi [MT_DENS (uptr->dynflags)]; /* corresponding to 25 feet of tape */
if (max_gap == 0) { /* if tape density has not been not set */
sizeof_gap = 0; /* then disable runaway detection */
max_gap = INT_MAX; /* and allow gaps of any size */
}
else /* otherwise */
sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */
runaway_counter = max_gap; /* initialize the runaway counter */
bufcntr = 0; /* force an initial read */
bufcap = 0; /* but of just one metadata marker */
ppos = uptr->pos; /* save the initial tape position */
do { /* loop until an object is accepted or an error occurs */
if (bufcntr == 0) { /* then if the buffer is empty */
if (sim_tape_bot (uptr)) { /* then if the search has backed into the BOT */
status = MTSE_BOT; /* then quit with an error */
break;
}
else if (bufcap == 0) /* otherwise if this is the initial read */
bufcap = 1; /* then start with just one marker */
else if (uptr->pos < sizeof (buffer)) /* otherwise if less than a full buffer remains */
bufcap = (uint32) uptr->pos /* then reduce the capacity accordingly */
/ sizeof (t_mtrlnt);
else /* otherwise reset the capacity */
bufcap = sizeof (buffer) /* to the full size of the buffer */
/ sizeof (buffer [0]);
sim_fseek (uptr->fileref, /* seek back to the location */
uptr->pos - bufcap * sizeof (t_mtrlnt), /* corresponding to the start */
SEEK_SET); /* of the buffer */
bufcntr = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */
bufcap, uptr->fileref); /* with tape metadata */
if (ferror (uptr->fileref)) { /* if a file I/O error occurred */
if (uptr->pos == ppos) /* then if this is the initial read */
MT_SET_PNU (uptr); /* then set position not updated */
status = sim_tape_ioerr (uptr); /* report the error and quit */
break;
}
}
*bc = buffer [--bufcntr]; /* store the metadata marker value */
uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* backspace over the marker */
if (*bc == MTR_TMK) { /* if the marker is a tape mark */
status = MTSE_TMK; /* then quit with tape mark status */
break;
}
else if (*bc == MTR_GAP) /* otherwise if the marker is a full gap */
runaway_counter -= sizeof_gap; /* then decrement the gap counter */
else if ((*bc & MTR_RHGAP) == MTR_RHGAP) { /* otherwise if the marker is a half gap */
uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2; /* then position forward to resync */
bufcntr = 0; /* mark the buffer as invalid to force a read */
runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter by half */
}
else { /* otherwise it is not a known marker */
classbit = MTR_FB (*bc); /* so get the bit corresponding to the class */
next_pos = uptr->pos - MTR_RL (*bc) /* it it's a data record */
- sizeof (t_mtrlnt); /* then preset to the start of the record */
if (f != MTUF_F_E11) /* if the format is not E11 */
next_pos -= MTR_RL (*bc) & 1; /* then record sizes are an even number of bytes */
if (classbit & accept) { /* if the class is accepted */
if (classbit == MTB_SMARK) /* then if it's a SIMH-reserved marker */
status = MTSE_RESERVED; /* then return reserved status */
else if (classbit == MTB_PMARK) /* otherwise if it's a private marker */
status = MTSE_OK; /* then return successful status */
else if (sim_fseek (uptr->fileref, /* otherwise position to the start */
next_pos + sizeof (t_mtrlnt), /* of the data area */
SEEK_SET) == 0) /* and if the seek succeeds */
uptr->pos = next_pos; /* then position past the record */
else /* otherwise the seek failed */
status = sim_tape_ioerr (uptr); /* so quit with I/O error status */
break; /* acceptance terminates the search */
}
else if (classbit & MTB_RECORDSET) { /* otherwise if ignoring a data record */
uptr->pos = next_pos; /* then position before the record */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the new position; if it fails */
status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
break;
}
bufcntr = 0; /* mark the buffer as invalid to force a read */
}
runaway_counter = max_gap; /* ignoring a marker or record resets the counter */
}
}
while (runaway_counter > 0); /* continue searching until runaway occurs */
if (runaway_counter <= 0) /* if a tape runaway occurred */
status = MTSE_RUNAWAY; /* then report it */
break; /* end of case */
case MTUF_F_TPC:
ppos = sim_tape_tpc_fnd (uptr, (t_addr *) uptr->filebuf); /* find prev rec */
sim_fseek (uptr->fileref, ppos, SEEK_SET); /* position */
sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref);
*bc = tpcbc; /* save rec lnt */
if (ferror (uptr->fileref)) /* error? */
status = sim_tape_ioerr (uptr);
else if (feof (uptr->fileref)) /* eof? */
status = MTSE_EOM;
else {
uptr->pos = ppos; /* spc over record */
if (*bc == MTR_TMK) /* tape mark? */
status = MTSE_TMK;
else
sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET);
}
break;
case MTUF_F_P7B:
for (sbc = 1, all_eof = 1; (t_addr) sbc <= uptr->pos ; sbc++) {
sim_fseek (uptr->fileref, uptr->pos - sbc, SEEK_SET);
sim_fread (&c, sizeof (uint8), 1, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
status = sim_tape_ioerr (uptr);
break;
}
else if (feof (uptr->fileref)) { /* eof? */
status = MTSE_EOM;
break;
}
else {
if ((c & P7B_DPAR) != P7B_EOF)
all_eof = 0;
if (c & P7B_SOR) /* start of record? */
break;
}
}
if (status == MTSE_OK) {
uptr->pos = uptr->pos - sbc; /* update position */
*bc = sbc; /* save rec lnt */
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
if (all_eof) /* tape mark? */
status = MTSE_TMK;
}
break;
default:
status = MTSE_FMT;
}
return status;
}
/* Read a data record or tape marker.
Read the data record or tape marker a the current tape position in the
indicated direction and return it via the supplied buffer or marker pointers,
respectively.
On entry, the "uptr" parameter points at the UNIT structure describing the
tape device, "buffer" points at a buffer large enough to receive a retrieved
data record, "class_count" points at a variable that receives the data record
length word containing the record class and length or the tape marker value,
"bufsize" indicates the size of the buffer in bytes, and "reverse" is TRUE if
the record is to be read in the reverse direction and FALSE if the record is
to be read in the forward direction.
If the tape format is extended SIMH, then "class_count" must point at a value
containing a bitmap of the desired record and marker classes to read. Each
class is represented by its corresponding bit. Classes present in the tape
image but not in the bitmap are skipped until an object of the specified
class is read. The standard markers (tape mark, etc.) are always read and
interpreted.
A successful read of a data record returns the data in the buffer and the
record class and length via the "class_count" parameter. A successful marker
read returns the marker value via the "class_count" parameter; the buffer is
not used.
For all other tape formats, the entry value indicated by "class_count" is
ignored, and all items supported by the specified format are returned.
Successful reads return data in the buffer and the record length via the
"class_count" parameter.
The result of the read is returned as the value of the function, as follows:
Status Condition
------------- ---------------------------------------------
MTSE_OK Successful read of a good data record
MTSE_RECE Successful read of a bad data record
MTSE_TMK Successful read of a tape mark
MTSE_RESERVED Successful read of a reserved marker
MTSE_UNATT The tape unit is not attached
MTSE_IOERR A host I/O error occurred
MTSE_FMT An invalid tape format is selected
MTSE_BOT Reading stopped at the beginning of the tape
MTSE_EOM Reading stopped at the end of the tape
MTSE_RUNAWAY Reading did not encounter any data
MTSE_INVRL The record is larger than the supplied buffer
or the record is incomplete
*/
static t_stat tape_read (UNIT *uptr, uint8 *buffer, t_mtrlnt *class_count, t_mtrlnt bufsize, t_bool reverse)
{
const uint32 f = MT_GET_FMT (uptr); /* the tape format */
t_mtrlnt cbc, rbc;
t_addr opos;
t_stat st;
cbc = *class_count; /* get the acceptance mask */
opos = uptr->pos; /* and save the original file position */
if (reverse) /* for a reverse read */
st = sim_tape_rdlntr (uptr, &cbc); /* get the preceding record length */
else /* otherwise */
st = sim_tape_rdlntf (uptr, &cbc); /* get the following record length */
if (st != MTSE_OK /* if the read failed */
|| (MTR_FB (cbc) & MTB_MARKERSET)) { /* or it returned a marker */
if (f == MTUF_F_EXT) /* then if the format is extended SIMH */
*class_count = cbc; /* then return the marker value */
return st; /* return the status */
}
rbc = MTR_RL (cbc); /* get the record length */
if (f == MTUF_F_EXT) /* if the format is extended SIMH */
*class_count = cbc; /* then return the class and length */
else /* otherwise */
*class_count = rbc; /* return just the length */
if (rbc > bufsize) /* if the record won't fit in the buffer */
st = MTSE_INVRL; /* then return invalid length status */
else { /* otherwise */
sim_fread (buffer, sizeof (uint8), rbc, uptr->fileref); /* read the data payload into the supplied buffer */
if (ferror (uptr->fileref)) /* if a host I/O error occurred */
st = sim_tape_ioerr (uptr); /* then return I/O error status */
else if (feof (uptr->fileref)) /* otherwise if the read was incomplete */
st = MTSE_INVRL; /* then report a record length error */
else if (f == MTUF_F_P7B) /* otherwise if the format is P7B */
buffer [0] = buffer [0] & P7B_DPAR; /* then strip the start-of-record flag */
}
if (st != MTSE_OK) { /* if the read failed */
MT_SET_PNU (uptr); /* then set the position not updated flag */
uptr->pos = opos; /* and restore the original position */
return st; /* and return the failure status */
}
else if (MTR_CF (cbc) == MTC_BAD) /* otherwise if a bad record was read */
return MTSE_RECE; /* then report a data error */
else /* otherwise */
return MTSE_OK; /* report a successful read */
}
/* Read record or marker forward.
Inputs:
uptr = pointer to tape unit
buf = pointer to buffer
bc = pointer to returned class/record length
max = maximum record size
Outputs:
status = operation status
*/
t_stat sim_tape_rdrecf (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max)
{
return tape_read (uptr, buf, bc, max, FALSE); /* read and return the next record or marker */
}
/* Read record or marker reverse.
Inputs:
uptr = pointer to tape unit
buf = pointer to buffer
bc = pointer to returned class/record length
max = maximum record size
Outputs:
status = operation status
*/
t_stat sim_tape_rdrecr (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max)
{
return tape_read (uptr, buf, bc, max, TRUE); /* read and return the prior record or marker */
}
/* Write a data record forward.
Write a data record at the current tape position and return the status of the
operation.
On entry, the "uptr" parameter points at the UNIT structure describing the
tape device, "buf" points at the buffer containing the data, and "clbc"
contains the class and record length.
If the tape format is extended SIMH, then "clbc" must contain a standard or
private data record class and if the class is 0 (i.e., a good data record),
then the record length must be non-zero. For all other tape formats, the
class must be 0 or 8 (good or bad data record); a record length of zero is
treated as a NOP for these formats.
The result of the write is returned as the value of the function, as follows:
Status Condition
------------- -------------------------------------------------
MTSE_OK Successful write of the data record
MTSE_UNATT The tape unit is not attached
MTSE_WRP The tape unit is write protected
MTSE_IOERR A host I/O error occurred
MTSE_INVRL The record length is improper or too long
MTSE_RESERVED The record class is reserved or is a marker class
MTSE_FMT The tape format does not support the record class
*/
t_stat sim_tape_wrrecf (UNIT *uptr, uint8 *buf, t_mtrlnt clbc)
{
const uint32 f = MT_GET_FMT (uptr); /* the tape format */
t_mtrlnt sbc;
uint32 classbit;
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
sbc = MTR_RL (clbc); /* get the record length */
classbit = MTR_FB (clbc); /* and the class field bit */
if (f == MTUF_F_EXT) { /* if the format is extended SIMH */
if (! (classbit & MTB_EXTENDED)) /* then if not in the extended record class */
return MTSE_RESERVED; /* then report a reserved class error */
else if (sbc == 0 && classbit == MTB_GOOD) /* otherwise if the length of a good record is zero */
return MTSE_INVRL; /* then report an invalid length error */
}
else if (! (classbit & MTB_STANDARD)) /* otherwise if the class is not a standard record */
return MTSE_FMT; /* then report a format error */
else if (sbc == 0 && classbit == MTB_GOOD) /* otherwise if the length of a good record is zero */
return MTSE_OK; /* then treat it as a NOP */
else if (sbc > MTR_MAXLEN) /* otherwise if the record is too long */
return MTSE_INVRL; /* then report an invalid length error */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then report it */
else if (sim_tape_wrp (uptr)) /* otherwise if the tape is write protected */
return MTSE_WRP; /* then report it */
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set the tape position */
switch (f) { /* dispatch on the format */
case MTUF_F_STD: /* standard */
case MTUF_F_EXT: /* extended standard */
sbc = (sbc + 1) & ~1; /* pad odd length */
/* fall through into the E11 handler */
case MTUF_F_E11: /* E11 */
sim_fwrite (&clbc, sizeof (t_mtrlnt), 1, uptr->fileref);
sim_fwrite (buf, sizeof (uint8), sbc, uptr->fileref);
sim_fwrite (&clbc, sizeof (t_mtrlnt), 1, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr);
return sim_tape_ioerr (uptr);
}
uptr->pos = uptr->pos + sbc + (2 * sizeof (t_mtrlnt)); /* move tape */
break;
case MTUF_F_P7B: /* Pierce 7B */
buf[0] = buf[0] | P7B_SOR; /* mark start of rec */
sim_fwrite (buf, sizeof (uint8), sbc, uptr->fileref);
sim_fwrite (buf, sizeof (uint8), 1, uptr->fileref); /* delimit rec */
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr);
return sim_tape_ioerr (uptr);
}
uptr->pos = uptr->pos + sbc; /* move tape */
break;
}
return MTSE_OK;
}
/* Write metadata forward (internal routine) */
static t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat)
{
MT_CLR_PNU (uptr);
if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */
return MTSE_UNATT;
if (sim_tape_wrp (uptr)) /* write prot? */
return MTSE_WRP;
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set pos */
sim_fwrite (&dat, sizeof (t_mtrlnt), 1, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr);
return sim_tape_ioerr (uptr);
}
uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* move tape */
return MTSE_OK;
}
/* Write a private marker.
Write a private marker value at the current tape position and return the
status of the operation.
On entry, the "uptr" parameter points at the UNIT structure describing the
tape device, and "mk" contains the marker class and value. The tape format
must be extended SIMH, and "mk" must be a member of the private marker class.
The result of the write is returned as the value of the function, as follows:
Status Condition
------------- ------------------------------------------------
MTSE_OK Successful write of the marker
MTSE_UNATT The tape unit is not attached
MTSE_WRP The tape unit is write protected
MTSE_IOERR A host I/O error occurred
MTSE_RESERVED The class is not the private marker class
MTSE_FMT The tape format does not support private markers
*/
t_stat sim_tape_wrmrk (UNIT *uptr, t_mtrlnt mk)
{
if (MT_GET_FMT (uptr) != MTUF_F_EXT) /* if the format is not extended SIMH */
return MTSE_FMT; /* then report a format error */
else if (MTR_CF (mk) != MTC_PMARK) /* otherwise if the marker is not private */
return MTSE_RESERVED; /* then report a reserved class error */
else /* otherwise */
return sim_tape_wrdata (uptr, mk); /* write the marker to the tape */
}
/* Write tape a mark */
t_stat sim_tape_wrtmk (UNIT *uptr)
{
if (MT_GET_FMT (uptr) == MTUF_F_P7B) { /* P7B? */
uint8 buf = P7B_EOF; /* eof mark */
return sim_tape_wrrecf (uptr, &buf, 1); /* write char */
}
return sim_tape_wrdata (uptr, MTR_TMK);
}
/* Write an end of medium */
t_stat sim_tape_wreom (UNIT *uptr)
{
t_stat result;
if (MT_GET_FMT (uptr) == MTUF_F_P7B) /* cant do P7B */
return MTSE_FMT;
result = sim_tape_wrdata (uptr, MTR_EOM); /* write the EOM marker */
uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* restore original tape position */
MT_SET_PNU (uptr); /* indicate that position was not updated */
return result;
}
/* Erase a gap of the specified number of bytes (internal routine).
This routine will write a gap of the requested number of bytes at the current
position of the file attached to the supplied unit.
On entry, the file is positioned to the start of the gap as indicated by the
"uptr->pos" value. The minimum gap size allowed is four bytes (one erase gap
marker); smaller values will be rounded up. As the SIMH tape format allows
erasures only in multiples of two bytes, an odd byte count is rounded up to
the next even value.
If the requested size will not accommodate an integral number of gap markers,
a leading half-gap marker is written first. Then the required number of gap
markers are written to fill the specified gap. To improve efficiency, each
"sim_fwrite" call writes multiple gaps.
If a host file I/O error occurs while writing, the file position is restored,
the position-not-updated flag is set, the error is reported to the console,
and the routine returns MTSE_IOERR. Otherwise, the routine returns MTSE_OK.
Implementation notes:
1. Erase gaps are currently supported only in standard and extended SIMH
tape formats.
2. There is no easy way to initialize the "gaps" array statically, so we do
it at run-time. However, being a static array, the initialization is
only performed once and the elements are guaranteed to be zero when this
routine is called for the first time
3. The "half_gap" array wants to be constant, but "sim_fwrite" does not
declare a compatible buffer pointer parameter.
4. A half-gap cannot be written by itself, as interpretation when reading
would be indeterminate.
*/
static t_stat tape_erase (UNIT *uptr, t_mtrlnt byte_count)
{
static t_mtrlnt gaps [256]; /* a block of erase gaps */
static uint8 half_gap [2] = { 0xFF, 0xFF }; /* upper half of an erase gap */
const uint32 buffer_size = sizeof gaps / sizeof gaps [0];
const uint32 meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */
const t_addr gap_pos = uptr->pos; /* the file position where the gap will start */
uint32 count, marker_count;
if (gaps [0] == 0) /* if the gap block has not been initialized */
for (count = 0; count < buffer_size; count++) /* then fill the block with erase gaps */
gaps [count] = MTR_GAP; /* to improve write performance */
sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* seek to the start of the gap */
byte_count = (byte_count + 1) & ~1; /* round the count to an even number */
if (byte_count < meta_size) /* if the size is smaller than an erase gap marker */
byte_count = meta_size; /* then increase the size to one marker */
else if (byte_count % meta_size > 0) { /* otherwise if an integral number of markers won't fit */
sim_fwrite (half_gap, sizeof (uint8), 2, /* then start the gap */
uptr->fileref); /* with a half-gap marker */
uptr->pos = uptr->pos + sizeof half_gap; /* advance the tape position */
byte_count = byte_count - sizeof half_gap; /* and drop the byte count for the half-gap */
}
marker_count = byte_count / meta_size; /* get the count of full gap markers */
while (marker_count > 0) { /* while full gaps are needed */
if (marker_count > buffer_size) /* if more than a full block is needed */
count = buffer_size; /* then write a full block of gaps */
else /* otherwise */
count = marker_count; /* write the remaining size needed */
sim_fwrite (gaps, meta_size, count, uptr->fileref); /* write the erase gap */
marker_count = marker_count - count; /* reduce the count by the amount erased */
}
if (ferror (uptr->fileref)) { /* if a host I/O error occurred */
uptr->pos = gap_pos; /* then reposition back to the gap start */
MT_SET_PNU (uptr); /* report that the position was not updated */
return sim_tape_ioerr (uptr); /* and that an error occurred */
}
else { /* otherwise the writes were successful */
uptr->pos = uptr->pos + byte_count; /* so advance the tape position past the gap */
MT_CLR_PNU (uptr); /* report that the position was updated */
return MTSE_OK; /* and return success */
}
}
/* Erase a gap in the forward direction (internal routine).
An erase gap is written in the forward direction on the tape unit specified
by "uptr" for the number of bytes specified by "gap_size". The status of the
operation is returned, and the file position is altered as follows:
Exit Condition File Position
------------------ ------------------
unit unattached unchanged
unsupported format unchanged
write protected unchanged
read error unchanged, PNU set
write error unchanged, PNU set
gap written updated
If the requested byte count equals the metadatum size, then the routine
succeeds only if it can overlay a single metadatum (i.e., a tape mark, an
end-of-medium marker, or an existing erase gap marker); otherwise, the file
position is not altered, PNU is set, and MTSE_INVRL (invalid record length)
status is returned.
An erase gap is represented in the tape image file by a special metadata
value repeated throughout the gap. The value is chosen so that it is still
recognizable even if it has been "cut in half" by a subsequent data overwrite
that does not end on a metadatum-sized boundary. In addition, a range of
metadata values are reserved for detection in the reverse direction.
This implementation supports erasing gaps in the middle of a populated tape
image and will always produce a valid image. It also produces valid images
when overwriting gaps with data records, with one exception: a data write
that leaves only two bytes of gap remaining will produce an invalid tape.
This limitation is deemed acceptable, as it is analogous to the existing
limitation that data records cannot overwrite other data records without
producing an invalid tape.
To write an erase gap, the implementation uses one of two approaches,
depending on whether or not the current tape position is at EOM. Erasing at
EOM presents no special difficulties; gap metadata markers are written for
the prescribed number of bytes. If the tape is not at EOM, then erasing must
take into account the existing record structure to ensure that a valid tape
image is maintained.
The general approach is to erase for the nominal number of bytes but to
increase that length if necessary to ensure that a partially overwritten data
record at the end of the gap can be altered to maintain validity. Because
the smallest legal tape record requires space for two metadata markers plus
two data bytes, an erasure that would leave less than that is increased to
consume the entire record. Otherwise, the final record is truncated by
rewriting the leading and trailing length words appropriately.
When reading in either direction, gap metadata markers are ignored (skipped)
until a record length header, non-gap marker, or physical EOF is encountered.
Thus, tape images containing gap metadata are transparent to the calling
simulator (unless tape runaway support is enabled -- see the notes at
"sim_tape_rdlntf" for details).
If the current tape format supports erase gaps, then this routine will write
a gap of the requested size. If the format does not, then no action will be
taken, and MTSE_OK status will be returned. This allows a device simulator
that supports writing erase gaps to use the same code without worrying about
the tape format currently selected by the user. A request for an erase gap
of zero length also succeeds with no action taken.
Considerations when reading erase gaps are discussed in more detail in the
comments of the "sim_tape_rdlntf" routine.
The scan of an existing tape image before erasing proceeds as follows.
After preliminary access checks (i.e., image is attached and is writable),
the file is positioned to the start of the area to be erased. The routine
then enters a loop that reads data items and accumulates the areas to be
erased until the required number of bytes have been examined. When that
happens, the gap is written at the original position, extended as necessary
to maintain tape integrity. If an error occurs, the scan is aborted, and the
appropriate error code is returned to the caller.
Each pass of the loop begins by reading the next metadatum in the file. If a
host I/O error occurs, the scan is aborted with an appropriate error return.
If an EOM metadatum was read, or the physical EOF was encountered, the scan
is completed by allocating the remaining gap space unconditionally.
If a gap or tape mark metadatum was read, the position is advanced over the
marker, and the scan continues. If a half-gap was read, the position is
adjusted to align with the next full metadatum, and the scan continues. If
the metadatum is not one of these items, it must be a data record leading
length marker. If the caller wants only a single metadatum erased, the
routine returns an invalid record length error.
Before adding part or all of the data record to the accumulated erase area,
record integrity is checked. Verification of the trailing length marker is
done in two steps. If the marker would be positioned beyond the end of the
file, then the tape image is considered to be invalid, and the scan is
terminated. If the location is within the file, the position is temporarily
moved to the location of the trailing length marker, which is read and
compared to the leading length marker. If the read fails, or the markers do
not compare, then the image is invalid, and the scan is completed by
allocating the remaining gap space unconditionally.
If the markers compare, then a check is made to see if the data record is
contained wholly within the area to be erased or if it extends beyond the end
of the erasure. In the first case, the space occupied by the record is
simply added to the accumulated area. In the second case, however, the
record must be truncated to maintain image validity.
Truncation is possible only if a valid record can be written into the
space occupied by the remaining part of the record. The smallest legal
record is two data bytes long, and such a record occupies ten bytes,
including the pair of four-byte length markers. If the remaining space is
too small, the gap is extended to consume the full record to avoid leaving an
invalid area.
If the size of the remaining record after the erasure is large enough, the
trailing length word is rewritten for the new, shorter size, and then the
leading length word immediately following the erased area is written to
match.
Once gap allocation is complete, the loop terminates, and the file is
repositioned to the start of the gap area. If the loop terminated for an
error, it is returned with PNU set. Otherwise, the new gap, lengthened if
necessary, is written, and PNU is cleared.
Implementation notes:
1. Erase gaps are currently supported only in standard and extended SIMH
tape formats.
2. Metadatum reads either succeed and returns 1 or fail and returns 0. If a
read fails, and "ferror" returns false, then it must have read into the
end of the file (only these three outcomes are possible).
3. The area scan is necessary for tape image integrity to ensure that a data
record straddling the end of the erasure is truncated appropriately. The
scan is guaranteed to succeed only if it begins at a valid metadatum. If
it begins in the middle of a previously overwritten data record, then the
scan will interpret old data values as tape formatting markers. The data
record sanity checks attempt to recover from this situation, but it is
still possible to corrupt valid data that follows an erasure of an
invalid area (e.g., if the leading and trailing length words happen
to match but actually represent previously recorded data rather than
record metadata). If an application knows that the erased area will
not contain valid formatting, the "sim_tape_erase" routine should be used
instead, as it erases without first scanning the area.
4. Truncating an existing data record corresponds to overwriting part of a
record on a real tape. Reading such a record on a real drive would
produce CRC errors, due to the lost portion. In simulation, we could
change a good record (Class 0) to a bad record (Class 8). However, this
is not possible for private or reserved record classes, as that would
change the classification (consider that a private class that had
been ignored would not be once it had been truncated and changed to Class
8). Given that there is no good general solution, we do not modify
classes for truncated records, as reading a partially erased record is an
"all bets are off" operation.
*/
static t_stat tape_erase_fwd (UNIT *uptr, t_mtrlnt gap_size)
{
const t_addr gap_pos = uptr->pos; /* the file position where the gap will start */
const uint32 format = MT_GET_FMT (uptr); /* the tape format */
const uint32 meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */
const uint32 min_rec_size = 2 + sizeof (t_mtrlnt) * 2; /* the smallest data record size */
size_t xfer;
t_mtrlnt meta, sbc, new_len, rec_size;
uint32 file_size;
int32 gap_needed = (int32) gap_size; /* the gap remaining to be allocated from the tape */
uint32 gap_alloc = 0; /* the gap currently allocated from the tape */
t_stat status = MTSE_OK; /* the status of the last operation */
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then we cannot proceed */
else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */
return MTSE_WRP; /* then we cannot write */
else if (gap_size == 0 /* otherwise if the gap is zero length */
|| (format != MTUF_F_STD && format != MTUF_F_EXT)) /* or gaps are not supported */
return MTSE_OK; /* then take no action */
MT_SET_PNU (uptr); /* errors from here on do not update the position */
file_size = sim_fsize (uptr->fileref); /* get the file size */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */
return sim_tape_ioerr (uptr); /* then quit with I/O error status */
do { /* scan the area to be erased */
xfer = sim_fread (&meta, meta_size, 1, uptr->fileref); /* starting with the next metadatum in the file */
if (ferror (uptr->fileref)) { /* if a read error occurred */
status = sim_tape_ioerr (uptr); /* then report an I/O error */
break; /* and quit the search */
}
else if (xfer == 1) /* otherwise if we had a good read */
uptr->pos = uptr->pos + meta_size; /* then move the tape over the datum */
if ((xfer == 0) || (meta == MTR_EOM)) { /* if the physical EOF or an EOM marker is seen */
gap_alloc = gap_alloc + gap_needed; /* then allocate the remainder of the space */
break; /* and terminate the search */
}
else if ((meta == MTR_GAP) || (meta == MTR_TMK)) { /* otherwise if a gap or tape mark is seen */
gap_alloc = gap_alloc + meta_size; /* then allocate the marker space */
gap_needed = gap_needed - meta_size; /* and reduce the amount remaining */
}
else if (gap_size == meta_size) { /* otherwise if the request is for a single metadatum */
status = MTSE_INVRL; /* then report an invalid record length error */
break; /* as we're not erasing a metadatum as required */
}
else if (meta == MTR_FHGAP) { /* otherwise if a half-gap is seen */
uptr->pos = uptr->pos - meta_size / 2; /* then back up to resync */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* position the tape; if it fails */
status = sim_tape_ioerr (uptr); /* then report an I/O error */
break; /* and quit the search */
}
gap_alloc = gap_alloc + meta_size / 2; /* allocate the marker space */
gap_needed = gap_needed - meta_size / 2; /* and reduce the amount remaining */
}
else if (uptr->pos + MTR_RL (meta) + meta_size > file_size) { /* otherwise if it cannot be a data record */
gap_alloc = gap_alloc + gap_needed; /* then presume an overwritten tape */
break; /* and allocate the remainder of the space */
}
else { /* otherwise it may be a data record */
sbc = MTR_RL (meta); /* so get the data length */
rec_size = ((sbc + 1) & ~1) + meta_size * 2; /* and the overall record size in bytes */
uptr->pos = uptr->pos + (sbc + 1) & ~1; /* position to the trailing length marker */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* position the tape; if it fails */
status = sim_tape_ioerr (uptr); /* then report an I/O error */
break; /* and quit the search */
}
xfer = sim_fread (&meta, meta_size, 1, uptr->fileref); /* read the metadatum */
if (ferror (uptr->fileref)) { /* if a read error occurred */
status = sim_tape_ioerr (uptr); /* then report an I/O error */
break; /* and quit the search */
}
else if ((xfer != 1) || (sbc != MTR_RL (meta))) { /* otherwise if the marker is bad or does not compare */
gap_alloc = gap_alloc + gap_needed; /* then presume an overwritten tape */
break; /* and allocate the remainder of the space */
}
else if (rec_size < gap_needed + min_rec_size) { /* otherwise if the record is smaller than needed */
uptr->pos = uptr->pos + meta_size; /* then skip over the record */
gap_alloc = gap_alloc + rec_size; /* and allocate the record space */
gap_needed = gap_needed - rec_size; /* and reduce the amount remaining */
}
else { /* otherwise the size is larger than needed */
new_len = MTR_CF (meta) | (sbc - gap_needed); /* so get the shortened record length */
status = sim_tape_wrdata (uptr, new_len); /* rewrite the trailing length marker */
uptr->pos = uptr->pos - 2 * meta_size /* move the position */
- sbc + gap_needed; /* back to the leading length marker */
if (status == MTSE_OK) /* if the trailing write succeeded */
status = sim_tape_wrdata (uptr, new_len); /* then rewrite the leading length marker */
gap_alloc = gap_alloc + gap_needed; /* the record provides the rest of the gap */
break; /* and no more space is needed */
}
}
}
while (gap_needed > 0); /* loop until all of the gap has been allocated */
uptr->pos = gap_pos; /* reposition to the start of the gap */
if (status == MTSE_OK) /* if the scan was successful */
return tape_erase (uptr, gap_alloc); /* then return the status of the erasure */
else /* otherwise the search failed */
return status; /* so return the status code with PNU */
}
/* Erase a gap in the reverse direction (internal routine).
An erase gap is written in the reverse direction on the tape unit specified
by "uptr" for the number of bytes specified by "gap_size". The status of the
operation is returned, and the file position is altered as follows:
Exit Condition File Position
------------------ ------------------
unit unattached unchanged
unsupported format unchanged
write protected unchanged
read error unchanged, PNU set
write error unchanged, PNU set
gap written updated
If the requested byte count equals the metadatum size, then the routine
succeeds only if it can overlay a single metadatum (i.e., a tape mark, an
end-of-medium marker, or an existing erase gap marker); otherwise, the file
position is not altered, PNU is set, and MTSE_INVRL (invalid record length)
status is returned.
Implementation notes:
1. Erase gaps are currently supported only in standard and extended SIMH
tape formats.
2. Erasing a record in the reverse direction currently succeeds only if the
gap requested occupies the same space as the record located immediately
before the current file position. This limitation may be lifted in a
future update.
3. The "sim_fread" call cannot return 0 in the absence of an error
condition. The preceding "sim_tape_bot" test ensures that "pos" >= 4, so
"sim_fseek" will back up at least that far, so "sim_fread" will read at
least one element. If the call returns zero, an error must have
occurred, so the "ferror" call must succeed.
*/
static t_stat tape_erase_rev (UNIT *uptr, t_mtrlnt gap_size)
{
const uint32 format = MT_GET_FMT (uptr); /* the tape format */
const uint32 meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */
t_stat status;
t_mtrlnt rec_size, metadatum;
t_addr gap_pos;
size_t xfer;
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then we cannot proceed */
else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */
return MTSE_WRP; /* then we cannot write */
else if (gap_size == 0 /* otherwise if the gap is zero length */
|| (format != MTUF_F_STD && format != MTUF_F_EXT)) /* or gaps are not supported */
return MTSE_OK; /* then take no action */
gap_pos = uptr->pos; /* save the starting position */
if (gap_size == meta_size) { /* if the request is for a single metadatum */
if (sim_tape_bot (uptr)) /* then if the unit is positioned at the BOT */
return MTSE_BOT; /* then erasing backward is not possible */
else /* otherwise */
uptr->pos -= meta_size; /* back up the file pointer */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */
return sim_tape_ioerr (uptr); /* then quit with I/O error status */
sim_fread (&metadatum, meta_size, 1, uptr->fileref); /* read a metadatum */
if (ferror (uptr->fileref)) /* if a file I/O error occurred */
return sim_tape_ioerr (uptr); /* then report the error and quit */
else if (metadatum == MTR_TMK) /* otherwise if a tape mark is present */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* then reposition the tape; if it fails */
return sim_tape_ioerr (uptr); /* then quit with I/O error status */
else { /* otherwise */
metadatum = MTR_GAP; /* replace it with an erase gap marker */
xfer = sim_fwrite (&metadatum, meta_size, /* write the gap marker */
1, uptr->fileref);
if (ferror (uptr->fileref) || xfer == 0) /* if a file I/O error occurred */
return sim_tape_ioerr (uptr); /* report the error and quit */
else /* otherwise the write succeeded */
status = MTSE_OK; /* so return success */
}
else if (metadatum == MTR_GAP) /* otherwise if a gap already exists */
status = MTSE_OK; /* then take no additional action */
else { /* otherwise a data record is present */
uptr->pos = gap_pos; /* so restore the starting position */
return MTSE_INVRL; /* and fail with invalid record length status */
}
}
else { /* otherwise it's an erase record request */
status = sim_tape_rdlntr (uptr, &rec_size); /* so get the length of the preceding record */
if (status == MTSE_OK /* if the read succeeded */
&& gap_size == rec_size + 2 * meta_size) { /* and the gap will exactly overlay the record */
gap_pos = uptr->pos; /* then save the gap start position */
status = tape_erase (uptr, gap_size); /* erase the record */
if (status == MTSE_OK) /* if the gap write succeeded */
uptr->pos = gap_pos; /* the reposition back to the start of the gap */
}
else { /* otherwise the read failed or is the wrong size */
uptr->pos = gap_pos; /* so restore the starting position */
if (status != MTSE_OK) /* if the record was not found */
return status; /* then return the failure reason */
else /* otherwise the record is the wrong size */
return MTSE_INVRL; /* so report an invalid record length */
}
}
return status; /* return the status of the erase operation */
}
/* Write an erase gap.
An erase gap is written on the tape unit specified by "uptr" for the length
specified by "gap_size" in tenths of an inch, and the status of the operation
is returned. The tape density must have been set by a previous
sim_tape_set_dens call; if it has not, then no action is taken, and MTSE_FMT
is returned.
If the requested gap length is zero, or the tape format currently selected
does not support erase gaps, the call succeeds with no action taken. This
allows a device simulator that supports writing erase gaps to use the same
code without worrying about the tape format currently selected by the user.
Because SIMH tape images do not carry physical parameters (e.g., recording
density), overwriting a tape image file containing a gap is problematic if
the density setting is not the same as that used during recording. There is
no way to establish a gap of a certain length unequivocally in an image file,
so this implementation establishes a gap of a certain number of bytes that
reflect the desired gap length at the tape density in bits per inch used
during writing.
*/
t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen)
{
const uint32 density = bpi [MT_DENS (uptr->dynflags)]; /* the tape density in bits per inch */
const uint32 byte_length = (gaplen * density) / 10; /* the size of the requested gap in bytes */
if (density == 0) /* if the density has not been set */
return MTSE_FMT; /* then report a format error */
else /* otherwise */
return tape_erase_fwd (uptr, byte_length); /* erase the requested gap size in bytes */
}
/* Erase a record forward.
An erase gap is written in the forward direction on the tape unit specified
by "uptr" for a length corresponding to a record containing the number of
bytes specified by "bc", and the status of the operation is returned. The
resulting gap will occupy "bc" bytes plus the size of the record length
metadata. This function may be used to erase a record of length "n" in place
by requesting a gap of length "n". After erasure, the tape will be
positioned at the end of the gap.
If a length of 0 is specified, then the metadatum marker at the current tape
position will be erased. If the tape is not positioned at a metadatum
marker, the routine fails with MTSE_INVRL, and the tape position is
unchanged.
*/
t_stat sim_tape_errecf (UNIT *uptr, t_mtrlnt bc)
{
const t_mtrlnt meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */
const t_mtrlnt gap_size = bc + 2 * meta_size; /* the requested gap size in bytes */
if (bc == 0) /* if a zero-length erase is requested */
return tape_erase_fwd (uptr, meta_size); /* then erase a metadatum marker */
else /* otherwise */
return tape_erase_fwd (uptr, gap_size); /* erase the requested gap */
}
/* Erase a record reverse.
An erase gap is written in the reverse direction on the tape unit specified
by "uptr" for a length corresponding to a record containing the number of
bytes specified by "bc", and the status of the operation is returned. The
resulting gap will occupy "bc" bytes plus the size of the record length
metadata. This function may be used to erase a record of length "n" in place
by requesting a gap of length "n". After erasure, the tape will be
positioned at the start of the gap.
If a length of 0 is specified, then the metadatum marker preceding the
current tape position will be erased. If the tape is not positioned after a
metadatum marker, the routine fails with MTSE_INVRL, and the tape position is
unchanged.
*/
t_stat sim_tape_errecr (UNIT *uptr, t_mtrlnt bc)
{
const t_mtrlnt meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */
const t_mtrlnt gap_size = bc + 2 * meta_size; /* the requested gap size in bytes */
if (bc == 0) /* if a zero-length erase is requested */
return tape_erase_rev (uptr, meta_size); /* then erase a metadatum marker */
else /* otherwise */
return tape_erase_rev (uptr, gap_size); /* erase the requested gap */
}
/* Erase a specified number of bytes.
An erase gap is written on the tape unit specified by "uptr" for the number
of bytes specified by "bc", and the status of the operation is returned. No
checking is done to preserve the tape structure while erasing, so the caller
is responsible for ensuring that the format remains valid.
If the requested byte count is zero, or the tape format currently selected
does not support erase gaps, the call succeeds with no action taken. This
allows a device simulator that supports writing erase gaps to use the same
code without worrying about the tape format currently selected by the user.
*/
t_stat sim_tape_erase (UNIT *uptr, t_mtrlnt bc)
{
const uint32 format = MT_GET_FMT (uptr); /* the tape format */
MT_CLR_PNU (uptr); /* clear the position-not-updated flag */
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then we cannot proceed */
else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */
return MTSE_WRP; /* then we cannot write */
else if (bc == 0 /* if the count is zero */
|| (format != MTUF_F_STD && format != MTUF_F_EXT)) /* or gaps are not supported */
return MTSE_OK; /* then take no action */
else /* otherwise */
return tape_erase (uptr, bc); /* erase the requested number of bytes */
}
/* Space record forward.
Inputs:
uptr = pointer to tape unit
bc = pointer to returned record length or marker
Outputs:
status = operation status
exit condition tape position
------------------ -----------------------------------------------------
unit unattached unchanged
read error unchanged, PNU set if initial read
end of file/medium updated if a gap precedes, else unchanged and PNU set
tape mark updated
other marker updated
tape runaway updated
data record updated
This routine is called to space over a record or metadatum marker in the
forward direction. On return, status is MTSE_OK if a data record, private
marker, or reserved marker was read, or an MTSE error code if a standard
marker (e.g. tape mark) was read, or an error occurred. In all cases, the
successfully read marker or data record length word is returned via the "bc"
pointer.
When the extended SIMH format is enabled, then the variable addressed by the
"bc" parameter must be set on entry to a bitmap of the object classes to
return. Each of the classes is represented by its corresponding bit, i.e.,
bit 0 represents class 0, bit 1 for class 1, etc. The routine will return
only objects from the selected classes. Unselected class objects will be
ignored by skipping over them until the first selected class object is seen.
This allows a simulator to declare those classes it understands (e.g.,
standard classes 0 and 8, plus private classes 2 and 7) and those classes it
wishes to ignore. Erase gap markers are always skipped, and standard markers
are always returned, so specifying an empty bitmap will perform the
equivalent of a "space file forward," returning only when a tape mark or
EOM/EOF is encountered.
When standard SIMH format is enabled, standard classes 0 and 8 are
automatically selected, and the entry value addressed by "bc" is ignored.
If the PNU ("position not updated") flag is set, then an error prevented a
preceding tape read or write command from moving the tape position. A space
command immediately following such a failed command is assumed to be part of
a reposition-and-retry error recovery sequence. Because the tape was not
actually moved, we skip the corresponding reposition here, so that the tape
will be correctly positioned for the retry.
Implementation notes:
1. PNU is set by both read and write positioning failures. A retry sequence
would match a space reverse with a forward read, and vice versa. We do
not maintain separate PNUs for forward and reverse operations, so a space
forward following a failed read forward would not move the tape.
However, this condition is deemed so unlikely as not to warrant keeping
direction-specific PNU flags.
*/
t_stat sim_tape_sprecf (UNIT *uptr, t_mtrlnt *bc)
{
const uint32 f = MT_GET_FMT (uptr);
t_stat st;
if (MT_TST_PNU (uptr)) { /* if the PNU flag is set */
MT_CLR_PNU (uptr); /* then clear it */
*bc = 0; /* report the record length as zero */
return MTSE_OK; /* and return with no tape motion */
}
st = sim_tape_rdlntf (uptr, bc); /* get the record length */
if (f != MTUF_F_EXT) /* if the format is not extended SIMH */
*bc = MTR_RL (*bc) & MTR_MAXLEN; /* then return just the record length */
return st;
}
/* Space record reverse.
Inputs:
uptr = pointer to tape unit
bc = pointer to returned record length or marker
Outputs:
status = operation status
exit condition tape position
------------------ -----------------------------------------------------
unit unattached unchanged
beginning of tape unchanged
read error unchanged, PNU set if initial read
tape mark updated
other marker updated
tape runaway updated
data record updated
This routine is called to space over a record or metadatum marker in the
reverse direction. On return, status is MTSE_OK if a data record, private
marker, or reserved marker was read, or an MTSE error code if a standard
marker (e.g. tape mark) was read, or an error occurred. In all cases, the
successfully read marker or data record length word is returned via the "bc"
pointer.
See the comments for the "sim_tape_sprecr" routine above for additional
considerations.
*/
t_stat sim_tape_sprecr (UNIT *uptr, t_mtrlnt *bc)
{
const uint32 f = MT_GET_FMT (uptr);
t_stat st;
if (MT_TST_PNU (uptr)) { /* if the PNU flag is set */
MT_CLR_PNU (uptr); /* then clear it */
*bc = 0; /* report the record length as zero */
return MTSE_OK; /* and return with no tape motion */
}
st = sim_tape_rdlntr (uptr, bc); /* get the record length */
if (f != MTUF_F_EXT) /* if the format is not extended SIMH */
*bc = MTR_RL (*bc) & MTR_MAXLEN; /* then return just the record length */
return st;
}
/* Rewind tape */
t_stat sim_tape_rewind (UNIT *uptr)
{
uptr->pos = 0;
MT_CLR_PNU (uptr);
return MTSE_OK;
}
/* Reset tape */
t_stat sim_tape_reset (UNIT *uptr)
{
MT_CLR_PNU (uptr);
return MTSE_OK;
}
/* Test for BOT */
t_bool sim_tape_bot (UNIT *uptr)
{
uint32 f = MT_GET_FMT (uptr);
return (uptr->pos <= fmts[f].bot) ? TRUE : FALSE;
}
/* Test for end of tape */
t_bool sim_tape_eot (UNIT *uptr)
{
return (uptr->capac && (uptr->pos >= uptr->capac)) ? TRUE : FALSE;
}
/* Test for write protect */
t_bool sim_tape_wrp (UNIT *uptr)
{
return ((uptr->flags & MTUF_WRP) || (MT_GET_FMT (uptr) == MTUF_F_TPC)) ? TRUE : FALSE;
}
/* Process I/O error */
static t_stat sim_tape_ioerr (UNIT *uptr)
{
perror ("Magtape library I/O error");
clearerr (uptr->fileref);
return MTSE_IOERR;
}
/* Set the tape format.
This validation routine is called to change the tape format of the unit
addressed by "uptr". If "desc" is NULL, then the string pointed to by "cptr"
must contain one of the defined format names, and "val" is ignored.
Otherwise, "desc" must point to a "uint32" variable that receives the current
tape format code, "val" must contain one of the MTUF_F_* constants in
sim_tape.h that specifies the new format code, and "cptr" is ignored.
If "cptr" specifies the new format name, the "fmts" table is searched for a
matching entry; if one is not found, SCPE_ARG is returned. The "SIMH" name
is used for both SIMH Standard and SIMH Extended format; which is selected
depends on whether the unit was set for extended format when the routine was
first entered. A unit declared with the MT_F_EXT flag indicates that it is
prepared to handle the extended-format calling sequence, so switching back to
"SIMH" format from another format returns the unit to SIMH Extended format.
In contrast, a unit originally declared with another format, including
MT_F_STD, indicates that it is not prepared to handle the extended format, so
switching back returns to SIMH Standard format.
If "val" specifies the new format, this check is skipped. This allows a
simulator to change to SIMH Extended format temporarily, e.g., to run
diagnostics, while ensuring that extended-format records are ignored during
normal simulator operation.
Implementation notes:
1. "fmts" table entries with blank (not NULL) names correspond to format
codes that are reserved for future use. They are skipped automatically
during the name search because the "cptr" string is stripped of blanks
before entry and thus will never match a table entry with a blank name.
2. While the "fmts" table contains entries for both SIMH Standard and SIMH
Extended formats, both entries use the same "SIMH" name. Therefore, only
the first (standard format) entry will match during the search.
*/
t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, char *cptr, void *desc)
{
uint32 *old_fmt = (uint32 *) desc; /* a pointer to receive the current format */
if (uptr == NULL) /* if the unit is not supplied */
return SCPE_IERR; /* then report an internal error */
else if (uptr->flags & UNIT_ATT) /* otherwise if the unit is attached */
return SCPE_ALATT; /* then report an already attached error */
else if (desc == NULL) { /* otherwise if the format is set interactively */
if (cptr == NULL || *cptr == '\0') /* then if there is no format keyword */
return SCPE_ARG; /* then report an argument error */
if (MT_GET_FMT (uptr) == MTUF_F_EXT) /* if the unit accepts extended SIMH format */
uptr->dynflags |= UNIT_EXTEND; /* then enable changing back to that format */
for (val = 0; val < (int32) FMT_COUNT; val++) /* loop through the format name table */
if (MATCH_CMD (cptr, fmts [val].name) == 0) { /* if the name matches */
if (val == MTUF_F_STD /* then if it's SIMH format */
&& (uptr->dynflags & UNIT_EXTEND)) /* and the unit handles extended format */
val = MTUF_F_EXT; /* then request extended format */
uptr->flags = (uptr->flags & ~MTUF_FMT) /* change to the new format */
| fmts [val].uflags; /* including any required unit flags */
return SCPE_OK; /* and return success */
}
return SCPE_ARG; /* the name is not in the format table */
}
else /* otherwise set the format programmatically */
if (val < 0 || val >= (int32) FMT_COUNT) /* then the supplied value is out of range */
return SCPE_ARG; /* then report an argument error */
else { /* otherwise the format code is valid */
*old_fmt = MT_GET_FMT (uptr); /* so return the current code */
uptr->flags = (uptr->flags & ~MTUF_FMT) /* change to the new format */
| fmts [val].uflags; /* including any required unit flags */
return SCPE_OK; /* and return success */
}
}
/* Show tape format */
t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, void *desc)
{
int32 f = MT_GET_FMT (uptr);
if (f < (int32) FMT_COUNT && fmts [f].name [0] != ' ')
fprintf (st, "%s format", fmts [f].name);
else
fprintf (st, "invalid format");
return SCPE_OK;
}
/* Map a TPC format tape image */
static uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map)
{
t_addr tpos;
t_tpclnt bc;
uint32 i, objc;
if ((uptr == NULL) || (uptr->fileref == NULL))
return 0;
for (objc = 0, tpos = 0;; ) {
sim_fseek (uptr->fileref, tpos, SEEK_SET);
i = sim_fread (&bc, sizeof (t_tpclnt), 1, uptr->fileref);
if (i == 0)
break;
if (map)
map[objc] = tpos;
objc++;
tpos = tpos + ((bc + 1) & ~1) + sizeof (t_tpclnt);
}
if (map) map[objc] = tpos;
return objc;
}
/* Find the preceding record in a TPC file */
static t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map)
{
uint32 lo, hi, p;
if (map == NULL)
return 0;
lo = 0;
hi = uptr->hwmark - 1;
do {
p = (lo + hi) >> 1;
if (uptr->pos == map[p])
return ((p == 0) ? map[p] : map[p - 1]);
else if (uptr->pos < map[p])
hi = p - 1;
else lo = p + 1;
}
while (lo <= hi);
return ((p == 0) ? map[p] : map[p - 1]);
}
/* Set tape capacity */
t_stat sim_tape_set_capac (UNIT *uptr, int32 val, char *cptr, void *desc)
{
t_addr cap;
t_stat r;
if ((cptr == NULL) || (*cptr == 0))
return SCPE_ARG;
if (uptr->flags & UNIT_ATT)
return SCPE_ALATT;
cap = (t_addr) get_uint (cptr, 10, sim_taddr_64 ? 2000000 : 2000, &r);
if (r != SCPE_OK)
return SCPE_ARG;
uptr->capac = cap * ((t_addr) 1000000);
return SCPE_OK;
}
/* Show tape capacity */
t_stat sim_tape_show_capac (FILE *st, UNIT *uptr, int32 val, void *desc)
{
if (uptr->capac) {
if (uptr->capac >= (t_addr) 1000000)
fprintf (st, "capacity=%dMB", (uint32) (uptr->capac / ((t_addr) 1000000)));
else if (uptr->capac >= (t_addr) 1000)
fprintf (st, "capacity=%dKB", (uint32) (uptr->capac / ((t_addr) 1000)));
else fprintf (st, "capacity=%dB", (uint32) uptr->capac);
}
else fprintf (st, "unlimited capacity");
return SCPE_OK;
}
/* Set the tape density.
Set the density of the specified tape unit either to the value supplied or to
the value represented by the supplied character string.
If "desc" is NULL, then "val" must be set to one of the MT_DENS_* constants
in sim_tape.h other than MT_DENS_NONE; the supplied value is used as the tape
density, and the character string is ignored. Otherwise, "desc" must point
at an int32 value containing a set of allowed densities constructed as a
bitwise OR of the appropriate MT_*_VALID values. In this case, the string
pointed to by "cptr" will be parsed for a decimal value corresponding to the
desired density in bits per inch and validated against the set of allowed
values.
In either case, SCPE_ARG is returned if the density setting is not valid or
allowed. If the setting is OK, the new density is set into the unit
structure, and SCPE_OK is returned.
*/
t_stat sim_tape_set_dens (UNIT *uptr, int32 val, char *cptr, void *desc)
{
uint32 density, new_bpi;
t_stat result = SCPE_OK;
if (uptr == NULL) /* if the unit pointer is null */
return SCPE_IERR; /* then the caller has screwed up */
else if (desc == NULL) /* otherwise if a validation set was not supplied */
if (val > 0 && val < (int32) BPI_COUNT) /* then if a valid density code was supplied */
uptr->dynflags = uptr->dynflags & ~MTVF_DENS_MASK /* then insert the code */
| val << UNIT_V_DF_TAPE; /* in the unit flags */
else /* otherwise the code is invalid */
return SCPE_ARG; /* so report a bad argument */
else { /* otherwise a validation set was supplied */
if (cptr == NULL || *cptr == 0) /* but if no value is present */
return SCPE_MISVAL; /* then report a missing value */
new_bpi = (uint32) get_uint (cptr, 10, UINT_MAX, &result); /* convert the string value */
if (result != SCPE_OK) /* if the conversion failed */
result = SCPE_ARG; /* then report a bad argument */
else for (density = 0; density < BPI_COUNT; density++) /* otherwise validate the density */
if (new_bpi == bpi [density] /* if it matches a value in the list */
&& ((1 << density) & *(int32 *) desc)) { /* and it's an allowed value */
uptr->dynflags = uptr->dynflags & ~MTVF_DENS_MASK /* then store the index of the value */
| density << UNIT_V_DF_TAPE; /* in the unit flags */
return SCPE_OK; /* and return success */
}
result = SCPE_ARG; /* if no match, then report a bad argument */
}
return result; /* return the result of the operation */
}
/* Show the tape density */
t_stat sim_tape_show_dens (FILE *st, UNIT *uptr, int32 val, void *desc)
{
uint32 tape_density;
if (uptr == NULL) /* if the unit pointer is null */
return SCPE_IERR; /* then the caller has screwed up */
else { /* otherwise get the density */
tape_density = bpi [MT_DENS (uptr->dynflags)]; /* of the tape from the unit flags */
if (tape_density) /* if it's set */
fprintf (st, "density=%d bpi", tape_density); /* then report it */
else /* otherwise */
fprintf (st, "density not set"); /* it was never set by the caller */
}
return SCPE_OK;
}