1 /* sim_tape.c: simulator tape support library
2 
3    Copyright (c) 1993-2008 Robert M Supnik
4    Copyright (c) 2021 The DPS8M Development Team
5 
6    Permission is hereby granted, free of charge, to any person obtaining a
7    copy of this software and associated documentation files (the "Software"),
8    to deal in the Software without restriction, including without limitation
9    the rights to use, copy, modify, merge, publish, distribute, sublicense,
10    and/or sell copies of the Software, and to permit persons to whom the
11    Software is furnished to do so, subject to the following conditions:
12 
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15 
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19    ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 
23    Except as contained in this notice, the name of Robert M Supnik shall not be
24    used in advertising or otherwise to promote the sale, use or other dealings
25    in this Software without prior written authorization from Robert M Supnik.
26 */
27 
28 /*
29    Public routines:
30 
31    sim_tape_attach      attach tape unit
32    sim_tape_detach      detach tape unit
33    sim_tape_attach_help help routine for attaching tapes
34    sim_tape_rdrecf      read tape record forward
35    sim_tape_rdrecr      read tape record reverse
36    sim_tape_wrrecf      write tape record forward
37    sim_tape_sprecf      space tape record forward
38    sim_tape_sprecr      space tape record reverse
39    sim_tape_wrtmk       write tape mark
40    sim_tape_wreom       erase remainder of tape
41    sim_tape_wreomrw     erase remainder of tape & rewind
42    sim_tape_wrgap       write erase gap
43    sim_tape_sprecsf     space records forward
44    sim_tape_spfilef     space files forward
45    sim_tape_sprecsr     space records reverse
46    sim_tape_spfiler     space files reverse
47    sim_tape_position    generalized position
48    sim_tape_rewind      rewind
49    sim_tape_reset       reset unit
50    sim_tape_bot         TRUE if at beginning of tape
51    sim_tape_eot         TRUE if at or beyond end of tape
52    sim_tape_wrp         TRUE if write protected
53    sim_tape_set_fmt     set tape format
54    sim_tape_show_fmt    show tape format
55    sim_tape_set_capac   set tape capacity
56    sim_tape_show_capac  show tape capacity
57    sim_tape_set_dens    set tape density
58    sim_tape_show_dens   show tape density
59 */
60 
61 #include "sim_defs.h"
62 #include "sim_tape.h"
63 #include <ctype.h>
64 
65 struct sim_tape_fmt {
66     const char          *name;                          /* name */
67     int32               uflags;                         /* unit flags */
68     t_addr              bot;                            /* bot test */
69     };
70 
71 static struct sim_tape_fmt fmts[MTUF_N_FMT] = {
72     { "SIMH", 0,       sizeof (t_mtrlnt) - 1 },
73     { "E11",  0,       sizeof (t_mtrlnt) - 1 },
74     { "TPC",  UNIT_RO, sizeof (t_tpclnt) - 1 },
75     { "P7B",  0,       0 },
76 /*  { "TPF",  UNIT_RO, 0 }, */
77     { NULL,   0,       0 }
78     };
79 
80 static const uint32 bpi [] = {                          /* tape density table, indexed by MT_DENS constants */
81     0,                                                  /*   0 = MT_DENS_NONE -- density not set */
82     200,                                                /*   1 = MT_DENS_200  -- 200 bpi NRZI */
83     556,                                                /*   2 = MT_DENS_556  -- 556 bpi NRZI */
84     800,                                                /*   3 = MT_DENS_800  -- 800 bpi NRZI */
85     1600,                                               /*   4 = MT_DENS_1600 -- 1600 bpi PE */
86     6250                                                /*   5 = MT_DENS_6250 -- 6250 bpi GCR */
87     };
88 
89 #define BPI_COUNT       (sizeof (bpi) / sizeof (bpi [0]))   /* count of density table entries */
90 
91 static t_stat sim_tape_ioerr (UNIT *uptr);
92 static t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat);
93 static uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map, uint32 mapsize);
94 static t_stat sim_tape_simh_check (UNIT *uptr);
95 static t_stat sim_tape_e11_check (UNIT *uptr);
96 static t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map);
97 static void sim_tape_data_trace (UNIT *uptr, const uint8 *data, size_t len, const char* txt, int detail, uint32 reason);
98 
99 
100 struct tape_context {
101     DEVICE              *dptr;              /* Device for unit (access to debug flags) */
102     uint32              dbit;               /* debugging bit for trace */
103     uint32              auto_format;        /* Format determined dynamically */
104     };
105 #define tape_ctx up8                        /* Field in Unit structure which points to the tape_context */
106 
107 /*
108    This routine is called when the simulator stops and any time
109    the asynch mode is changed (enabled or disabled)
110 */
_sim_tape_io_flush(UNIT * uptr)111 static void _sim_tape_io_flush (UNIT *uptr)
112 {
113 fflush (uptr->fileref);
114 }
115 
116 /* Attach tape unit */
117 
sim_tape_attach(UNIT * uptr,CONST char * cptr)118 t_stat sim_tape_attach (UNIT *uptr, CONST char *cptr)
119 {
120 DEVICE *dptr;
121 
122 if ((dptr = find_dev_from_unit (uptr)) == NULL)
123     return SCPE_NOATT;
124 return sim_tape_attach_ex (uptr, cptr, (dptr->flags & DEV_DEBUG) ? 0xFFFFFFFF : 0, 0);
125 }
126 
sim_tape_attach_ex(UNIT * uptr,const char * cptr,uint32 dbit,int completion_delay)127 t_stat sim_tape_attach_ex (UNIT *uptr, const char *cptr, uint32 dbit, int completion_delay)
128 {
129 struct tape_context *ctx;
130 uint32 objc;
131 DEVICE *dptr;
132 char gbuf[CBUFSIZE];
133 t_stat r;
134 t_bool auto_format = FALSE;
135 
136 if ((dptr = find_dev_from_unit (uptr)) == NULL)
137     return SCPE_NOATT;
138 if (sim_switches & SWMASK ('F')) {                      /* format spec? */
139     cptr = get_glyph (cptr, gbuf, 0);                   /* get spec */
140     if (*cptr == 0)                                     /* must be more */
141         return SCPE_2FARG;
142     if (sim_tape_set_fmt (uptr, 0, gbuf, NULL) != SCPE_OK)
143         return sim_messagef (SCPE_ARG, "Invalid Tape Format: %s\n", gbuf);
144     sim_switches = sim_switches & ~(SWMASK ('F'));      /* Record Format specifier already processed */
145     auto_format = TRUE;
146     }
147 if (MT_GET_FMT (uptr) == MTUF_F_TPC)
148     sim_switches |= SWMASK ('R');                       /* Force ReadOnly attach for TPC tapes */
149 r = attach_unit (uptr, (CONST char *)cptr);             /* attach unit */
150 if (r != SCPE_OK)                                       /* error? */
151     return sim_messagef (r, "Can't open tape image: %s\n", cptr);
152 switch (MT_GET_FMT (uptr)) {                            /* case on format */
153 
154     case MTUF_F_STD:                                    /* SIMH */
155         if (SCPE_OK != sim_tape_simh_check (uptr)) {
156             sim_tape_detach (uptr);
157             return SCPE_FMT;                            /* yes, complain */
158             }
159         break;
160 
161     case MTUF_F_E11:                                    /* E11 */
162         if (SCPE_OK != sim_tape_e11_check (uptr)) {
163             sim_tape_detach (uptr);
164             return SCPE_FMT;                            /* yes, complain */
165             }
166         break;
167 
168     case MTUF_F_TPC:                                    /* TPC */
169         objc = sim_tape_tpc_map (uptr, NULL, 0);        /* get # objects */
170         if (objc == 0) {                                /* tape empty? */
171             sim_tape_detach (uptr);
172             return SCPE_FMT;                            /* yes, complain */
173             }
174         uptr->filebuf = calloc (objc + 1, sizeof (t_addr));
175         if (uptr->filebuf == NULL) {                    /* map allocated? */
176             sim_tape_detach (uptr);
177             return SCPE_MEM;                            /* no, complain */
178             }
179         uptr->hwmark = objc + 1;                        /* save map size */
180         sim_tape_tpc_map (uptr, (t_addr *) uptr->filebuf, objc);/* fill map */
181         break;
182 
183     default:
184         break;
185         }
186 
187 uptr->tape_ctx = ctx = (struct tape_context *)calloc(1, sizeof(struct tape_context));
188 ctx->dptr = dptr;                                       /* save DEVICE pointer */
189 ctx->dbit = dbit;                                       /* save debug bit */
190 ctx->auto_format = auto_format;                         /* save that we auto selected format */
191 
192 sim_tape_rewind (uptr);
193 
194 uptr->io_flush = _sim_tape_io_flush;
195 
196 return SCPE_OK;
197 }
198 
199 /* Detach tape unit */
200 
sim_tape_detach(UNIT * uptr)201 t_stat sim_tape_detach (UNIT *uptr)
202 {
203 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
204 uint32 f = 0;
205 t_stat r;
206 t_bool auto_format = FALSE;
207 
208 if (uptr != NULL)
209     f = MT_GET_FMT (uptr);
210 
211 if ((ctx == NULL) || !(uptr->flags & UNIT_ATT))
212     return SCPE_IERR;
213 
214 if (uptr->io_flush)
215     uptr->io_flush (uptr);                              /* flush buffered data */
216 if (ctx)
217     auto_format = ctx->auto_format;
218 
219 r = detach_unit (uptr);                                 /* detach unit */
220 if (r != SCPE_OK)
221     return r;
222 switch (f) {                                            /* case on format */
223 
224     case MTUF_F_TPC:                                    /* TPC */
225         if (uptr->filebuf)                              /* free map */
226             free (uptr->filebuf);
227         uptr->filebuf = NULL;
228         uptr->hwmark = 0;
229         break;
230 
231     default:
232         break;
233         }
234 
235 sim_tape_rewind (uptr);
236 free (uptr->tape_ctx);
237 uptr->tape_ctx = NULL;
238 uptr->io_flush = NULL;
239 if (auto_format)    /* format was determined or specified at attach time? */
240     sim_tape_set_fmt (uptr, 0, "SIMH", NULL);   /* restore default format */
241 return SCPE_OK;
242 }
243 
sim_tape_attach_help(FILE * st,DEVICE * dptr,const UNIT * uptr,int32 flag,const char * cptr)244 t_stat sim_tape_attach_help(FILE *st, DEVICE *dptr, const UNIT *uptr, int32 flag, const char *cptr)
245 {
246 fprintf (st, "%s Tape Attach Help\n\n", dptr->name);
247 if (0 == (uptr-dptr->units)) {
248     if (dptr->numunits > 1) {
249         uint32 i;
250 
251         for (i=0; i < dptr->numunits; ++i)
252             if (dptr->units[i].flags & UNIT_ATTABLE)
253                 fprintf (st, "  sim> ATTACH {switches} %s%lu tapefile\n\n", dptr->name, (unsigned long)i);
254         }
255     else
256         fprintf (st, "  sim> ATTACH {switches} %s tapefile\n\n", dptr->name);
257     }
258 else
259     fprintf (st, "  sim> ATTACH {switches} %s tapefile\n\n", dptr->name);
260 fprintf (st, "Attach command switches\n");
261 fprintf (st, "    -R          Attach Read Only.\n");
262 fprintf (st, "    -E          Must Exist (if not specified an attempt to create the indicated\n");
263 fprintf (st, "                virtual tape will be attempted).\n");
264 fprintf (st, "    -F          Open the indicated tape container in a specific format (default\n");
265 fprintf (st, "                is SIMH, alternatives are E11, TPC and P7B)\n");
266 return SCPE_OK;
267 }
268 
sim_tape_data_trace(UNIT * uptr,const uint8 * data,size_t len,const char * txt,int detail,uint32 reason)269 static void sim_tape_data_trace(UNIT *uptr, const uint8 *data, size_t len, const char* txt, int detail, uint32 reason)
270 {
271 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
272 
273 if (ctx == NULL)
274     return;
275 if (sim_deb && (ctx->dptr->dctrl & reason))
276     sim_data_trace(ctx->dptr, uptr, (detail ? data : NULL), "", len, txt, reason);
277 }
278 
279 /* Read record length forward (internal routine)
280 
281    Inputs:
282         uptr    =       pointer to tape unit
283         bc      =       pointer to returned record length
284    Outputs:
285         status  =       operation status
286 
287    exit condition       tape position
288    ------------------   -----------------------------------------------------
289    unit unattached      unchanged
290    read error           unchanged, PNU set
291    end of file/medium   updated if a gap precedes, else unchanged and PNU set
292    tape mark            updated
293    tape runaway         updated
294    data record          updated, sim_fread will read record forward
295 
296    This routine is called to set up a record read or spacing in the forward
297    direction.  On return, status is MTSE_OK and the tape is positioned at the
298    first data byte if a record was encountered, or status is an MTSE error code
299    giving the reason that the operation did not succeed and the tape position is
300    as indicated above.
301 
302    The ANSI standards for magnetic tape recording (X3.32, X3.39, and X3.54) and
303    the equivalent ECMA standard (ECMA-62) specify a maximum erase gap length of
304    25 feet (7.6 meters).  While gaps of any length may be written, gaps longer
305    than this are non-standard and may indicate that an unrecorded or erased tape
306    is being read.
307 
308    If the tape density has been set via a previous "sim_tape_set_dens" call,
309    then the length is monitored when skipping over erase gaps.  If the length
310    reaches 25 feet, motion is terminated, and MTSE_RUNAWAY status is returned.
311    Runaway status is also returned if an end-of-medium marker or the physical
312    end of file is encountered while spacing over a gap; however, MTSE_EOM is
313    returned if the tape is positioned at the EOM on entry.
314 
315    If the density has not been set, then a gap of any length is skipped, and
316    MTSE_RUNAWAY status is never returned.  In effect, erase gaps present in the
317    tape image file will be transparent to the caller.
318 
319    Erase gaps are currently supported only in SIMH (MTUF_F_STD) tape format.
320    Because gaps may be partially overwritten with data records, gap metadata
321    must be examined marker-by-marker.  To reduce the number of file read calls,
322    a buffer of metadata elements is used.  The buffer size is initially
323    established at 256 elements but may be set to any size desired.  To avoid a
324    large read for the typical case where an erase gap is not present, the first
325    read is of a single metadatum marker.  If that is a gap marker, then
326    additional buffered reads are performed.
327 
328    See the notes at "sim_tape_wrgap" regarding the erase gap implementation.
329 
330    Implementation notes:
331 
332     1. For programming convenience, erase gap processing is performed for both
333        SIMH standard and E11 tape formats, although the latter will never
334        contain erase gaps, as the "sim_tape_wrgap" call takes no action for the
335        E11 format.
336 
337     2. The "feof" call cannot return a non-zero value on the first pass through
338        the loop, because the "sim_fseek" call resets the internal end-of-file
339        indicator.  Subsequent passes only occur if an erase gap is present, so
340        a non-zero return indicates an EOF was seen while reading through a gap.
341 
342     3. The dynamic start/stop test of the HP 3000 magnetic tape diagnostic
343        heavily exercises the erase gap scanning code.  Sample test execution
344        times for various buffer sizes on a 2 GHz host platform are:
345 
346          buffer size    execution time
347          (elements)     (CPU seconds)
348          -----------    --------------
349                1             7200
350               32              783
351              128              237
352              256              203
353              512              186
354             1024              171
355 
356     4. Because an erase gap may precede the logical end-of-medium, represented
357        either by the physical end-of-file or by an EOM marker, the "position not
358        updated" flag is set only if the tape is positioned at the EOM when the
359        routine is entered.  If at least one gap marker precedes the EOM, then
360        the PNU flag is not set.  This ensures that a backspace-and-retry
361        sequence will work correctly in both cases.
362 */
363 
sim_tape_rdlntf(UNIT * uptr,t_mtrlnt * bc)364 static t_stat sim_tape_rdlntf (UNIT *uptr, t_mtrlnt *bc)
365 {
366 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
367 uint8 c;
368 t_bool all_eof;
369 uint32 f = MT_GET_FMT (uptr);
370 t_mtrlnt sbc;
371 t_tpclnt tpcbc;
372 t_mtrlnt buffer [256];                                  /* local tape buffer */
373 uint32 bufcntr, bufcap;                                 /* buffer counter and capacity */
374 int32 runaway_counter, sizeof_gap;                      /* bytes remaining before runaway and bytes per gap */
375 t_stat r = MTSE_OK;
376 
377 MT_CLR_PNU (uptr);                                      /* clear the position-not-updated flag */
378 
379 if ((uptr->flags & UNIT_ATT) == 0)                      /* if the unit is not attached */
380     return MTSE_UNATT;                                  /*   then quit with an error */
381 if (ctx == NULL)                                        /* if not properly attached? */
382     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
383 
384 sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);         /* set the initial tape position */
385 
386 switch (f) {                                            /* the read method depends on the tape format */
387 
388     case MTUF_F_STD:
389     case MTUF_F_E11:
390         runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set the largest legal gap size in bytes */
391 
392         if (runaway_counter == 0) {                     /* if tape density has not been not set */
393             sizeof_gap = 0;                             /*   then disable runaway detection */
394             runaway_counter = INT_MAX;                  /*     to allow gaps of any size */
395             }
396         else                                            /* otherwise */
397             sizeof_gap = sizeof (t_mtrlnt);             /*   set the size of the gap */
398 
399         bufcntr = 0;                                    /* force an initial read */
400         bufcap = 0;                                     /*   but of just one metadata marker */
401 
402         do {                                            /* loop until a record, gap, or error is seen */
403             if (bufcntr == bufcap) {                    /* if the buffer is empty then refill it */
404                 if (feof (uptr->fileref)) {             /* if we hit the EOF while reading a gap */
405                     if (sizeof_gap > 0)                 /*   then if detection is enabled */
406                         r = MTSE_RUNAWAY;               /*     then report a tape runaway */
407                     else                                /*   otherwise report the physical EOF */
408                         r = MTSE_EOM;                   /*     as the end-of-medium */
409                     break;
410                     }
411 
412                 else if (bufcap == 0)                   /* otherwise if this is the initial read */
413                     bufcap = 1;                         /*   then start with just one marker */
414 
415                 else                                    /* otherwise reset the capacity */
416                     bufcap = sizeof (buffer)            /*   to the full size of the buffer */
417                                / sizeof (buffer [0]);
418 
419                 bufcap = sim_fread (buffer,             /* fill the buffer */
420                                     sizeof (t_mtrlnt),  /*   with tape metadata */
421                                     bufcap,
422                                     uptr->fileref);
423 
424                 if (ferror (uptr->fileref)) {           /* if a file I/O error occurred */
425                     if (bufcntr == 0)                   /*   then if this is the initial read */
426                         MT_SET_PNU (uptr);              /*     then set position not updated */
427 
428                     r = sim_tape_ioerr (uptr);          /* report the error and quit */
429                     break;
430                     }
431 
432                 else if (bufcap == 0                    /* otherwise if positioned at the physical EOF */
433                   || buffer [0] == MTR_EOM)             /*   or at the logical EOM */
434                     if (bufcntr == 0) {                 /*     then if this is the initial read */
435                         MT_SET_PNU (uptr);              /*       then set position not updated */
436                         r = MTSE_EOM;                   /*         and report the end-of-medium and quit */
437                         break;
438                         }
439 
440                     else {                              /*     otherwise some gap has already been skipped */
441                         if (sizeof_gap > 0)             /*       so if detection is enabled */
442                             r = MTSE_RUNAWAY;           /*         then report a tape runaway */
443                         else                            /*       otherwise report the physical EOF */
444                             r = MTSE_EOM;               /*         as the end-of-medium */
445                         break;
446                         }
447 
448                 else                                    /* otherwise reset the index */
449                     bufcntr = 0;                        /*   to the start of the buffer */
450                 }
451 
452             *bc = buffer [bufcntr++];                   /* store the metadata marker value */
453 
454             if (*bc == MTR_EOM) {                       /* if an end-of-medium marker is seen */
455                 if (sizeof_gap > 0)                     /*   then if detection is enabled */
456                     r = MTSE_RUNAWAY;                   /*     then report a tape runaway */
457                 else                                    /*   otherwise report the physical EOF */
458                     r = MTSE_EOM;                       /*     as the end-of-medium */
459                 break;
460                 }
461 
462             uptr->pos = uptr->pos + sizeof (t_mtrlnt);  /* space over the marker */
463 
464             if (*bc == MTR_TMK) {                       /* if the value is a tape mark */
465                 r = MTSE_TMK;                           /*   then quit with tape mark status */
466                 break;
467                 }
468 
469             else if (*bc == MTR_GAP)                    /* otherwise if the value is a full gap */
470                 runaway_counter -= sizeof_gap;          /*   then decrement the gap counter */
471 
472             else if (*bc == MTR_FHGAP) {                        /* otherwise if the value if a half gap */
473                 uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2;  /*   then back up */
474                 (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /*     to resync */
475                 bufcntr = bufcap;                               /* mark the buffer as invalid to force a read */
476 
477                 *bc = MTR_GAP;                                  /* reset the marker */
478                 runaway_counter -= sizeof_gap / 2;              /*   and decrement the gap counter */
479                 }
480 
481             else {                                                  /* otherwise it's a record marker */
482                 if (bufcntr < bufcap)                               /* if the position is within the buffer */
483                     (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /*   then seek to the data area */
484 
485                 sbc = MTR_L (*bc);                                  /* extract the record length */
486                 uptr->pos = uptr->pos + sizeof (t_mtrlnt)           /* position to the start */
487                   + (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc);       /*   of the record */
488                 }
489             }
490         while (*bc == MTR_GAP && runaway_counter > 0);  /* continue until data or runaway occurs */
491 
492         if (r == MTSE_OK && runaway_counter <= 0)       /* if a tape runaway occurred */
493             r = MTSE_RUNAWAY;                           /*   then report it */
494 
495         break;                                          /* otherwise the operation succeeded */
496 
497     case MTUF_F_TPC:
498         (void)sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref);
499         *bc = tpcbc;                                    /* save rec lnt */
500         if (ferror (uptr->fileref)) {                   /* error? */
501             MT_SET_PNU (uptr);                          /* pos not upd */
502             return sim_tape_ioerr (uptr);
503             }
504         if (feof (uptr->fileref)) {                     /* eof? */
505             MT_SET_PNU (uptr);                          /* pos not upd */
506             r = MTSE_EOM;
507             break;
508             }
509         uptr->pos = uptr->pos + sizeof (t_tpclnt);      /* spc over reclnt */
510         if (tpcbc == TPC_TMK)                           /* tape mark? */
511             r = MTSE_TMK;
512         uptr->pos = uptr->pos + ((tpcbc + 1) & ~1);     /* spc over record */
513         break;
514 
515     case MTUF_F_P7B:
516         for (sbc = 0, all_eof = 1; ; sbc++) {           /* loop thru record */
517             (void)sim_fread (&c, sizeof (uint8), 1, uptr->fileref);
518             if (ferror (uptr->fileref)) {               /* error? */
519                 MT_SET_PNU (uptr);                      /* pos not upd */
520                 return sim_tape_ioerr (uptr);
521                 }
522             if (feof (uptr->fileref)) {                 /* eof? */
523                 if (sbc == 0)                           /* no data? eom */
524                     return MTSE_EOM;
525                 break;                                  /* treat like eor */
526                 }
527             if ((sbc != 0) && (c & P7B_SOR))            /* next record? */
528                 break;
529             if ((c & P7B_DPAR) != P7B_EOF)
530                 all_eof = 0;
531             }
532         *bc = sbc;                                      /* save rec lnt */
533         (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
534         uptr->pos = uptr->pos + sbc;                    /* spc over record */
535         if (all_eof)                                    /* tape mark? */
536             r = MTSE_TMK;
537         break;
538 
539     default:
540         return MTSE_FMT;
541         }
542 sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", r, *bc, uptr->pos);
543 return r;
544 }
545 
546 /* Read record length reverse (internal routine)
547 
548    Inputs:
549         uptr    =       pointer to tape unit
550         bc      =       pointer to returned record length
551    Outputs:
552         status  =       operation status
553 
554    exit condition       tape position
555    ------------------   -------------------------------------------
556    unit unattached      unchanged
557    beginning of tape    unchanged
558    read error           unchanged
559    end of file          unchanged
560    end of medium        updated
561    tape mark            updated
562    tape runaway         updated
563    data record          updated, sim_fread will read record forward
564 
565    This routine is called to set up a record read or spacing in the reverse
566    direction.  On return, status is MTSE_OK and the tape is positioned at the
567    first data byte if a record was encountered, or status is an MTSE error code
568    giving the reason that the operation did not succeed and the tape position is
569    as indicated above.
570 
571    See the notes at "sim_tape_rdlntf" and "sim_tape_wrgap" regarding tape
572    runaway and the erase gap implementation, respectively.
573 */
574 
sim_tape_rdlntr(UNIT * uptr,t_mtrlnt * bc)575 static t_stat sim_tape_rdlntr (UNIT *uptr, t_mtrlnt *bc)
576 {
577 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
578 uint8 c;
579 t_bool all_eof;
580 uint32 f = MT_GET_FMT (uptr);
581 t_addr ppos;
582 t_mtrlnt sbc;
583 t_tpclnt tpcbc;
584 t_mtrlnt buffer [256];                                  /* local tape buffer */
585 uint32 bufcntr, bufcap;                                 /* buffer counter and capacity */
586 int32 runaway_counter, sizeof_gap;                      /* bytes remaining before runaway and bytes per gap */
587 t_stat r = MTSE_OK;
588 
589 MT_CLR_PNU (uptr);                                      /* clear the position-not-updated flag */
590 
591 if ((uptr->flags & UNIT_ATT) == 0)                      /* if the unit is not attached */
592     return MTSE_UNATT;                                  /*   then quit with an error */
593 if (ctx == NULL)                                        /* if not properly attached? */
594     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
595 
596 if (sim_tape_bot (uptr))                                /* if the unit is positioned at the BOT */
597     return MTSE_BOT;                                    /*   then reading backward is not possible */
598 
599 switch (f) {                                            /* the read method depends on the tape format */
600 
601     case MTUF_F_STD:
602     case MTUF_F_E11:
603         runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set largest legal gap size in bytes */
604 
605         if (runaway_counter == 0) {                     /* if tape density has not been not set */
606             sizeof_gap = 0;                             /*   then disable runaway detection */
607             runaway_counter = INT_MAX;                  /*     to allow gaps of any size */
608             }
609 
610         else                                            /* otherwise */
611             sizeof_gap = sizeof (t_mtrlnt);             /*   set the size of the gap */
612 
613         bufcntr = 0;                                    /* force an initial read */
614         bufcap = 1;                                     /*   but of just one metadata marker */
615 
616         do {                                            /* loop until a record, gap, or error seen */
617             if (bufcntr == 0) {                         /* if the buffer is empty then refill it */
618                 if (sim_tape_bot (uptr)) {              /* if the search has backed into the BOT */
619                     r = MTSE_BOT;                       /*   then quit with an error */
620                     break;
621                     }
622 
623                 else if (uptr->pos < sizeof (buffer))   /* if less than a full buffer remains */
624                     bufcap = (uint32) uptr->pos         /*   then reduce the capacity accordingly */
625                                / sizeof (t_mtrlnt);
626 
627                 (void)sim_fseek (uptr->fileref,                           /* seek back to the location */
628                            uptr->pos - bufcap * sizeof (t_mtrlnt),  /*   corresponding to the start */
629                            SEEK_SET);                               /*     of the buffer */
630 
631                 bufcntr = sim_fread (buffer, sizeof (t_mtrlnt),     /* fill the buffer */
632                                      bufcap, uptr->fileref);        /*   with tape metadata */
633 
634                 if (ferror (uptr->fileref)) {           /* if a file I/O error occurred */
635                     MT_SET_PNU (uptr);                  /*   then set position not updated */
636                     r = sim_tape_ioerr (uptr);          /*     report the error and quit */
637                     break;
638                     }
639 
640                 else                                    /* otherwise reset the capacity */
641                     bufcap = sizeof (buffer)            /*   to the full size of the buffer */
642                                / sizeof (buffer [0]);
643                 }
644 
645             *bc = buffer [--bufcntr];                   /* store the metadata marker value */
646 
647             uptr->pos = uptr->pos - sizeof (t_mtrlnt);  /* backspace over the marker */
648 
649             if (*bc == MTR_TMK) {                       /* if the marker is a tape mark */
650                 r = MTSE_TMK;                           /*   then quit with tape mark status */
651                 break;
652                 }
653 
654             else if (*bc == MTR_GAP)                    /* otherwise if the marker is a full gap */
655                 runaway_counter -= sizeof_gap;          /*   then decrement the gap counter */
656 
657             else if ((*bc & MTR_M_RHGAP) == MTR_RHGAP           /* otherwise if the marker */
658               || *bc == MTR_RRGAP) {                            /*   is a half gap */
659                 uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2;  /*     then position forward to resync */
660                 bufcntr = 0;                                    /* mark the buffer as invalid to force a read */
661 
662                 *bc = MTR_GAP;                                  /* reset the marker */
663                 runaway_counter -= sizeof_gap / 2;              /*   and decrement the gap counter */
664                 }
665 
666             else {                                              /* otherwise it's a record marker */
667                 sbc = MTR_L (*bc);                              /* extract the record length */
668                 uptr->pos = uptr->pos - sizeof (t_mtrlnt)       /* position to the start */
669                   - (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc);   /*   of the record */
670                 (void)sim_fseek (uptr->fileref,                       /* seek to the data area */
671                            uptr->pos + sizeof (t_mtrlnt), SEEK_SET);
672                 }
673             }
674         while (*bc == MTR_GAP && runaway_counter > 0);  /* continue until data or runaway occurs */
675 
676         if (r == MTSE_OK && runaway_counter <= 0)       /* if a tape runaway occurred */
677             r = MTSE_RUNAWAY;                           /*   then report it */
678 
679         break;                                          /* otherwise the operation succeeded */
680 
681     case MTUF_F_TPC:
682         ppos = sim_tape_tpc_fnd (uptr, (t_addr *) uptr->filebuf); /* find prev rec */
683         (void)sim_fseek (uptr->fileref, ppos, SEEK_SET);      /* position */
684         (void)sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref);
685         *bc = tpcbc;                                    /* save rec lnt */
686         if (ferror (uptr->fileref))                     /* error? */
687             return sim_tape_ioerr (uptr);
688         if (feof (uptr->fileref)) {                     /* eof? */
689             r = MTSE_EOM;
690             break;
691             }
692         uptr->pos = ppos;                               /* spc over record */
693         if (*bc == MTR_TMK) {                           /* tape mark? */
694             r = MTSE_TMK;
695             break;
696             }
697         (void)sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET);
698         break;
699 
700     case MTUF_F_P7B:
701         for (sbc = 1, all_eof = 1; (t_addr) sbc <= uptr->pos ; sbc++) {
702             (void)sim_fseek (uptr->fileref, uptr->pos - sbc, SEEK_SET);
703             (void)sim_fread (&c, sizeof (uint8), 1, uptr->fileref);
704             if (ferror (uptr->fileref))                 /* error? */
705                 return sim_tape_ioerr (uptr);
706             if (feof (uptr->fileref)) {                 /* eof? */
707                 r = MTSE_EOM;
708                 break;
709                 }
710             if ((c & P7B_DPAR) != P7B_EOF)
711                 all_eof = 0;
712             if (c & P7B_SOR)                            /* start of record? */
713                 break;
714             }
715         uptr->pos = uptr->pos - sbc;                    /* update position */
716         *bc = sbc;                                      /* save rec lnt */
717         (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
718         if (all_eof)                                    /* tape mark? */
719             r = MTSE_TMK;
720         break;
721 
722     default:
723         return MTSE_FMT;
724         }
725 sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", r, *bc, uptr->pos);
726 return r;
727 }
728 
729 /* Read record forward
730 
731    Inputs:
732         uptr    =       pointer to tape unit
733         buf     =       pointer to buffer
734         bc      =       pointer to returned record length
735         max     =       maximum record size
736    Outputs:
737         status  =       operation status
738 
739    exit condition       position
740 
741    unit unattached      unchanged
742    read error           unchanged, PNU set
743    end of file/medium   unchanged, PNU set
744    invalid record       unchanged, PNU set
745    tape mark            updated
746    data record          updated
747    data record error    updated
748 */
749 
sim_tape_rdrecf(UNIT * uptr,uint8 * buf,t_mtrlnt * bc,t_mtrlnt max)750 t_stat sim_tape_rdrecf (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max)
751 {
752 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
753 uint32 f = MT_GET_FMT (uptr);
754 t_mtrlnt i, tbc, rbc;
755 t_addr opos;
756 t_stat st;
757 
758 if (ctx == NULL)                                        /* if not properly attached? */
759     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
760 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_rdrecf(unit=%d, buf=%p, max=%d)\n", (int)(uptr-ctx->dptr->units), buf, max);
761 
762 opos = uptr->pos;                                       /* old position */
763 if (MTSE_OK != (st = sim_tape_rdlntf (uptr, &tbc)))     /* read rec lnt */
764     return st;
765 *bc = rbc = MTR_L (tbc);                                /* strip error flag */
766 if (rbc > max) {                                        /* rec out of range? */
767     MT_SET_PNU (uptr);
768     uptr->pos = opos;
769     return MTSE_INVRL;
770     }
771 i = (t_mtrlnt)sim_fread (buf, sizeof (uint8), rbc, uptr->fileref);/* read record */
772 if (ferror (uptr->fileref)) {                           /* error? */
773     MT_SET_PNU (uptr);
774     uptr->pos = opos;
775     return sim_tape_ioerr (uptr);
776     }
777 for ( ; i < rbc; i++)                                   /* fill with 0's */
778     buf[i] = 0;
779 if (f == MTUF_F_P7B)                                    /* p7b? strip SOR */
780     buf[0] = buf[0] & P7B_DPAR;
781 sim_tape_data_trace(uptr, buf, rbc, "Record Read", ctx->dptr->dctrl & MTSE_DBG_DAT, MTSE_DBG_STR);
782 return (MTR_F (tbc)? MTSE_RECE: MTSE_OK);
783 }
784 
sim_tape_rdrecf_a(UNIT * uptr,uint8 * buf,t_mtrlnt * bc,t_mtrlnt max,TAPE_PCALLBACK callback)785 t_stat sim_tape_rdrecf_a (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max, TAPE_PCALLBACK callback)
786 {
787 t_stat r = SCPE_OK;
788     r = sim_tape_rdrecf (uptr, buf, bc, max);
789 return r;
790 }
791 
792 
793 /* Read record reverse
794 
795    Inputs:
796         uptr    =       pointer to tape unit
797         buf     =       pointer to buffer
798         bc      =       pointer to returned record length
799         max     =       maximum record size
800    Outputs:
801         status  =       operation status
802 
803    exit condition       position
804 
805    unit unattached      unchanged
806    read error           unchanged
807    end of file          unchanged
808    end of medium        updated
809    invalid record       unchanged
810    tape mark            updated
811    data record          updated
812    data record error    updated
813 */
814 
sim_tape_rdrecr(UNIT * uptr,uint8 * buf,t_mtrlnt * bc,t_mtrlnt max)815 t_stat sim_tape_rdrecr (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max)
816 {
817 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
818 uint32 f = MT_GET_FMT (uptr);
819 t_mtrlnt i, rbc, tbc;
820 t_stat st;
821 
822 if (ctx == NULL)                                        /* if not properly attached? */
823     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
824 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_rdrecr(unit=%d, buf=%p, max=%d)\n", (int)(uptr-ctx->dptr->units), buf, max);
825 
826 if (MTSE_OK != (st = sim_tape_rdlntr (uptr, &tbc)))     /* read rec lnt */
827     return st;
828 *bc = rbc = MTR_L (tbc);                                /* strip error flag */
829 if (rbc > max)                                          /* rec out of range? */
830     return MTSE_INVRL;
831 i = (t_mtrlnt)sim_fread (buf, sizeof (uint8), rbc, uptr->fileref);/* read record */
832 if (ferror (uptr->fileref))                             /* error? */
833     return sim_tape_ioerr (uptr);
834 for ( ; i < rbc; i++)                                   /* fill with 0's */
835     buf[i] = 0;
836 if (f == MTUF_F_P7B)                                    /* p7b? strip SOR */
837     buf[0] = buf[0] & P7B_DPAR;
838 sim_tape_data_trace(uptr, buf, rbc, "Record Read Reverse", ctx->dptr->dctrl & MTSE_DBG_DAT, MTSE_DBG_STR);
839 return (MTR_F (tbc)? MTSE_RECE: MTSE_OK);
840 }
841 
sim_tape_rdrecr_a(UNIT * uptr,uint8 * buf,t_mtrlnt * bc,t_mtrlnt max,TAPE_PCALLBACK callback)842 t_stat sim_tape_rdrecr_a (UNIT *uptr, uint8 *buf, t_mtrlnt *bc, t_mtrlnt max, TAPE_PCALLBACK callback)
843 {
844 t_stat r = SCPE_OK;
845     r = sim_tape_rdrecr (uptr, buf, bc, max);
846 return r;
847 }
848 
849 /* Write record forward
850 
851    Inputs:
852         uptr    =       pointer to tape unit
853         buf     =       pointer to buffer
854         bc      =       record length
855    Outputs:
856         status  =       operation status
857 
858    exit condition       position
859 
860    unit unattached      unchanged
861    write protect        unchanged
862    write error          unchanged, PNU set
863    data record          updated
864 */
865 
sim_tape_wrrecf(UNIT * uptr,uint8 * buf,t_mtrlnt bc)866 t_stat sim_tape_wrrecf (UNIT *uptr, uint8 *buf, t_mtrlnt bc)
867 {
868 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
869 uint32 f = MT_GET_FMT (uptr);
870 t_mtrlnt sbc;
871 
872 if (ctx == NULL)                                        /* if not properly attached? */
873     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
874 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrrecf(unit=%d, buf=%p, bc=%d)\n", (int)(uptr-ctx->dptr->units), buf, bc);
875 
876 sim_tape_data_trace(uptr, buf, bc, "Record Write", ctx->dptr->dctrl & MTSE_DBG_DAT, MTSE_DBG_STR);
877 MT_CLR_PNU (uptr);
878 sbc = MTR_L (bc);
879 if ((uptr->flags & UNIT_ATT) == 0)                      /* not attached? */
880     return MTSE_UNATT;
881 if (sim_tape_wrp (uptr))                                /* write prot? */
882     return MTSE_WRP;
883 if (sbc == 0)                                           /* nothing to do? */
884     return MTSE_OK;
885 (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);         /* set pos */
886 switch (f) {                                            /* case on format */
887 
888     case MTUF_F_STD:                                    /* standard */
889         sbc = MTR_L ((bc + 1) & ~1);                    /* pad odd length */
890     case MTUF_F_E11:                                    /* E11 */
891         (void)sim_fwrite (&bc, sizeof (t_mtrlnt), 1, uptr->fileref);
892         (void)sim_fwrite (buf, sizeof (uint8), sbc, uptr->fileref);
893         (void)sim_fwrite (&bc, sizeof (t_mtrlnt), 1, uptr->fileref);
894         if (ferror (uptr->fileref)) {                   /* error? */
895             MT_SET_PNU (uptr);
896             return sim_tape_ioerr (uptr);
897             }
898         uptr->pos = uptr->pos + sbc + (2 * sizeof (t_mtrlnt));  /* move tape */
899         break;
900 
901     case MTUF_F_P7B:                                    /* Pierce 7B */
902         buf[0] = buf[0] | P7B_SOR;                      /* mark start of rec */
903         (void)sim_fwrite (buf, sizeof (uint8), sbc, uptr->fileref);
904         (void)sim_fwrite (buf, sizeof (uint8), 1, uptr->fileref); /* delimit rec */
905         if (ferror (uptr->fileref)) {                   /* error? */
906             MT_SET_PNU (uptr);
907             return sim_tape_ioerr (uptr);
908             }
909         uptr->pos = uptr->pos + sbc;                    /* move tape */
910         break;
911         }
912 sim_tape_data_trace(uptr, buf, sbc, "Record Written", ctx->dptr->dctrl & MTSE_DBG_DAT, MTSE_DBG_STR);
913 return MTSE_OK;
914 }
915 
sim_tape_wrrecf_a(UNIT * uptr,uint8 * buf,t_mtrlnt bc,TAPE_PCALLBACK callback)916 t_stat sim_tape_wrrecf_a (UNIT *uptr, uint8 *buf, t_mtrlnt bc, TAPE_PCALLBACK callback)
917 {
918 t_stat r = SCPE_OK;
919     r = sim_tape_wrrecf (uptr, buf, bc);
920 return r;
921 }
922 
923 /* Write metadata forward (internal routine) */
924 
sim_tape_wrdata(UNIT * uptr,uint32 dat)925 static t_stat sim_tape_wrdata (UNIT *uptr, uint32 dat)
926 {
927 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
928 
929 MT_CLR_PNU (uptr);
930 if ((uptr->flags & UNIT_ATT) == 0)                      /* not attached? */
931     return MTSE_UNATT;
932 if (ctx == NULL)                                        /* if not properly attached? */
933     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
934 if (sim_tape_wrp (uptr))                                /* write prot? */
935     return MTSE_WRP;
936 (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);         /* set pos */
937 (void)sim_fwrite (&dat, sizeof (t_mtrlnt), 1, uptr->fileref);
938 if (ferror (uptr->fileref)) {                           /* error? */
939     MT_SET_PNU (uptr);
940     return sim_tape_ioerr (uptr);
941     }
942 sim_debug (MTSE_DBG_STR, ctx->dptr, "wr_lnt: lnt: %d, pos: %" T_ADDR_FMT "u\n", dat, uptr->pos);
943 uptr->pos = uptr->pos + sizeof (t_mtrlnt);              /* move tape */
944 return MTSE_OK;
945 }
946 
947 /* Write tape mark */
948 
sim_tape_wrtmk(UNIT * uptr)949 t_stat sim_tape_wrtmk (UNIT *uptr)
950 {
951 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
952 
953 if (ctx == NULL)                                        /* if not properly attached? */
954     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
955 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrtmk(unit=%d)\n", (int)(uptr-ctx->dptr->units));
956 if (MT_GET_FMT (uptr) == MTUF_F_P7B) {                  /* P7B? */
957     uint8 buf = P7B_EOF;                                /* eof mark */
958     return sim_tape_wrrecf (uptr, &buf, 1);             /* write char */
959     }
960 return sim_tape_wrdata (uptr, MTR_TMK);
961 }
962 
sim_tape_wrtmk_a(UNIT * uptr,TAPE_PCALLBACK callback)963 t_stat sim_tape_wrtmk_a (UNIT *uptr, TAPE_PCALLBACK callback)
964 {
965 t_stat r = MTSE_OK;
966     r = sim_tape_wrtmk (uptr);
967 return r;
968 }
969 
970 /* Write end of medium */
971 
sim_tape_wreom(UNIT * uptr)972 t_stat sim_tape_wreom (UNIT *uptr)
973 {
974 t_stat result;
975 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
976 
977 if (ctx == NULL)                                        /* if not properly attached? */
978     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
979 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wreom(unit=%d)\n", (int)(uptr-ctx->dptr->units));
980 if (MT_GET_FMT (uptr) == MTUF_F_P7B)                    /* cant do P7B */
981     return MTSE_FMT;
982 
983 result = sim_tape_wrdata (uptr, MTR_EOM);               /* write the EOM marker */
984 
985 uptr->pos = uptr->pos - sizeof (t_mtrlnt);              /* restore original tape position */
986 MT_SET_PNU (uptr);                                      /* indicate that position was not updated */
987 
988 return result;
989 }
990 
sim_tape_wreom_a(UNIT * uptr,TAPE_PCALLBACK callback)991 t_stat sim_tape_wreom_a (UNIT *uptr, TAPE_PCALLBACK callback)
992 {
993 t_stat r = MTSE_OK;
994     r = sim_tape_wreom (uptr);
995 return r;
996 }
997 
998 /* Write end of medium-rewind */
999 
sim_tape_wreomrw(UNIT * uptr)1000 t_stat sim_tape_wreomrw (UNIT *uptr)
1001 {
1002 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1003 t_stat r;
1004 
1005 if (ctx == NULL)                                        /* if not properly attached? */
1006     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1007 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wreomrw(unit=%d)\n", (int)(uptr-ctx->dptr->units));
1008 if (MT_GET_FMT (uptr) == MTUF_F_P7B)                    /* cant do P7B */
1009     return MTSE_FMT;
1010 r = sim_tape_wrdata (uptr, MTR_EOM);
1011 if (r == MTSE_OK)
1012     r = sim_tape_rewind (uptr);
1013 return r;
1014 }
1015 
sim_tape_wreomrw_a(UNIT * uptr,TAPE_PCALLBACK callback)1016 t_stat sim_tape_wreomrw_a (UNIT *uptr, TAPE_PCALLBACK callback)
1017 {
1018 t_stat r = MTSE_OK;
1019     r = sim_tape_wreomrw (uptr);
1020 return r;
1021 }
1022 
1023 /* Write erase gap
1024 
1025    Inputs:
1026         uptr    = pointer to tape unit
1027         gaplen  = length of gap in tenths of an inch
1028 
1029    Outputs:
1030         status  = operation status
1031 
1032    exit condition       position
1033    ------------------   ------------------
1034    unit unattached      unchanged
1035    unsupported format   unchanged
1036    write protected      unchanged
1037    read error           unchanged, PNU set
1038    write error          unchanged, PNU set
1039    gap written          updated
1040 
1041 
1042    An erase gap is represented in the tape image file by a special metadata
1043    value.  This value is chosen so that it is still recognizable even if it has
1044    been "cut in half" by a subsequent data overwrite that does not end on a
1045    metadatum-sized boundary.  In addition, a range of metadata values are
1046    reserved for detection in the reverse direction.  Erase gaps are currently
1047    supported only in SIMH (MTUF_F_STD) tape format.
1048 
1049    This implementation supports erasing gaps in the middle of a populated tape
1050    image and will always produce a valid image.  It also produces valid images
1051    when overwriting gaps with data records, with one exception: a data write
1052    that leaves only two bytes of gap remaining will produce an invalid tape.
1053    This limitation is deemed acceptable, as it is analogous to the existing
1054    limitation that data records cannot overwrite other data records without
1055    producing an invalid tape.
1056 
1057    Because SIMH tape images do not carry physical parameters (e.g., recording
1058    density), overwriting a tape image file containing gap metadata is
1059    problematic if the density setting is not the same as that used during
1060    recording.  There is no way to establish a gap of a certain length
1061    unequivocally in an image file, so this implementation establishes a gap of a
1062    certain number of bytes that reflect the desired gap length at the tape
1063    density in bits per inch used during writing.
1064 
1065    To write an erase gap, the implementation uses one of two approaches,
1066    depending on whether or not the current tape position is at EOM.  Erasing at
1067    EOM presents no special difficulties; gap metadata markers are written for
1068    the prescribed number of bytes.  If the tape is not at EOM, then erasing must
1069    take into account the existing record structure to ensure that a valid tape
1070    image is maintained.
1071 
1072    The general approach is to erase for the nominal number of bytes but to
1073    increase that length, if necessary, to ensure that a partially overwritten
1074    data record at the end of the gap can be altered to maintain validity.
1075    Because the smallest legal tape record requires space for two metadata
1076    markers plus two data bytes, an erasure that would leave less than that
1077    is increased to consume the entire record.  Otherwise, the final record is
1078    truncated appropriately by rewriting the leading and trailing length words
1079    appropriately.
1080 
1081    When reading in either direction, gap metadata markers are ignored (skipped)
1082    until a record length header, EOF marker, EOM marker, or physical EOF is
1083    encountered.  Thus, tape images containing gap metadata are transparent to
1084    the calling simulator (unless tape runaway support is enabled -- see the
1085    notes at "sim_tape_rdlntf" for details).
1086 
1087    The permissibility of data record lengths that are not multiples of the
1088    metadatum size presents a difficulty when reading.  If such an "odd length"
1089    record is written over a gap, half of a metadata marker will exist
1090    immediately after the trailing record length.
1091 
1092    This condition is detected when reading forward by the appearance of a
1093    "reversed" marker.  The value appears reversed because the value is made up
1094    of half of one marker and half of the next.  This is handled by seeking
1095    forward two bytes to resync (the stipulation above that the overwrite cannot
1096    leave only two bytes of gap means that at least one "whole" metadata marker
1097    will follow).  Reading in reverse presents a more complex problem, because
1098    half of the marker is from the preceding trailing record length marker and
1099    therefore could be any of a range of values.  However, that range is
1100    restricted by the SIMH tape specification requirement that record length
1101    metadata values must have bits 30:24 set to zero.  This allows unambiguous
1102    detection of the condition.
1103 
1104    The value chosen for gap metadata and the values reserved for "half-gap"
1105    detection are:
1106 
1107      0xFFFFFFFE            - primary gap value
1108      0xFFFEFFFF            - reserved (indicates half-gap in forward reads)
1109      0xFFFF0000:0xFFFF00FF - reserved (indicates half-gap in reverse reads)
1110      0xFFFF8000:0xFFFF80FF - reserved (indicates half-gap in reverse reads)
1111 
1112    If the tape density has been set via a previous sim_tape_set_dens call, and
1113    the tape format is set to SIMH format, then this routine will write a gap of
1114    the appropriate size.  If the density has not been set, then no action will
1115    be taken, and either MTSE_IOERR or MTSE_OK status will be returned, depending
1116    on whether SIMH or another format is selected, respectively.  A simulator
1117    that calls this routine must set the density beforehand; failure to do so is
1118    an error.  However, calling while another format is enabled is OK and is
1119    treated as a no-operation.  This allows a device simulator that supports
1120    writing erase gaps to use the same code without worrying about the tape
1121    format currently selected by the user.
1122 */
1123 
sim_tape_wrgap(UNIT * uptr,uint32 gaplen)1124 t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen)
1125 {
1126 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1127 t_stat st;
1128 t_mtrlnt meta, sbc, new_len, rec_size;
1129 t_addr gap_pos = uptr->pos;
1130 uint32 file_size, marker_count, tape_density;
1131 int32 gap_needed;
1132 uint32 gap_alloc = 0;                                   /* gap currently allocated from the tape */
1133 const uint32 format = MT_GET_FMT (uptr);                /* tape format */
1134 const uint32 meta_size = sizeof (t_mtrlnt);             /* bytes per metadatum */
1135 const uint32 min_rec_size = 2 + sizeof (t_mtrlnt) * 2;  /* smallest data record size */
1136 
1137 if (ctx == NULL)                                        /* if not properly attached? */
1138     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1139 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrgap(unit=%d, gaplen=%u)\n", (int)(uptr-ctx->dptr->units), gaplen);
1140 
1141 MT_CLR_PNU (uptr);
1142 
1143 if ((uptr->flags & UNIT_ATT) == 0)                      /* if the unit is not attached */
1144     return MTSE_UNATT;                                  /*   then we cannot proceed */
1145 
1146 else if (sim_tape_wrp (uptr))                           /* otherwise if the unit is write protected */
1147     return MTSE_WRP;                                    /*   then we cannot write */
1148 
1149 tape_density = bpi [MT_DENS (uptr->dynflags)];          /* get the density of the tape */
1150 
1151 if (format != MTUF_F_STD)                               /* if erase gaps aren't supported by the format */
1152     return MTSE_OK;                                     /*   then take no action */
1153 else if (tape_density == 0)                             /* otherwise if the density is not set */
1154     return MTSE_IOERR;                                  /*   then report an I/O error */
1155 else                                                    /* otherwise */
1156     gap_needed = (gaplen * tape_density) / 10;          /*   determine the gap size needed in bytes */
1157 
1158 file_size = sim_fsize (uptr->fileref);                  /* get file size */
1159 sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);         /* position tape */
1160 
1161 /* Read tape records and allocate to gap until amount required is consumed.
1162 
1163    Read next metadatum from tape:
1164     - EOF or EOM: allocate remainder of bytes needed.
1165     - TMK or GAP: allocate sizeof(metadatum) bytes.
1166     - Reverse GAP: allocate sizeof(metadatum) / 2 bytes.
1167     - Data record: see below.
1168 
1169    Loop until bytes needed = 0.
1170 */
1171 
1172 do {
1173     (void)sim_fread (&meta, meta_size, 1, uptr->fileref);     /* read metadatum */
1174 
1175     if (ferror (uptr->fileref)) {                       /* read error? */
1176         uptr->pos = gap_pos;                            /* restore original position */
1177         MT_SET_PNU (uptr);                              /* position not updated */
1178         return sim_tape_ioerr (uptr);                   /* translate error */
1179         }
1180     else
1181         uptr->pos = uptr->pos + meta_size;              /* move tape over datum */
1182 
1183     if (feof (uptr->fileref) || (meta == MTR_EOM)) {    /* at eof or eom? */
1184         gap_alloc = gap_alloc + gap_needed;             /* allocate remainder */
1185         gap_needed = 0;
1186         }
1187 
1188     else if ((meta == MTR_GAP) || (meta == MTR_TMK)) {  /* gap or tape mark? */
1189         gap_alloc = gap_alloc + meta_size;              /* allocate marker space */
1190         gap_needed = gap_needed - meta_size;            /* reduce requirement */
1191         }
1192 
1193     else if (meta == MTR_FHGAP) {                       /* half gap? */
1194         uptr->pos = uptr->pos - meta_size / 2;          /* backup to resync */
1195         sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* position tape */
1196         gap_alloc = gap_alloc + meta_size / 2;          /* allocate marker space */
1197         gap_needed = gap_needed - meta_size / 2;        /* reduce requirement */
1198         }
1199 
1200     else if (uptr->pos +
1201              MTR_L (meta) + meta_size > file_size) {    /* rec len out of range? */
1202         gap_alloc = gap_alloc + gap_needed;             /* presume overwritten tape */
1203         gap_needed = 0;                                 /* allocate remainder */
1204         }
1205 
1206 /* Allocate a data record:
1207     - Determine record size in bytes (including metadata)
1208     - If record size - bytes needed < smallest allowed record size,
1209       allocate entire record to gap, else allocate needed amount and
1210       truncate data record to reflect remainder.
1211 */
1212     else {                                              /* data record */
1213         sbc = MTR_L (meta);                             /* get record data length */
1214         rec_size = ((sbc + 1) & ~1) + meta_size * 2;    /* overall size in bytes */
1215 
1216         if (rec_size < gap_needed + min_rec_size) {         /* rec too small? */
1217             uptr->pos = uptr->pos - meta_size + rec_size;   /* position past record */
1218             sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* move tape */
1219             gap_alloc = gap_alloc + rec_size;               /* allocate record */
1220             gap_needed = gap_needed - rec_size;             /* reduce requirement */
1221             }
1222 
1223         else {                                              /* record size OK */
1224             uptr->pos = uptr->pos - meta_size + gap_needed; /* position to end of gap */
1225             new_len = MTR_F (meta) | (sbc - gap_needed);    /* truncate to new len */
1226             st = sim_tape_wrdata (uptr, new_len);           /* write new rec len */
1227 
1228             if (st != MTSE_OK) {                            /* write OK? */
1229                 uptr->pos = gap_pos;                        /* restore orig pos */
1230                 return st;                                  /* PNU was set by wrdata */
1231                 }
1232 
1233             uptr->pos = uptr->pos + sbc - gap_needed;       /* position to end of data */
1234             st = sim_tape_wrdata (uptr, new_len);           /* write new rec len */
1235 
1236             if (st != MTSE_OK) {                            /* write OK? */
1237                 uptr->pos = gap_pos;                        /* restore orig pos */
1238                 return st;                                  /* PNU was set by wrdata */
1239                 }
1240 
1241             gap_alloc = gap_alloc + gap_needed;             /* allocate remainder */
1242             gap_needed = 0;
1243             }
1244         }
1245     }
1246 while (gap_needed > 0);
1247 
1248 uptr->pos = gap_pos;                                    /* reposition to gap start */
1249 
1250 if (gap_alloc & (meta_size - 1)) {                      /* gap size "odd?" */
1251     st = sim_tape_wrdata (uptr, MTR_FHGAP);             /* write half gap marker */
1252     if (st != MTSE_OK) {                                /* write OK? */
1253         uptr->pos = gap_pos;                            /* restore orig pos */
1254         return st;                                      /* PNU was set by wrdata */
1255         }
1256     uptr->pos = uptr->pos - meta_size / 2;              /* realign position */
1257     gap_alloc = gap_alloc - 2;                          /* decrease gap to write */
1258     }
1259 
1260 marker_count = gap_alloc / meta_size;                   /* count of gap markers */
1261 
1262 do {
1263     st = sim_tape_wrdata (uptr, MTR_GAP);               /* write gap markers */
1264     if (st != MTSE_OK) {                                /* write OK? */
1265         uptr->pos = gap_pos;                            /* restore orig pos */
1266         return st;                                      /* PNU was set by wrdata */
1267         }
1268     }
1269 while (--marker_count > 0);
1270 
1271 return MTSE_OK;
1272 }
1273 
sim_tape_wrgap_a(UNIT * uptr,uint32 gaplen,TAPE_PCALLBACK callback)1274 t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, TAPE_PCALLBACK callback)
1275 {
1276 t_stat r = MTSE_OK;
1277 return r;
1278 }
1279 
1280 /* Space record forward
1281 
1282    Inputs:
1283         uptr    =       pointer to tape unit
1284         bc      =       pointer to size of record skipped
1285    Outputs:
1286         status  =       operation status
1287 
1288    exit condition       position
1289 
1290    unit unattached      unchanged
1291    read error           unchanged, PNU set
1292    end of file/medium   unchanged, PNU set
1293    tape mark            updated
1294    data record          updated
1295    data record error    updated
1296 */
1297 
sim_tape_sprecf(UNIT * uptr,t_mtrlnt * bc)1298 t_stat sim_tape_sprecf (UNIT *uptr, t_mtrlnt *bc)
1299 {
1300 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1301 t_stat st;
1302 
1303 if (ctx == NULL)                                        /* if not properly attached? */
1304     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1305 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_sprecf(unit=%d)\n", (int)(uptr-ctx->dptr->units));
1306 
1307 st = sim_tape_rdlntf (uptr, bc);                        /* get record length */
1308 *bc = MTR_L (*bc);
1309 return st;
1310 }
1311 
sim_tape_sprecf_a(UNIT * uptr,t_mtrlnt * bc,TAPE_PCALLBACK callback)1312 t_stat sim_tape_sprecf_a (UNIT *uptr, t_mtrlnt *bc, TAPE_PCALLBACK callback)
1313 {
1314 t_stat r = MTSE_OK;
1315     r = sim_tape_sprecf (uptr, bc);
1316 return r;
1317 }
1318 
1319 /* Space records forward
1320 
1321    Inputs:
1322         uptr    =       pointer to tape unit
1323         count   =       count of records to skip
1324         skipped =       pointer to number of records actually skipped
1325    Outputs:
1326         status  =       operation status
1327 
1328    exit condition       position
1329 
1330    unit unattached      unchanged
1331    read error           unchanged, PNU set
1332    end of file/medium   unchanged, PNU set
1333    tape mark            updated
1334    data record          updated
1335    data record error    updated
1336 */
1337 
sim_tape_sprecsf(UNIT * uptr,uint32 count,uint32 * skipped)1338 t_stat sim_tape_sprecsf (UNIT *uptr, uint32 count, uint32 *skipped)
1339 {
1340 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1341 t_stat st;
1342 t_mtrlnt tbc;
1343 
1344 if (ctx == NULL)                                        /* if not properly attached? */
1345     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1346 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_sprecsf(unit=%d, count=%d)\n", (int)(uptr-ctx->dptr->units), count);
1347 
1348 *skipped = 0;
1349 while (*skipped < count) {                              /* loopo */
1350     st = sim_tape_sprecf (uptr, &tbc);                  /* spc rec */
1351     if (st != MTSE_OK)
1352         return st;
1353     *skipped = *skipped + 1;                            /* # recs skipped */
1354     }
1355 return MTSE_OK;
1356 }
1357 
sim_tape_sprecsf_a(UNIT * uptr,uint32 count,uint32 * skipped,TAPE_PCALLBACK callback)1358 t_stat sim_tape_sprecsf_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)
1359 {
1360 t_stat r = MTSE_OK;
1361     r = sim_tape_sprecsf (uptr, count, skipped);
1362 return r;
1363 }
1364 
1365 /* Space record reverse
1366 
1367    Inputs:
1368         uptr    =       pointer to tape unit
1369         bc      =       pointer to size of records skipped
1370    Outputs:
1371         status  =       operation status
1372 
1373    exit condition       position
1374 
1375    unit unattached      unchanged
1376    beginning of tape    unchanged
1377    read error           unchanged
1378    end of file          unchanged
1379    end of medium        updated
1380    tape mark            updated
1381    data record          updated
1382 */
1383 
sim_tape_sprecr(UNIT * uptr,t_mtrlnt * bc)1384 t_stat sim_tape_sprecr (UNIT *uptr, t_mtrlnt *bc)
1385 {
1386 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1387 t_stat st;
1388 
1389 if (ctx == NULL)                                        /* if not properly attached? */
1390     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1391 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_sprecr(unit=%d)\n", (int)(uptr-ctx->dptr->units));
1392 
1393 if (MT_TST_PNU (uptr)) {
1394     MT_CLR_PNU (uptr);
1395     *bc = 0;
1396     return MTSE_OK;
1397     }
1398 st = sim_tape_rdlntr (uptr, bc);                        /* get record length */
1399 *bc = MTR_L (*bc);
1400 return st;
1401 }
1402 
sim_tape_sprecr_a(UNIT * uptr,t_mtrlnt * bc,TAPE_PCALLBACK callback)1403 t_stat sim_tape_sprecr_a (UNIT *uptr, t_mtrlnt *bc, TAPE_PCALLBACK callback)
1404 {
1405 t_stat r = MTSE_OK;
1406     r = sim_tape_sprecr (uptr, bc);
1407 return r;
1408 }
1409 
1410 /* Space records reverse
1411 
1412    Inputs:
1413         uptr    =       pointer to tape unit
1414         count   =       count of records to skip
1415         skipped =       pointer to number of records actually skipped
1416    Outputs:
1417         status  =       operation status
1418 
1419    exit condition       position
1420 
1421    unit unattached      unchanged
1422    beginning of tape    unchanged
1423    read error           unchanged
1424    end of file          unchanged
1425    end of medium        updated
1426    tape mark            updated
1427    data record          updated
1428 */
1429 
sim_tape_sprecsr(UNIT * uptr,uint32 count,uint32 * skipped)1430 t_stat sim_tape_sprecsr (UNIT *uptr, uint32 count, uint32 *skipped)
1431 {
1432 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1433 t_stat st;
1434 t_mtrlnt tbc;
1435 
1436 if (ctx == NULL)                                        /* if not properly attached? */
1437     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1438 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_sprecsr(unit=%d, count=%d)\n", (int)(uptr-ctx->dptr->units), count);
1439 
1440 *skipped = 0;
1441 while (*skipped < count) {                              /* loopo */
1442     st = sim_tape_sprecr (uptr, &tbc);                  /* spc rec rev */
1443     if (st != MTSE_OK)
1444         return st;
1445     *skipped = *skipped + 1;                            /* # recs skipped */
1446     }
1447 return MTSE_OK;
1448 }
1449 
sim_tape_sprecsr_a(UNIT * uptr,uint32 count,uint32 * skipped,TAPE_PCALLBACK callback)1450 t_stat sim_tape_sprecsr_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)
1451 {
1452 t_stat r = MTSE_OK;
1453     r = sim_tape_sprecsr (uptr, count, skipped);
1454 return r;
1455 }
1456 
1457 /* Space files forward by record
1458 
1459    Inputs:
1460         uptr    =       pointer to tape unit
1461         count   =       count of files to skip
1462         skipped =       pointer to number of files actually skipped
1463         recsskipped =   pointer to number of records skipped
1464         check_leot =    flag to detect and stop skip between two successive tape marks
1465    Outputs:
1466         status  =       operation status
1467 
1468    exit condition       position
1469 
1470    unit unattached      unchanged
1471    read error           unchanged, PNU set
1472    end of file/medium   unchanged, PNU set
1473    tape mark            updated
1474    data record          updated
1475    data record error    updated
1476 */
1477 
sim_tape_spfilebyrecf(UNIT * uptr,uint32 count,uint32 * skipped,uint32 * recsskipped,t_bool check_leot)1478 t_stat sim_tape_spfilebyrecf (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped, t_bool check_leot)
1479 {
1480 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1481 t_stat st;
1482 t_bool last_tapemark = FALSE;
1483 uint32 filerecsskipped;
1484 
1485 *skipped = *recsskipped = 0;
1486 if (ctx == NULL)                                        /* if not properly attached? */
1487     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1488 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_spfilebyrecf(unit=%d, count=%d, check_leot=%d)\n", (int)(uptr-ctx->dptr->units), count, check_leot);
1489 
1490 if (check_leot) {
1491     t_mtrlnt rbc;
1492 
1493     st = sim_tape_rdlntr (uptr, &rbc);
1494     last_tapemark = (MTSE_TMK == st);
1495     if ((st == MTSE_OK) || (st == MTSE_TMK))
1496         sim_tape_rdlntf (uptr, &rbc);
1497     }
1498 *skipped = 0;
1499 *recsskipped = 0;
1500 while (*skipped < count) {                              /* loopo */
1501     while (1) {
1502         st = sim_tape_sprecsf (uptr, 0x1ffffff, &filerecsskipped);/* spc recs */
1503         *recsskipped += filerecsskipped;
1504         if (st != MTSE_OK)
1505             break;
1506         }
1507     if (st == MTSE_TMK) {
1508         *skipped = *skipped + 1;                        /* # files skipped */
1509         if (check_leot && (filerecsskipped == 0) && last_tapemark) {
1510             uint32 filefileskipped;
1511             sim_tape_spfilebyrecr (uptr, 1, &filefileskipped, &filerecsskipped);
1512             *skipped = *skipped - 1;                    /* adjust # files skipped */
1513             return MTSE_LEOT;
1514             }
1515         last_tapemark = TRUE;
1516         }
1517     else
1518         return st;
1519     }
1520 return MTSE_OK;
1521 }
1522 
sim_tape_spfilebyrecf_a(UNIT * uptr,uint32 count,uint32 * skipped,uint32 * recsskipped,t_bool check_leot,TAPE_PCALLBACK callback)1523 t_stat sim_tape_spfilebyrecf_a (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped, t_bool check_leot, TAPE_PCALLBACK callback)
1524 {
1525 t_stat r = MTSE_OK;
1526     r = sim_tape_spfilebyrecf (uptr, count, skipped, recsskipped, check_leot);
1527 return r;
1528 }
1529 
1530 /* Space files forward
1531 
1532    Inputs:
1533         uptr    =       pointer to tape unit
1534         count   =       count of files to skip
1535         skipped =       pointer to number of files actually skipped
1536    Outputs:
1537         status  =       operation status
1538 
1539    exit condition       position
1540 
1541    unit unattached      unchanged
1542    read error           unchanged, PNU set
1543    end of file/medium   unchanged, PNU set
1544    tape mark            updated
1545    data record          updated
1546    data record error    updated
1547 */
1548 
sim_tape_spfilef(UNIT * uptr,uint32 count,uint32 * skipped)1549 t_stat sim_tape_spfilef (UNIT *uptr, uint32 count, uint32 *skipped)
1550 {
1551 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1552 uint32 totalrecsskipped;
1553 
1554 if (ctx == NULL)                                        /* if not properly attached? */
1555     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1556 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_spfilef(unit=%d, count=%d)\n", (int)(uptr-ctx->dptr->units), count);
1557 
1558 return sim_tape_spfilebyrecf (uptr, count, skipped, &totalrecsskipped, FALSE);
1559 }
1560 
sim_tape_spfilef_a(UNIT * uptr,uint32 count,uint32 * skipped,TAPE_PCALLBACK callback)1561 t_stat sim_tape_spfilef_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)
1562 {
1563 t_stat r = MTSE_OK;
1564     r = sim_tape_spfilef (uptr, count, skipped);
1565 return r;
1566 }
1567 
1568 /* Space files reverse by record
1569 
1570    Inputs:
1571         uptr    =       pointer to tape unit
1572         count   =       count of files to skip
1573         skipped =       pointer to number of files actually skipped
1574         recsskipped =   pointer to number of records skipped
1575    Outputs:
1576         status  =       operation status
1577 
1578    exit condition       position
1579 
1580    unit unattached      unchanged
1581    beginning of tape    unchanged
1582    read error           unchanged
1583    end of file          unchanged
1584    end of medium        updated
1585    tape mark            updated
1586    data record          updated
1587 */
1588 
sim_tape_spfilebyrecr(UNIT * uptr,uint32 count,uint32 * skipped,uint32 * recsskipped)1589 t_stat sim_tape_spfilebyrecr (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped)
1590 {
1591 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1592 t_stat st;
1593 uint32 filerecsskipped = 0;
1594 
1595 *skipped = 0;
1596 *recsskipped = 0;
1597 if (ctx == NULL)                                        /* if not properly attached? */
1598     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1599 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_spfilebyrecr(unit=%d, count=%d)\n", (int)(uptr-ctx->dptr->units), count);
1600 
1601 while (*skipped < count) {                              /* loopo */
1602     while (1) {
1603         st = sim_tape_sprecsr (uptr, 0x1ffffff, &filerecsskipped);/* spc recs rev */
1604         *recsskipped += filerecsskipped;
1605         if (st != MTSE_OK)
1606             break;
1607         }
1608     if (st == MTSE_TMK)
1609         *skipped = *skipped + 1;                        /* # files skipped */
1610     else
1611         return st;
1612     }
1613 return MTSE_OK;
1614 }
1615 
sim_tape_spfilebyrecr_a(UNIT * uptr,uint32 count,uint32 * skipped,uint32 * recsskipped,TAPE_PCALLBACK callback)1616 t_stat sim_tape_spfilebyrecr_a (UNIT *uptr, uint32 count, uint32 *skipped, uint32 *recsskipped, TAPE_PCALLBACK callback)
1617 {
1618 t_stat r = MTSE_OK;
1619     r = sim_tape_spfilebyrecr (uptr, count, skipped, recsskipped);
1620 return r;
1621 }
1622 
1623 /* Space files reverse
1624 
1625    Inputs:
1626         uptr    =       pointer to tape unit
1627         count   =       count of files to skip
1628         skipped =       pointer to number of files actually skipped
1629    Outputs:
1630         status  =       operation status
1631 
1632    exit condition       position
1633 
1634    unit unattached      unchanged
1635    beginning of tape    unchanged
1636    read error           unchanged
1637    end of file          unchanged
1638    end of medium        updated
1639    tape mark            updated
1640    data record          updated
1641 */
1642 
sim_tape_spfiler(UNIT * uptr,uint32 count,uint32 * skipped)1643 t_stat sim_tape_spfiler (UNIT *uptr, uint32 count, uint32 *skipped)
1644 {
1645 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1646 uint32 totalrecsskipped;
1647 
1648 if (ctx == NULL)                                        /* if not properly attached? */
1649     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1650 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_spfiler(unit=%d, count=%d)\n", (int)(uptr-ctx->dptr->units), count);
1651 
1652 return sim_tape_spfilebyrecr (uptr, count, skipped, &totalrecsskipped);
1653 }
1654 
sim_tape_spfiler_a(UNIT * uptr,uint32 count,uint32 * skipped,TAPE_PCALLBACK callback)1655 t_stat sim_tape_spfiler_a (UNIT *uptr, uint32 count, uint32 *skipped, TAPE_PCALLBACK callback)
1656 {
1657 t_stat r = MTSE_OK;
1658     r = sim_tape_spfiler (uptr, count, skipped);
1659 return r;
1660 }
1661 
1662 /* Rewind tape */
1663 
sim_tape_rewind(UNIT * uptr)1664 t_stat sim_tape_rewind (UNIT *uptr)
1665 {
1666 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1667 
1668 if (uptr->flags & UNIT_ATT) {
1669     if (ctx == NULL)                                    /* if not properly attached? */
1670         return sim_messagef (SCPE_IERR, "Bad Attach\n");/*   that's a problem */
1671     sim_debug (ctx->dbit, ctx->dptr, "sim_tape_rewind(unit=%d)\n", (int)(uptr-ctx->dptr->units));
1672     }
1673 uptr->pos = 0;
1674 MT_CLR_PNU (uptr);
1675 return MTSE_OK;
1676 }
1677 
sim_tape_rewind_a(UNIT * uptr,TAPE_PCALLBACK callback)1678 t_stat sim_tape_rewind_a (UNIT *uptr, TAPE_PCALLBACK callback)
1679 {
1680 t_stat r = MTSE_OK;
1681     r = sim_tape_rewind (uptr);
1682 return r;
1683 }
1684 
1685 /* Position Tape */
1686 
sim_tape_position(UNIT * uptr,uint32 flags,uint32 recs,uint32 * recsskipped,uint32 files,uint32 * filesskipped,uint32 * objectsskipped)1687 t_stat sim_tape_position (UNIT *uptr, uint32 flags, uint32 recs, uint32 *recsskipped, uint32 files, uint32 *filesskipped, uint32 *objectsskipped)
1688 {
1689 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1690 t_stat r = MTSE_OK;
1691 
1692 if (ctx == NULL)                                        /* if not properly attached? */
1693     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1694 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_position(unit=%d, flags=0x%X, recs=%d, files=%d)\n", (int)(uptr-ctx->dptr->units), flags, recs, files);
1695 
1696 *recsskipped = *filesskipped = *objectsskipped = 0;
1697 if (flags & MTPOS_M_REW)
1698     r = sim_tape_rewind (uptr);
1699 if (r != MTSE_OK)
1700     return r;
1701 if (flags & MTPOS_M_OBJ) {
1702     uint32 objs = recs;
1703     uint32 skipped;
1704     uint32 objsremaining = objs;
1705 
1706     while (*objectsskipped < objs) {                       /* loopo */
1707         if (flags & MTPOS_M_REV)                        /* reverse? */
1708             r = sim_tape_sprecsr (uptr, objsremaining, &skipped);
1709         else
1710             r = sim_tape_sprecsf (uptr, objsremaining, &skipped);
1711         objsremaining = objsremaining - (skipped + ((r == MTSE_TMK) ? 1 : 0));
1712         if ((r == MTSE_TMK) || (r == MTSE_OK))
1713             *objectsskipped = *objectsskipped + skipped + ((r == MTSE_TMK) ? 1 : 0);
1714         else
1715             return r;
1716         }
1717     r = MTSE_OK;
1718     }
1719 else {
1720     uint32 fileskiprecs;
1721 
1722     if (flags & MTPOS_M_REV)                            /* reverse? */
1723         r = sim_tape_spfilebyrecr (uptr, files, filesskipped, &fileskiprecs);
1724     else
1725         r = sim_tape_spfilebyrecf (uptr, files, filesskipped, &fileskiprecs, (flags & MTPOS_M_DLE));
1726     if (r != MTSE_OK)
1727         return r;
1728     if (flags & MTPOS_M_REV)                            /* reverse? */
1729         r = sim_tape_sprecsr (uptr, recs, recsskipped);
1730     else
1731         r = sim_tape_sprecsf (uptr, recs, recsskipped);
1732     if (r == MTSE_TMK)
1733         *filesskipped = *filesskipped + 1;
1734     *objectsskipped = fileskiprecs + *filesskipped + *recsskipped;
1735     }
1736 return r;
1737 }
1738 
sim_tape_position_a(UNIT * uptr,uint32 flags,uint32 recs,uint32 * recsskipped,uint32 files,uint32 * filesskipped,uint32 * objectsskipped,TAPE_PCALLBACK callback)1739 t_stat sim_tape_position_a (UNIT *uptr, uint32 flags, uint32 recs, uint32 *recsskipped, uint32 files, uint32 *filesskipped, uint32 *objectsskipped, TAPE_PCALLBACK callback)
1740 {
1741 t_stat r = MTSE_OK;
1742     r = sim_tape_position (uptr, flags, recs, recsskipped, files, filesskipped, objectsskipped);
1743 return r;
1744 }
1745 
1746 /* Reset tape */
1747 
sim_tape_reset(UNIT * uptr)1748 t_stat sim_tape_reset (UNIT *uptr)
1749 {
1750 struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx;
1751 
1752 MT_CLR_PNU (uptr);
1753 if (!(uptr->flags & UNIT_ATT))                          /* attached? */
1754     return SCPE_OK;
1755 
1756 if (ctx == NULL)                                        /* if not properly attached? */
1757     return sim_messagef (SCPE_IERR, "Bad Attach\n");    /*   that's a problem */
1758 sim_debug (ctx->dbit, ctx->dptr, "sim_tape_reset(unit=%d)\n", (int)(uptr-ctx->dptr->units));
1759 
1760 _sim_tape_io_flush(uptr);
1761 return SCPE_OK;
1762 }
1763 
1764 /* Test for BOT */
1765 
sim_tape_bot(UNIT * uptr)1766 t_bool sim_tape_bot (UNIT *uptr)
1767 {
1768 uint32 f = MT_GET_FMT (uptr);
1769 
1770 return (uptr->pos <= fmts[f].bot)? TRUE: FALSE;
1771 }
1772 
1773 /* Test for end of tape */
1774 
sim_tape_eot(UNIT * uptr)1775 t_bool sim_tape_eot (UNIT *uptr)
1776 {
1777 return (uptr->capac && (uptr->pos >= uptr->capac))? TRUE: FALSE;
1778 }
1779 
1780 /* Test for write protect */
1781 
sim_tape_wrp(UNIT * uptr)1782 t_bool sim_tape_wrp (UNIT *uptr)
1783 {
1784 return ((uptr->flags & MTUF_WRP) || (MT_GET_FMT (uptr) == MTUF_F_TPC))? TRUE: FALSE;
1785 }
1786 
1787 /* Process I/O error */
1788 
sim_tape_ioerr(UNIT * uptr)1789 static t_stat sim_tape_ioerr (UNIT *uptr)
1790 {
1791 sim_printf ("%s: Magtape library I/O error: %s\n", sim_uname (uptr), strerror (errno));
1792 clearerr (uptr->fileref);
1793 return MTSE_IOERR;
1794 }
1795 
1796 /* Set tape format */
1797 
sim_tape_set_fmt(UNIT * uptr,int32 val,CONST char * cptr,void * desc)1798 t_stat sim_tape_set_fmt (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
1799 {
1800 uint32 f;
1801 
1802 if (uptr->flags & UNIT_ATT)
1803     return SCPE_ALATT;
1804 if (uptr == NULL)
1805     return SCPE_IERR;
1806 if (cptr == NULL)
1807     return SCPE_ARG;
1808 for (f = 0; f < MTUF_N_FMT; f++) {
1809     if (fmts[f].name && (strcmp (cptr, fmts[f].name) == 0)) {
1810         uptr->flags = (uptr->flags & ~MTUF_FMT) |
1811             (f << MTUF_V_FMT) | fmts[f].uflags;
1812         return SCPE_OK;
1813         }
1814     }
1815 return SCPE_ARG;
1816 }
1817 
1818 /* Show tape format */
1819 
sim_tape_show_fmt(FILE * st,UNIT * uptr,int32 val,CONST void * desc)1820 t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
1821 {
1822 int32 f = MT_GET_FMT (uptr);
1823 
1824 if (fmts[f].name)
1825     fprintf (st, "%s format", fmts[f].name);
1826 else fprintf (st, "invalid format");
1827 return SCPE_OK;
1828 }
1829 
1830 /* Map a TPC format tape image */
1831 
sim_tape_tpc_map(UNIT * uptr,t_addr * map,uint32 mapsize)1832 static uint32 sim_tape_tpc_map (UNIT *uptr, t_addr *map, uint32 mapsize)
1833 {
1834 t_addr tpos, leot = 0;
1835 t_addr tape_size;
1836 t_tpclnt bc, last_bc = 0xFFFF;
1837 uint32 had_double_tape_mark = 0;
1838 size_t i;
1839 uint32 objc, sizec;
1840 uint32 *countmap = NULL;
1841 uint8 *recbuf = NULL;
1842 DEVICE *dptr = find_dev_from_unit (uptr);
1843 
1844 if ((uptr == NULL) || (uptr->fileref == NULL))
1845     return 0;
1846 countmap = (uint32 *)calloc (65536, sizeof(*countmap));
1847 recbuf = (uint8 *)malloc (65536);
1848 tape_size = (t_addr)sim_fsize (uptr->fileref);
1849 sim_debug (MTSE_DBG_STR, dptr, "tpc_map: tape_size: %" T_ADDR_FMT "u\n", tape_size);
1850 for (objc = 0, sizec = 0, tpos = 0;; ) {
1851     sim_fseek (uptr->fileref, tpos, SEEK_SET);
1852     i = sim_fread (&bc, sizeof (t_tpclnt), 1, uptr->fileref);
1853     if (i == 0)     /* past or at eof? */
1854         break;
1855     if (countmap[bc] == 0)
1856         sizec++;
1857     ++countmap[bc];
1858     if (map && (objc < mapsize))
1859         map[objc] = tpos;
1860     if (bc) {
1861         sim_debug (MTSE_DBG_STR, dptr, "tpc_map: %d byte count at pos: %" T_ADDR_FMT "u\n", bc, tpos);
1862         if (sim_deb && (dptr->dctrl & MTSE_DBG_STR)) {
1863             (void)sim_fread (recbuf, 1, bc, uptr->fileref);
1864             sim_data_trace(dptr, uptr, ((dptr->dctrl & MTSE_DBG_DAT) ? recbuf : NULL), "", bc, "Data Record", MTSE_DBG_STR);
1865             }
1866         }
1867     else
1868         sim_debug (MTSE_DBG_STR, dptr, "tpc_map: tape mark at pos: %" T_ADDR_FMT "u\n", tpos);
1869     objc++;
1870     tpos = tpos + ((bc + 1) & ~1) + sizeof (t_tpclnt);
1871     if ((bc == 0) && (last_bc == 0)) {  /* double tape mark? */
1872         had_double_tape_mark = objc;
1873         leot = tpos;
1874         }
1875     last_bc = bc;
1876     }
1877 sim_debug (MTSE_DBG_STR, dptr, "tpc_map: objc: %u, different record sizes: %u\n", objc, sizec);
1878 for (i=0; i<65535; i++) {
1879     if (countmap[i]) {
1880         if (i == 0)
1881             sim_debug (MTSE_DBG_STR, dptr, "tpc_map: summary - %u tape marks\n", countmap[i]);
1882         else
1883             sim_debug (MTSE_DBG_STR, dptr, "tpc_map: summary - %u %d byte record%s\n", countmap[i], (int)i, (countmap[i] > 1) ? "s" : "");
1884         }
1885     }
1886 if (((last_bc != 0xffff) &&
1887      (tpos > tape_size) &&
1888      (!had_double_tape_mark))    ||
1889     (!had_double_tape_mark)      ||
1890     ((objc == countmap[0]) &&
1891      (countmap[0] != 2))) {     /* Unreasonable format? */
1892     if (last_bc != 0xffff)
1893         sim_debug (MTSE_DBG_STR, dptr, "tpc_map: ERROR unexpected EOT byte count: %d\n", last_bc);
1894     if (tpos > tape_size)
1895         sim_debug (MTSE_DBG_STR, dptr, "tpc_map: ERROR next record position %" T_ADDR_FMT "u beyond EOT: %" T_ADDR_FMT "u\n", tpos, tape_size);
1896     if (objc == countmap[0])
1897         sim_debug (MTSE_DBG_STR, dptr, "tpc_map: ERROR tape cnly contains tape marks\n");
1898     free (countmap);
1899     free (recbuf);
1900     return 0;
1901     }
1902 
1903 if ((last_bc != 0xffff) && (tpos > tape_size)) {
1904     sim_debug (MTSE_DBG_STR, dptr, "tpc_map: WARNING unexpected EOT byte count: %d, double tape mark before %" T_ADDR_FMT "u provides logical EOT\n", last_bc, leot);
1905     objc = had_double_tape_mark;
1906     tpos = leot;
1907     }
1908 if (map)
1909     map[objc] = tpos;
1910 sim_debug (MTSE_DBG_STR, dptr, "tpc_map: OK objc: %d\n", objc);
1911 free (countmap);
1912 free (recbuf);
1913 return objc;
1914 }
1915 
1916 /* Check the basic structure of a SIMH format tape image */
1917 
sim_tape_simh_check(UNIT * uptr)1918 static t_stat sim_tape_simh_check (UNIT *uptr)
1919 {
1920 return SCPE_OK;
1921 }
1922 
1923 /* Check the basic structure of a E11 format tape image */
1924 
sim_tape_e11_check(UNIT * uptr)1925 static t_stat sim_tape_e11_check (UNIT *uptr)
1926 {
1927 return SCPE_OK;
1928 }
1929 
1930 /* Find the preceding record in a TPC file */
1931 
sim_tape_tpc_fnd(UNIT * uptr,t_addr * map)1932 static t_addr sim_tape_tpc_fnd (UNIT *uptr, t_addr *map)
1933 {
1934 uint32 lo, hi, p;
1935 
1936 
1937 if (map == NULL)
1938     return 0;
1939 lo = 0;
1940 hi = uptr->hwmark - 1;
1941 do {
1942     p = (lo + hi) >> 1;
1943     if (uptr->pos == map[p])
1944         return ((p == 0)? map[p]: map[p - 1]);
1945     else if (uptr->pos < map[p])
1946         hi = p - 1;
1947     else lo = p + 1;
1948     }
1949 while (lo <= hi);
1950 return ((p == 0)? map[p]: map[p - 1]);
1951 }
1952 
1953 /* Set tape capacity */
1954 
sim_tape_set_capac(UNIT * uptr,int32 val,CONST char * cptr,void * desc)1955 t_stat sim_tape_set_capac (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
1956 {
1957 t_addr cap;
1958 t_stat r;
1959 
1960 if ((cptr == NULL) || (*cptr == 0))
1961     return SCPE_ARG;
1962 if (uptr->flags & UNIT_ATT)
1963     return SCPE_ALATT;
1964 cap = (t_addr) get_uint (cptr, 10, sim_taddr_64? 2000000: 2000, &r);
1965 if (r != SCPE_OK)
1966     return SCPE_ARG;
1967 uptr->capac = cap * ((t_addr) 1000000);
1968 return SCPE_OK;
1969 }
1970 
1971 /* Show tape capacity */
1972 
sim_tape_show_capac(FILE * st,UNIT * uptr,int32 val,CONST void * desc)1973 t_stat sim_tape_show_capac (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
1974 {
1975 if (uptr->capac) {
1976     if (uptr->capac >= (t_addr) 1000000)
1977         fprintf (st, "capacity=%luMB", (unsigned long)(uptr->capac / ((t_addr) 1000000)));
1978     else {
1979         if (uptr->capac >= (t_addr) 1000)
1980             fprintf (st, "capacity=%luKB", (unsigned long)(uptr->capac / ((t_addr) 1000)));
1981         else
1982             fprintf (st, "capacity=%luB", (unsigned long)uptr->capac);
1983         }
1984     }
1985 else
1986     fprintf (st, "unlimited capacity");
1987 return SCPE_OK;
1988 }
1989 
1990 /* Set the tape density.
1991 
1992    Set the density of the specified tape unit either to the value supplied or to
1993    the value represented by the supplied character string.
1994 
1995    If "desc" is NULL, then "val" must be set to one of the MT_DENS_* constants
1996    in sim_tape.h other than MT_DENS_NONE; the supplied value is used as the tape
1997    density, and the character string is ignored.  Otherwise, "desc" must point
1998    at an int32 value containing a set of allowed densities constructed as a
1999    bitwise OR of the appropriate MT_*_VALID values.  In this case, the string
2000    pointed to by "cptr" will be parsed for a decimal value corresponding to the
2001    desired density in bits per inch and validated against the set of allowed
2002    values.
2003 
2004    In either case, SCPE_ARG is returned if the density setting is not valid or
2005    allowed.  If the setting is OK, the new density is set into the unit
2006    structure, and SCPE_OK is returned.
2007 */
2008 
sim_tape_set_dens(UNIT * uptr,int32 val,CONST char * cptr,void * desc)2009 t_stat sim_tape_set_dens (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
2010 {
2011 uint32 density, new_bpi;
2012 t_stat result = SCPE_OK;
2013 
2014 if (uptr == NULL)                                               /* if the unit pointer is null */
2015     return SCPE_IERR;                                           /*   then the caller has screwed up */
2016 
2017 else if (desc == NULL)                                          /* otherwise if a validation set was not supplied */
2018     if (val > 0 && val < (int32) BPI_COUNT)                     /*   then if a valid density code was supplied */
2019         uptr->dynflags = (uptr->dynflags & ~MTVF_DENS_MASK)     /*     then insert the code */
2020                            | (val << UNIT_V_DF_TAPE);           /*       in the unit flags */
2021     else                                                        /*   otherwise the code is invalid */
2022         return SCPE_ARG;                                        /*     so report a bad argument */
2023 
2024 else {                                                          /* otherwise a validation set was supplied */
2025     if (cptr == NULL || *cptr == 0)                             /*   but if no value is present */
2026         return SCPE_MISVAL;                                     /*     then report a missing value */
2027 
2028     new_bpi = (uint32) get_uint (cptr, 10, UINT_MAX, &result);  /* convert the string value */
2029 
2030     if (result != SCPE_OK)                                      /* if the conversion failed */
2031         return SCPE_ARG;                                        /*   then report a bad argument */
2032 
2033     else for (density = 0; density < BPI_COUNT; density++)      /* otherwise validate the density */
2034         if (new_bpi == bpi [density]                            /* if it matches a value in the list */
2035           && ((1 << density) & *(const int32 *) desc)) {        /*   and it's an allowed value */
2036             uptr->dynflags = (uptr->dynflags & ~MTVF_DENS_MASK) /*     then store the index of the value */
2037                                | density << UNIT_V_DF_TAPE;     /*       in the unit flags */
2038             return SCPE_OK;                                     /*         and return success */
2039             }
2040 
2041     result = SCPE_ARG;                                          /* if no match, then report a bad argument */
2042     }
2043 
2044 return result;                                                  /* return the result of the operation */
2045 }
2046 
2047 /* Show the tape density */
2048 
sim_tape_show_dens(FILE * st,UNIT * uptr,int32 val,CONST void * desc)2049 t_stat sim_tape_show_dens (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
2050 {
2051 uint32 tape_density;
2052 
2053 if (uptr == NULL)                                       /* if the unit pointer is null */
2054     return SCPE_IERR;                                   /*   then the caller has screwed up */
2055 
2056 else {                                                  /* otherwise get the density */
2057     tape_density = bpi [MT_DENS (uptr->dynflags)];      /*   of the tape from the unit flags */
2058 
2059     if (tape_density)                                   /* if it's set */
2060         fprintf (st, "density=%lu bpi",
2061             (unsigned long)tape_density);               /*   then report it */
2062     else                                                /* otherwise */
2063         fprintf (st, "density not set");                /*   it was never set by the caller */
2064     }
2065 
2066 return SCPE_OK;
2067 }
2068