mirror of
https://github.com/simh/simh.git
synced 2026-01-11 23:52:58 +00:00
- Improved tape_erase_fwd corrupt image error checking - Added sim_tape_erase global, tape_erase internal functions
2276 lines
111 KiB
C
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;
|
|
}
|