root/src/simh/sim_tape.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _sim_tape_io_flush
  2. sim_tape_attach
  3. sim_tape_attach_ex
  4. sim_tape_detach
  5. sim_tape_attach_help
  6. sim_tape_data_trace
  7. sim_tape_rdlntf
  8. sim_tape_rdlntr
  9. sim_tape_rdrecf
  10. sim_tape_rdrecf_a
  11. sim_tape_rdrecr
  12. sim_tape_rdrecr_a
  13. sim_tape_wrrecf
  14. sim_tape_wrrecf_a
  15. sim_tape_wrdata
  16. sim_tape_wrtmk
  17. sim_tape_wrtmk_a
  18. sim_tape_wreom
  19. sim_tape_wreom_a
  20. sim_tape_wreomrw
  21. sim_tape_wreomrw_a
  22. sim_tape_wrgap
  23. sim_tape_wrgap_a
  24. sim_tape_sprecf
  25. sim_tape_sprecf_a
  26. sim_tape_sprecsf
  27. sim_tape_sprecsf_a
  28. sim_tape_sprecr
  29. sim_tape_sprecr_a
  30. sim_tape_sprecsr
  31. sim_tape_sprecsr_a
  32. sim_tape_spfilebyrecf
  33. sim_tape_spfilef
  34. sim_tape_spfilef_a
  35. sim_tape_spfilebyrecr
  36. sim_tape_spfilebyrecr_a
  37. sim_tape_spfiler
  38. sim_tape_spfiler_a
  39. sim_tape_rewind
  40. sim_tape_rewind_a
  41. sim_tape_reset
  42. sim_tape_bot
  43. sim_tape_eot
  44. sim_tape_wrp
  45. sim_tape_ioerr
  46. sim_tape_set_fmt
  47. sim_tape_show_fmt
  48. sim_tape_tpc_map
  49. sim_tape_simh_check
  50. sim_tape_e11_check
  51. sim_tape_tpc_fnd
  52. sim_tape_set_capac
  53. sim_tape_show_capac
  54. sim_tape_set_dens
  55. sim_tape_show_dens

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

/* [previous][next][first][last][top][bottom][index][help] */