1 /* sim_extension.c: SCP extension routines
2 
3    Copyright (c) 2019-2021, J. David Bryan
4 
5    Permission is hereby granted, free of charge, to any person obtaining a
6    copy of this software and associated documentation files (the "Software"),
7    to deal in the Software without restriction, including without limitation
8    the rights to use, copy, modify, merge, publish, distribute, sublicense,
9    and/or sell copies of the Software, and to permit persons to whom the
10    Software is furnished to do so, subject to the following conditions:
11 
12    The above copyright notice and this permission notice shall be included in
13    all copies or substantial portions of the Software.
14 
15    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18    THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22    Except as contained in this notice, the name of the author shall not be
23    used in advertising or otherwise to promote the sale, use or other dealings
24    in this Software without prior written authorization from the author.
25 
26    26-Apr-21    JDB     Added PAUSE command
27    24-Apr-21    JDB     BREAK DELAY and REPLY DELAY now accept times
28    25-Mar-21    JDB     Added -E switch to IF command
29                         Added "decode" parameter to "parse_quoted_string" routine
30    13-Mar-21    JDB     Added "ex_attach_unit" routine to position write-only files
31    31-Jan-21    JDB     Added down-conversion casts
32    16-Dec-20    JDB     Added "iso646.h" inclusion to use "not" for "!"
33    12-Dec-20    JDB     Added global definition for "ex_sim_execution_rate"
34                         Added timed execution breakpoints
35    21-Oct-20    JDB     A NULL poll pointer is now valid for MUX attach and detach
36    20-Oct-20    JDB     Added FLUSH command to post file contents to disc
37    25-Aug-20    JDB     Call of "vm_sim_vm_init" is now conditional on USE_VM_INIT
38    03-Aug-20    JDB     IF now retains remainder of breakpoint command list
39    06-Jul-20    JDB     Silence spurious compiler warning in "ex_if_cmd"
40    14-Feb-20    JDB     First release version
41    18-Mar-19    JDB     Created
42 
43 
44    This module implements extensions to the base Simulation Control Program
45    (SCP) front end for SIMH version 3.x.  The current extensions are:
46 
47      - host serial port support for the console and terminal multiplexers
48 
49      - automated prompt/response processing, initially for the system console,
50        but extendable to other keyboard/display units
51 
52      - concurrent console mode to enter SCP commands without stopping simulation
53 
54      - extended breakpoints for output string matching and timed execution
55 
56      - work-alikes for a subset of the SCP 4.x commands
57 
58      - execution of a global initialization file at simulator startup
59 
60      - automatic seek-to-end when attaching existing files to write-only units
61 
62    The module, and its associated declarations file (sim_extension.h) act as
63    shims between the front end and a simulator-specific back end, such as the HP
64    2100 or HP 3000 simulator.  Each simulator back end must have this inclusion:
65 
66      #include "sim_extension.h"
67 
68    ...placed in the "<sim>_defs.h" file that is included by all back-end
69    modules, and all back-end modules must be compiled with the inclusion.  Also,
70    this module (sim_extension.c) must be compiled and linked with the other SCP
71    and back-end modules.
72 
73    Extending SCP is possible by the use of hooks and shims.  SCP 3.x provides a
74    number of replaceable function and variable pointers (the "hooks") that may
75    be altered to implement custom behavior.  Hooks are necessary where the
76    internal behavior of the SCP must change -- for example, when adding new
77    command executors to the original table of commands.  A one-time initializer
78    within this module is called by SCP at simulator startup.  This initializer
79    points the desired hooks at functions within this module to implement the
80    extended actions.
81 
82    To extend the capability at the interface between the SCP front end and the
83    simulator-specific back end, shims are used to intercept the calls between
84    them.  One call from the front end to the back end -- "sim_instr" -- is
85    intercepted.  All of the other shims are for calls from the back end to
86    front-end services or global variables set by the back end.  The general
87    mechanism is to use a macro to rename a given function identifier within the
88    context of the back end. The new name refers to the extension shim, which,
89    internally, may call the original function.  For example, "sim_extension.h"
90    includes this macro:
91 
92      #define sim_putchar       ex_sim_putchar
93 
94    ...which is included during the back-end compilation.  Therefore, a back-end
95    module that called "sim_putchar" would actually be compiled to call
96    "ex_sim_putchar", a function within this module, instead.  Within the shim,
97    the macro substitution is not done, so a call to "sim_putchar" calls the
98    front-end function.
99 
100 
101    The following shims are provided:
102 
103      Shimmed Routine     Source Module   Shimming Routine
104      ================    =============   =================
105      tmxr_poll_conn      sim_tmxr.c      ex_tmxr_poll_conn
106 
107      sim_putchar         sim_console.c   ex_sim_putchar
108      sim_putchar_s       sim_console.c   ex_sim_putchar_s
109      sim_poll_kbd        sim_console.c   ex_sim_poll_kbd
110 
111      sim_brk_test        scp.c           ex_sim_brk_test
112      attach_unit         scp.c           ex_attach_unit
113 
114      sim_instr           hp----_cpu.c    vm_sim_instr
115      sim_vm_init         hp----_sys.c    vm_sim_vm_init
116      sim_vm_cmd          hp----_sys.c    vm_sim_vm_cmd
117 
118 
119    The following extensions are provided:
120 
121      Extension Routine   Module Extended  Extended Action
122      ==================  ===============  ===================================
123      tmxr_attach_unit    sim_tmxr.c       Attach a network or serial port
124      tmxr_detach_unit    sim_tmxr.c       Detach a network or serial port
125      tmxr_detach_line    sim_tmxr.c       Detach a serial port
126      tmxr_control_line   sim_tmxr.c       Control a line
127      tmxr_line_status    sim_tmxr.c       Get a line's status
128      tmxr_line_free      sim_tmxr.c       Check if a line is disconnected
129      tmxr_mux_free       sim_tmxr.c       Check if all lines are disconnected
130 
131 
132    The following extension hooks are provided:
133 
134      Hook Name                Hook Description
135      ======================   ===========================
136      vm_console_input_unit    Console input unit pointer
137      vm_console_output_unit   Console output unit pointer
138 
139 
140    The following SCP hooks are used:
141 
142      Hook Name           Source Module
143      ================    =============
144      sim_vm_init         scp.c
145      sim_vm_cmd          scp.c
146      sim_vm_unit_name    scp.c
147      sub_args            scp.c
148      sim_get_radix       scp.c
149 
150      tmxr_read           sim_tmxr.c
151      tmxr_write          sim_tmxr.c
152      tmxr_show           sim_tmxr.c
153      tmxr_close          sim_tmxr.c
154      tmxr_is_extended    sim_tmxr.c
155 
156    The extension hooks should be set by the back end to point at the console
157    units.  They are used to identify the console units for the REPLY and BREAK
158    commands.
159 
160    The VM-specific SCP hooks may be set as usual by the back end.  The extension
161    module intercepts these but ensures that they are called as part of extension
162    processing, so their effects are maintained.
163 
164 
165    Implementation notes:
166 
167     1. If the base set of options to the SHOW CONSOLE command is changed in
168        "scp.c", the "ex_show_console" routine below must be changed as well.
169        See the routine's implementation note for details.
170 
171     2. The existing SCP command table model presents some difficulties when
172        extending the command set.  When adding new commands that begin with the
173        same letter(s) as existing commands, the existing commands must be
174        duplicated in the extension table before the new ones.  This is required
175        to preserve the standard command abbreviation behavior when the
176        extension table is searched first.  For example, when adding a new CLEAR
177        command, the existing CONTINUE command must be duplicated and placed
178        ahead of the CLEAR entry, so that entering "C" invokes CONTINUE and not
179        CLEAR.  But duplicating the CONTINUE entry introduces the potential for
180        error if the standard CONTINUE entry is changed.  This means that
181        duplicated entries must be copied dynamically, which then means that the
182        new table cannot be a constant.
183 
184        Also, in some standard and extension cases, additional actions may need
185        to be taken for specific commands.  The method of identifying the
186        command by testing the "action" field of the resulting CTAB entry does
187        not work if the command has been replaced.  For instance, testing
188        "cmdp->action == &do_cmd" doesn't work if the DO command was replaced.
189        The only reliable way is to test the "name" field for the expected
190        command name string, i.e., testing "strcmp (cmdp->name, "DO") == 0",
191        which is both slow and awkward.
192 
193        Further, new commands may be valid only in certain contexts, e.g., in a
194        command file or only during some specific mode of execution.  There is
195        no easy way of passing context to the command executor, except by global
196        variables, as all executors receive the same parameters (an integer
197        argument and a character pointer).
198 
199        In short, we have to jump through some hoops in implementing the command
200        extensions.  These are described in the comments associated with their
201        appearances.
202 
203     3. The ISO 646 header file is included to permit the substitution of "not"
204        for the logical negation operator "!".  The latter is very hard to spot
205        in dense code and is easy to mistake for a numeral 1, a lower-case L, or
206        a vertical bar.
207 */
208 
209 
210 
211 #define COMPILING_EXTENSIONS                    /* defined to prevent shimming in sim_extension.h */
212 
213 #include <ctype.h>
214 #include <float.h>
215 #include <iso646.h>
216 #include <signal.h>
217 #include <time.h>
218 #include <sys/stat.h>
219 
220 #include "sim_defs.h"
221 #include "sim_serial.h"
222 #include "sim_extension.h"
223 
224 
225 
226 /* The following pragmas quell clang and Microsoft Visual C++ warnings that are
227    on by default but should not be, in my opinion.  They warn about the use of
228    perfectly valid code and require the addition of redundant parentheses and
229    braces to silence them.  Rather than clutter up the code with scores of extra
230    symbols that, in my view, make the code harder to read and maintain, I elect
231    to suppress these warnings.
232 
233    VC++ 2008 warning descriptions:
234 
235     - 4114: "same type qualifier used more than once" [legal per C99]
236 
237     - 4554: "check operator precedence for possible error; use parentheses to
238             clarify precedence"
239 
240     - 4996: "function was declared deprecated"
241 */
242 
243 #if defined (__clang__)
244 
245   #pragma clang diagnostic ignored "-Wlogical-not-parentheses"
246   #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
247   #pragma clang diagnostic ignored "-Wbitwise-op-parentheses"
248   #pragma clang diagnostic ignored "-Wshift-op-parentheses"
249   #pragma clang diagnostic ignored "-Wdangling-else"
250 
251 #elif defined (_MSC_VER)
252 
253   #pragma warning (disable: 4114 4554 4996)
254 
255 #endif
256 
257 
258 /* MSVC does not have the C-standard "snprintf" function */
259 
260 #if defined (_MSC_VER)
261   #define snprintf  _snprintf
262 #endif
263 
264 
265 /* Character constants (as integers) */
266 
267 #define BS                  0010
268 #define CR                  0015
269 #define LF                  0012
270 #define ESC                 0033
271 #define DEL                 0177
272 
273 
274 /* Flags for restricted-use commands */
275 
276 #define EX_GOTO             0
277 #define EX_CALL             1
278 #define EX_RETURN           2
279 #define EX_ABORT            3
280 
281 
282 /* External variable declarations (actually should be in sim_console.h) */
283 
284 extern TMXR sim_con_tmxr;
285 
286 
287 /* External routine declarations (actually should be in scp.h) */
288 
289 extern t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
290 
291 
292 /* One-time extension initializer */
293 
294 static void ex_initialize (void);
295 
296 
297 /* Hooks provided by SCP */
298 
299 void (*sim_vm_init) (void) = ex_initialize;     /* use our one-time initializer */
300 
301 
302 /* Hooks provided by us for use by the back end virtual machine */
303 
304 CTAB *vm_sim_vm_cmd          = NULL;            /* VM command extension table */
305 UNIT *vm_console_input_unit  = NULL;            /* console input unit pointer */
306 UNIT *vm_console_output_unit = NULL;            /* console output unit pointer */
307 
308 
309 /* Global extension variables */
310 
311 uint32 ex_sim_execution_rate = 0;               /* VM execution rate in instructions per second */
312 
313 
314 /* Pointer to the VM handler for unit names */
315 
316 static char * (*vm_unit_name_handler) (const UNIT *uptr) = NULL;
317 
318 
319 
320 /* Extended terminal multiplexer line descriptor.
321 
322    This structure extends the TMLN structure defined by the multiplexer library
323    to enable serial port support.  The TMLN structure contains a void extension
324    pointer, "exptr", which will be initialized to NULL by the line descriptor
325    declarations in the various multiplexer simulators.  For lines controlled by
326    extension routines, this pointer is changed to point at an EX_TMLN extension
327    structure, which is defined below.
328 
329 
330    Implementation notes:
331 
332     1. The name of the serial port is kept in an allocated buffer and referenced
333        by the UNIT's "filename" pointer.  The "sername" pointer points at the
334        same buffer; it is needed only to permit the "ex_tmxr_show" routine to
335        print the name when given the TMLN structure.  This pointer must NOT be
336        freed; the buffer is deallocated by freeing the "filename" pointer.
337 */
338 
339 typedef struct {                                /* extended line descriptor */
340     SERHANDLE  serport;                         /*   serial port handle */
341     char       *sername;                        /*   copy of the serial port name pointer */
342     t_bool     controlled;                      /*   TRUE if the modem lines are controlled */
343     TMCKT      signals;                         /*   modem control signals */
344     } EX_TMLN;
345 
346 
347 /* Pointers to the standard routines provided by the TMXR library */
348 
349 static int32 (*tmxr_base_read)  (TMLN *lp, int32 length);
350 static int32 (*tmxr_base_write) (TMLN *lp, int32 length);
351 static void  (*tmxr_base_show)  (TMLN *lp, FILE *stream);
352 static void  (*tmxr_base_close) (TMLN *lp);
353 
354 
355 /* Hooked terminal multiplexer replacement routine declarations */
356 
357 static int32  ex_tmxr_read     (TMLN *lp, int32 length);
358 static int32  ex_tmxr_write    (TMLN *lp, int32 length);
359 static void   ex_tmxr_show     (TMLN *lp, FILE *stream);
360 static void   ex_tmxr_close    (TMLN *lp);
361 static t_bool ex_tmxr_extended (TMLN *lp);
362 
363 
364 /* Local terminal multiplexer extension routine declarations */
365 
366 static t_stat  ex_tmxr_attach_line (TMXR *mp, UNIT *uptr, char *cptr);
367 static EX_TMLN *serial_line        (TMLN *lp);
368 
369 
370 
371 /* Extended breakpoint types.
372 
373 
374    Implementation notes:
375 
376     1. Twenty-five breakpoint types, designated by the letters A-S and U-Z, are
377        reserved for VM use.  These breakpoint types are referenced by adding the
378        corresponding switches to the BREAK command.  The "T" type is reserved to
379        indicate a temporary breakpoint that is automatically removed when it is
380        triggered.  As the "sim_brk_summ" global that holds the currently defined
381        breakpoint types is a 32-bit value, six additional breakpoint types
382        (ASCII codes 133-140 octal) may be defined.  These types cannot be
383        specified by the user, as the SCP "get_switches" routine accepts only
384        alphabetic switch characters.
385 */
386 
387 #define BP_TIMED            (SWMASK ('^'))              /* the timed breakpoint type */
388 #define BP_STRING           (SWMASK ('_'))              /* the default string breakpoint type */
389 
390 #define BP_EXTENDED         (BP_TIMED | BP_STRING)      /* all extended breakpoint types */
391 
392 
393 /* String breakpoint structure.
394 
395    String breakpoints are implemented by shimming the terminal output routines
396    and matching each output character to a breakpoint string.  A string
397    breakpoint structure holds the character string to be matched and some
398    additional data that defines how the breakpoint is handled.  The structure is
399    populated by a BREAK command having a quoted string parameter.  The structure
400    may exist only until the breakpoint occurs (a "temporary" breakpoint) or
401    until a NOBREAK command is issued to cancel it (a "permanent" breakpoint).
402    Consequently, a breakpoint may be reused and so much contain enough
403    information to allow the breakpoint to be reset after it triggers.
404 
405    The set of active breakpoint structures are maintained in a linked list
406    headed by the "sb_list" global variable.
407 
408 
409    Implementation notes:
410 
411     1. The structure contains some fields (e.g., "count") that are not used in
412        the initial implementation.  They are present for future expansion.
413 
414     2. String breakpoints are initially implemented for the system console only.
415        Future expansion to allow breakpoints on terminal multiplexer lines is
416        envisioned.
417 
418     3. The trigger field contains the simulation global time at which a matched
419        breakpoint should trigger.  It is set to -1 if the breakpoint has not yet
420        matched (i.e., is still pending).
421 */
422 
423 typedef struct sbnode {                         /* string breakpoint descriptor */
424     UNIT          *uptr;                        /*   output unit pointer */
425     char          match [CBUFSIZE];             /*   match string */
426     char          *mptr;                        /*   match pointer */
427     int32         type;                         /*   mask of breakpoint types */
428     int32         count;                        /*   proceed count */
429     int32         delay;                        /*   trigger enable delay */
430     double        trigger;                      /*   trigger time */
431     char          action [CBUFSIZE];            /*   action string */
432     struct sbnode *next;                        /*   pointer to the next entry in the list */
433     } STRING_BREAKPOINT;
434 
435 typedef STRING_BREAKPOINT *SBPTR;               /* the string breakpoint node pointer type */
436 
437 static SBPTR sb_list = NULL;                    /* the pointer to the head of the breakpoint list */
438 
439 
440 /* String breakpoint local SCP support routines */
441 
442 static t_stat string_bp_service (UNIT *uptr);
443 
444 
445 /* String breakpoint SCP data structures */
446 
447 /* Unit list */
448 
449 static UNIT string_bp_unit [] = {
450 /*           Event Routine        Unit Flags  Capacity  Delay */
451 /*           -------------------  ----------  --------  ----- */
452     { UDATA (&string_bp_service,  UNIT_IDLE,     0),      0   }
453     };
454 
455 
456 /* Reply structure.
457 
458    Replies are implemented by shimming the terminal input routines and supplying
459    characters one-at-a-time from a response string.  A reply structure holds the
460    character string to be supplied and some additional data that defines how the
461    reply is handled.  The structure is populated by a REPLY command having a
462    quoted string parameter.  The structure exists only until the reply is
463    completed.
464 
465 
466    Implementation notes:
467 
468     1. Replies are initially implemented for the system console only.  Future
469        expansion to allow replies on terminal multiplexer lines is envisioned.
470 
471     2. Only a single reply may exist in the initial implementation.  Multiple
472        replies for different devices would be implemented by a linked-list of
473        structures that are allocated dynamically.  Initially, though, the
474        "reply" global points at a single static structure instead of the head of
475        a list of structures.
476 
477     3. A reply is pending if "rptr" points at "reply [0]" and the current
478        simulation time is earlier than the "trigger" time.
479 */
480 
481 typedef struct rpnode {                         /* reply descriptor */
482     UNIT          *uptr;                        /*   input unit pointer */
483     char          reply [CBUFSIZE];             /*   reply string */
484     char          *rptr;                        /*   reply pointer */
485     double        trigger;                      /*   trigger time */
486     struct rpnode *next;                        /*   pointer to the next entry in the list */
487     } REPLY;
488 
489 typedef REPLY *RPPTR;                           /* the reply node pointer type */
490 
491 static RPPTR rp_list = NULL;                    /* the pointer to the head of the reply list */
492 
493 static REPLY rpx;                               /* the initial reply descriptor */
494 
495 
496 /* Local default break and reply delay declarations */
497 
498 static int32 break_delay = 0;
499 static int32 reply_delay = 0;
500 
501 
502 /* Local string breakpoint extension routine declarations */
503 
504 static void  free_breakpoints (void);
505 static SBPTR find_breakpoint  (char *match, SBPTR *prev);
506 static void  test_breakpoint  (int32 test_char);
507 
508 
509 /* Timed breakpoint local variables */
510 
511 static char timed_bp_actions [CBUFSIZE];        /* the timed breakpoint actions */
512 
513 
514 /* Timed breakpoint local SCP support routines */
515 
516 static t_stat timed_bp_service (UNIT *uptr);
517 
518 
519 /* Timed breakpoint SCP data structures */
520 
521 /* Unit list */
522 
523 static UNIT timed_bp_unit [] = {
524 /*           Event Routine       Unit Flags  Capacity  Delay */
525 /*           ------------------  ----------  --------  ----- */
526     { UDATA (&timed_bp_service,  UNIT_IDLE,     0),      0   }
527     };
528 
529 
530 /* Extended breakpoint local SCP support routines */
531 
532 static char *breakpoint_name (const UNIT *uptr);
533 
534 
535 /* Concurrent console mode status returns */
536 
537 #define SCPE_EXEC   (SCPE_LAST + 1)             /* a command is ready to execute */
538 #define SCPE_ABORT  (SCPE_LAST + 2)             /* an ABORT command was entered */
539 
540 
541 /* Concurrent console mode enumerator */
542 
543 typedef enum {                                  /* keyboard mode enumerator */
544     Console,                                    /*   keystrokes are sent to the console */
545     Command                                     /*   keystrokes are sent to the command buffer */
546     } KEY_MODE;
547 
548 
549 /* Signal handler function type */
550 
551 typedef void (*SIG_HANDLER) (int);
552 
553 
554 /* Concurrent console mode local variables */
555 
556 static t_bool concurrent_mode = TRUE;           /* the console mode, initially in Concurrent mode */
557 
558 static KEY_MODE keyboard_mode = Console;        /* the keyboard mode, initially delivered to the console */
559 
560 static char cmd_buf [CBUFSIZE];                 /* the concurrent console command buffer */
561 static char *cmd_ptr;                           /* the command buffer pointer */
562 static char *concurrent_do_ptr = NULL;          /* the pointer to a concurrent DO command line */
563 
564 static t_bool concurrent_run = FALSE;           /* TRUE if the VM is executing in concurrent mode */
565 static t_bool stop_requested = FALSE;           /* TRUE if a CTRL+E was entered by the user */
566 
567 
568 /* Concurrent console mode local routine declarations */
569 
570 static void   wru_handler (int sig);
571 static void   put_string  (const char *cptr);
572 static t_stat get_command (char *cptr, CTAB **cmdp);
573 
574 
575 
576 /* Local command extension declarations */
577 
578 static int32 ex_quiet = 0;                      /* a copy of the global "sim_quiet" setting  */
579 
580 
581 /* Command handler function type */
582 
583 typedef t_stat CMD_HANDLER (int32 flag, char *cptr);
584 
585 
586 /* Command extension handler function declarations */
587 
588 static t_stat ex_break_cmd      (int32 flag, char *cptr);
589 static t_stat ex_reply_cmd      (int32 flag, char *cptr);
590 static t_stat ex_run_cmd        (int32 flag, char *cptr);
591 static t_stat ex_do_cmd         (int32 flag, char *cptr);
592 static t_stat ex_if_cmd         (int32 flag, char *cptr);
593 static t_stat ex_delete_cmd     (int32 flag, char *cptr);
594 static t_stat ex_flush_cmd      (int32 flag, char *cptr);
595 static t_stat ex_pause_cmd      (int32 flag, char *cptr);
596 static t_stat ex_restricted_cmd (int32 flag, char *cptr);
597 static t_stat ex_set_cmd        (int32 flag, char *cptr);
598 static t_stat ex_show_cmd       (int32 flag, char *cptr);
599 
600 
601 /* Command extension table.
602 
603    This table defines commands and command behaviors that are specific to this
604    extension.  Specifically:
605 
606      * RUN and GO accept an UNTIL clause, and all forms provide a quiet DO mode
607 
608      * BREAK and NOBREAK provide temporary and string breakpoints
609 
610      * REPLY and NOREPLY provide console responses
611 
612      * DO provides transfers to labels and quiet stops
613 
614      * IF and GOTO add conditional command file execution
615 
616      * DELETE adds platform-independent file purges
617 
618      * PAUSE adds platform-independent time delays to command file executions
619 
620      * CALL and RETURN add labeled subroutine invocations
621 
622      * ABORT stops execution of the current and any nested DO command files
623 
624      * SET adds ENVIRONMENT to define environment variables
625        and CONSOLE CONCURRENT/NOCONCURRENT to set the console concurrency mode
626 
627      * SHOW adds string breakpoints to BREAK and adds REPLY for replies
628 
629    The table is initialized with only those fields that differ from the standard
630    command table.  During one-time initialization, empty or zero fields are
631    filled in from the corresponding standard command table entries.  This
632    ensures that the extension table automatically picks up any changes to the
633    standard commands that it modifies.
634 
635 
636    Implementation notes:
637 
638     1. The RESET and DEPOSIT commands are duplicated from the standard SCP
639        command table so that entering "R" doesn't invoke the RUN command and
640        entering "D" doesn't invoke the DO command.  This would otherwise occur
641        because the extension command table is searched before the standard
642        command table.  Similarly, the ATTACH, ASSIGN, and ASSERT commands are
643        duplicated so that entering "A" doesn't invoke the ABORT command.
644 
645     2. The "execute_file" routine needs to do special processing for the RUN,
646        GO, STEP, CONTINUE, and BOOT commands.  Unfortunately, there is no easy
647        way to determine these commands from their CTAB entries.  We cannot
648        simply check the action routine pointers, because the VM may override
649        them, potentially with five different routines.  We can't use special
650        argument values, because they are used by the standard RUN executor to
651        differentiate between the various commands (and they are not unique to
652        these commands).  What we need is a VM extension field in the CTAB
653        structure, but there isn't one.  So we rely on the hack that nothing in
654        3.x uses the "help_base" field, which was added for 4.x compatibility. We
655        therefore indicate a RUN, etc. command by setting the "help_base" field
656        non-null (the actual value is, so far, immaterial).
657 */
658 
659 static CTAB ex_cmds [] = {
660 /*    Name        Action Routine     Argument     Help String                                                       */
661 /*    ----------  -----------------  -----------  ----------------------------------------------------------------- */
662     { "RESET",    NULL,               0,          NULL                                                              },
663     { "DEPOSIT",  NULL,               0,          NULL                                                              },
664     { "ATTACH",   NULL,               0,          NULL                                                              },
665     { "ASSIGN",   NULL,               0,          NULL                                                              },
666     { "ASSERT",   NULL,               0,          NULL                                                              },
667 
668     { "RUN",      ex_run_cmd,         0,          NULL, "RUN"                                                       },
669     { "GO",       ex_run_cmd,         0,          NULL, "RUN"                                                       },
670     { "STEP",     ex_run_cmd,         0,          NULL, "RUN"                                                       },
671     { "CONTINUE", ex_run_cmd,         0,          NULL, "RUN"                                                       },
672     { "BOOT",     ex_run_cmd,         0,          NULL, "RUN"                                                       },
673 
674     { "BREAK",    ex_break_cmd,       0,          NULL                                                              },
675     { "NOBREAK",  ex_break_cmd,       0,          NULL                                                              },
676 
677     { "REPLY",    ex_reply_cmd,       0,          "reply <string> {<delay>} send characters to the console\n"       },
678     { "NOREPLY",  ex_reply_cmd,       1,          "noreply                  cancel a pending reply\n"               },
679 
680     { "DO",       ex_do_cmd,          1,          NULL                                                              },
681 
682     { "IF",       ex_if_cmd,          0,          "if <cond> <cmd>;...      execute commands if condition TRUE\n"   },
683     { "DELETE",   ex_delete_cmd,      0,          "del{ete} <file>          delete a file\n"                        },
684     { "FLUSH",    ex_flush_cmd,       0,          "f{lush}                  flush all open files to disc\n"         },
685     { "PAUSE",    ex_pause_cmd,       0,          "pause <time> <units>     pause for a specified time\n"           },
686 
687     { "GOTO",     ex_restricted_cmd,  EX_GOTO,    "goto <label>             transfer control to the labeled line\n" },
688     { "CALL",     ex_restricted_cmd,  EX_CALL,    "call <label> {<par>...}  call the labeled subroutine\n"          },
689     { "RETURN",   ex_restricted_cmd,  EX_RETURN,  "return                   return control from a subroutine\n"     },
690     { "ABORT",    ex_restricted_cmd,  EX_ABORT,   "abort                    abort nested command files\n"           },
691 
692     { "SET",      ex_set_cmd,         0,          NULL                                                              },
693     { "SHOW",     ex_show_cmd,        0,          NULL                                                              },
694 
695     { NULL }
696     };
697 
698 static const uint32 ex_cmd_count = sizeof ex_cmds / sizeof ex_cmds [0]; /* the count of commands in the table */
699 
700 
701 /* Standard front-end command handler pointer declarations */
702 
703 static CMD_HANDLER *break_handler = NULL;
704 static CMD_HANDLER *run_handler   = NULL;
705 static CMD_HANDLER *set_handler   = NULL;
706 static CMD_HANDLER *show_handler  = NULL;
707 
708 
709 /* Extended command handler pointer declarations */
710 
711 static CMD_HANDLER *ex_do_handler = NULL;
712 
713 
714 /* SET/SHOW command extension handler routine declarations */
715 
716 static t_stat ex_set_console     (int32 flag, char *cptr);
717 static t_stat ex_set_environment (int32 flag, char *cptr);
718 static t_stat ex_set_concurrent  (int32 flag, char *cptr);
719 static t_stat ex_set_serial      (int32 flag, char *cptr);
720 
721 static t_stat ex_show_console    (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
722 static t_stat ex_show_break      (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
723 static t_stat ex_show_reply      (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
724 static t_stat ex_show_delays     (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
725 static t_stat ex_show_concurrent (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
726 static t_stat ex_show_serial     (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
727 
728 
729 /* Hooked command extension replacement routine declarations */
730 
731 static void  ex_substitute_args (char *iptr, char *optr, int32 bufsize, char *args []);
732 static int32 ex_get_radix       (const char *cptr, int32 switches, int32 default_radix);
733 
734 
735 /* Local command extension routine declarations */
736 
737 static t_stat execute_file         (FILE *file, int32 flag, char *cptr);
738 static t_stat goto_label           (FILE *stream, char *cptr);
739 static t_stat gosub_label          (FILE *stream, char *filename, int32 flag, char *cptr);
740 static void   replace_token        (char **out_ptr, int32 *out_size, char *token_ptr);
741 static void   copy_string          (char **target, int32 *target_size, const char *source, int32 source_size);
742 static char   *parse_quoted_string (char *sptr, char *dptr, t_bool upshift, t_bool decode);
743 static t_stat parse_time           (char *keyword, char **cptr, int32 *ticks);
744 static char   *encode              (const char *source);
745 
746 
747 
748 /* *********************  Extension Module Initializer  ************************
749 
750    This routine is called once by the SCP startup code.  It fills in the
751    extension command table from the corresponding system command table entries,
752    saves pointers to the original system command handlers where needed, and
753    installs the extension command table and argument substituter.
754 
755    If the VM defines an initializer, it is called.  Then if the VM set up its
756    own command table, this routine merges the two auxiliary tables, ensuring
757    that any VM-defined commands override the corresponding extension commands.
758    This is required because SCP only allows a single user-specified command
759    table.
760 
761 
762    Implementation notes:
763 
764     1. The "ex_cmd_count" includes the NULL entry at the end of the extension
765        command table, so adding in any VM command table entries gives the total
766        command count plus one for the NULL entry, as is needed for the correct
767        memory allocation size.
768 
769     2. Because the initializer is a void function, it cannot return an
770        indication that the memory allocation failed.  If it did, the VM-defined
771        command table will be ignored.
772 
773     3. For those overriding extension commands that will call the system command
774        handlers (e.g., BREAK), we save the action routine pointers before
775        installing the extension command table.  Similarly, those overriding VM
776        commands that intend to call the system command handlers will save the
777        corresponding action pointers.  However, because the extension table is
778        installed before calling the VM initializer, the VM's action pointers
779        will point at the extension command handlers.  So, for example, a BREAK
780        command calls the VM BREAK handler, which in turn calls the extension
781        BREAK handler, which in turn calls the system BREAK handler.
782 */
783 
ex_initialize(void)784 static void ex_initialize (void)
785 {
786 uint32 cmd_count;
787 CTAB   *systab, *vmtab, *extab = ex_cmds;
788 
789 while (extab->name != NULL) {                           /* loop through the extension command table */
790     systab = find_cmd (extab->name);                    /* find the corresponding system command table entry */
791 
792     if (systab != NULL) {                               /* if it is present */
793         if (extab->action == NULL)                      /*   then if the action routine field is empty */
794             extab->action = systab->action;             /*     then fill it in */
795 
796         if (extab->arg == 0)                            /* if the command argument field is empty */
797             extab->arg = systab->arg;                   /*   then fill it in */
798 
799         if (extab->help == NULL)                        /* if the help string field is empty */
800             extab->help = systab->help;                 /*   then fill it in */
801 
802         if (extab->help_base == NULL)                   /* if the help base string field is empty */
803             extab->help_base = systab->help_base;       /*   then fill it in */
804 
805         extab->message = systab->message;               /* fill in the message field as we never override it */
806         }
807 
808     extab++;                                            /* point at the next table entry */
809     }
810 
811 break_handler = find_cmd ("BREAK")->action;             /* set up the BREAK/NOBREAK command handler */
812 run_handler   = find_cmd ("RUN")->action;               /*   and the RUN/GO command handler */
813 set_handler   = find_cmd ("SET")->action;               /*     and the SET command handler */
814 show_handler  = find_cmd ("SHOW")->action;              /*       and the SHOW command handler */
815 
816 sim_vm_cmd    = ex_cmds;                                /* set up the extension command table */
817 sub_args      = ex_substitute_args;                     /*   and argument substituter */
818 sim_get_radix = ex_get_radix;                           /*     and EX/DEP/SET radix configuration */
819 
820 #if defined (USE_VM_INIT)
821 if (vm_sim_vm_init != NULL)                             /* if the VM has a one-time initializer */
822     vm_sim_vm_init ();                                  /*   then call it now */
823 #endif
824 
825 vm_unit_name_handler = sim_vm_unit_name;                /* save the unit name hook in case the VM set it */
826 sim_vm_unit_name = breakpoint_name;                     /*   and substitute our own */
827 
828 if (vm_sim_vm_cmd != NULL) {                            /* if the VM defines its own command table */
829     cmd_count = ex_cmd_count;                           /*   then extension table entry count */
830 
831     for (vmtab = vm_sim_vm_cmd; vmtab->name != NULL; vmtab++)   /* add the number of VM command entries */
832         cmd_count = cmd_count + 1;                              /*   to the number of extension entries */
833 
834     systab = (CTAB *) calloc (cmd_count, sizeof (CTAB));    /* allocate a table large enough to hold all entries */
835 
836     if (systab != NULL) {                               /* if the allocation succeeded */
837         memcpy (systab, ex_cmds, sizeof ex_cmds);       /*   then populate the extension commands first */
838 
839         for (vmtab = vm_sim_vm_cmd; vmtab->name != NULL; vmtab++) { /* for each VM command */
840             for (extab = systab; extab->name != NULL; extab++)      /*   if it overrides */
841                 if (strcmp (extab->name, vmtab->name) == 0) {       /*     an extension command */
842                     memcpy (extab, vmtab, sizeof (CTAB));           /*       then replace the extension entry */
843                     break;                                          /*         with the VM entry */
844                     }
845 
846             if (extab->name == NULL)                    /* if the VM command does not match an extension command */
847                 memcpy (extab, vmtab, sizeof (CTAB));   /*   then add it to the end of the table */
848             }
849 
850         sim_vm_cmd = systab;                            /* install the combined VM and extension table */
851         }
852     }
853 
854 ex_do_handler = find_cmd ("DO")->action;                /* get the address of the extended DO command handler */
855 
856 ex_quiet = sim_quiet;                                   /* save the global quietness setting */
857 
858 tmxr_base_read  = tmxr_read;                            /* get the dedicated socket reader */
859 tmxr_read       = ex_tmxr_read;                         /*   and replace it with the generic reader */
860 
861 tmxr_base_write = tmxr_write;                           /* do the same */
862 tmxr_write      = ex_tmxr_write;                        /*   for the generic writer */
863 
864 tmxr_base_show  = tmxr_show;                            /* and the same */
865 tmxr_show       = ex_tmxr_show;                         /*   for the generic show */
866 
867 tmxr_base_close = tmxr_close;                           /* and the same */
868 tmxr_close      = ex_tmxr_close;                        /*   for the generic closer */
869 
870 tmxr_is_extended = ex_tmxr_extended;                    /* install the extension detection hook */
871 
872 return;
873 }
874 
875 
876 
877 /* ********************  Terminal Multiplexer Extensions  **********************
878 
879    This module extends the following existing routines in "sim_tmxr.c":
880 
881      tmxr_poll_conn -- poll for new network or serial connections
882 
883    ...and adds the following new routines:
884 
885      tmxr_attach_unit   -- attach a network or serial port
886      tmxr_detach_unit   -- detach a network or serial port
887      tmxr_detach_line   -- detach a serial port
888      tmxr_control_line  -- control a line
889      tmxr_line_status   -- get a line' status
890      tmxr_line_free     -- return TRUE if a specified line is disconnected
891      tmxr_mux_free      -- return TRUE if all lines and network port are disconnected
892 
893    The module implementation requires that multiline multiplexers define a unit
894    per line and that unit numbers correspond to line numbers.
895 
896    Multiplexer lines may be connected to terminal emulators supporting the
897    Telnet protocol via sockets, or to hardware terminals via host serial ports.
898    Concurrent Telnet and serial connections may be mixed on multiline
899    multiplexers.
900 
901    When connecting via serial ports, individual multiplexer lines are attached
902    to specific host ports using port names appropriate for the host system:
903 
904      sim> attach MUX2 com1      (or /dev/ttyS0)
905 
906    Serial port parameters may be optionally specified:
907 
908      sim> attach MUX2 com1;9600-8n1
909 
910    If the port parameters are omitted, then the host system defaults for the
911    specified port are used.  The port is allocated during the attach call, but
912    the actual connection is deferred until the multiplexer is polled for
913    connections, as with Telnet connections.
914 
915    Individual lines may be disconnected from serial ports with:
916 
917      sim> detach MUX2
918 
919    Telnet and serial port connections may be dropped with:
920 
921      sim> set MUX2 DISCONNECT
922 
923    This will disconnect a Telnet client and will drop the Data Terminal Ready
924    (DTR) signal on a serial port for 500 milliseconds.  The serial port remains
925    attached to the line.
926 
927 
928    Single-line devices may be attached either to a Telnet listening port or to a
929    serial port.  The device attach routine may be passed either a port number or
930    a serial port name.  This routine calls "tmxr_attach_unit".  If the return
931    value is SCPE_OK, then a Telnet port number or serial port name was passed
932    and was opened.  Otherwise, the attachment failed, and the returned status
933    code value should be reported.
934 
935    The device detach routine calls "tmxr_detach_unit" to close either the Telnet
936    listening port or the serial port.
937 
938    Multi-line devices with a unit per line and a separate poll unit attach
939    serial ports to the former and a Telnet listening port to the latter.  Both
940    types of attachments may be made concurrently.  The system ATTACH and DETACH
941    commands are used, for example:
942 
943      sim> attach MUX 1050       -- attach the listening port
944      sim> attach MUX0 com1      -- attach a serial port
945 
946    SCP passes the same pointer to unit 0 in both cases.  However, the SCP global
947    variable "sim_ref_type" will indicate whether the device (REF_DEVICE) or a
948    unit (REF_UNIT) was specified.  In the cases of a RESTORE or a DETACH ALL,
949    where the user does not specify a device or unit, "sim_ref_type" will be set
950    to REF_NONE.
951 
952    After a line is detached, the device simulator should clear the "rcve" field
953    of the associated line descriptor.  However, detaching a listening port will
954    disconnect all active Telnet lines but will not disturb any serial lines.
955    Consequently, the device simulator cannot determine whether or not a
956    multiplexer is active solely by examining the UNIT_ATT flag on the poll unit.
957    Instead, the "tmxr_line_free" routine should be called for each line, and
958    reception on the line should be inhibited if the routine returns TRUE.
959 
960    Finally, the "tmxr_mux_free" routine should be called to determine if the
961    multiplexer is now free (i.e., the listening port is detached and no other
962    serial connections exist).  If the routine returns TRUE, the poll service may
963    be stopped.
964 
965    The "tmxr_attach_unit" and "tmxr_detach_unit" decide which type of port to
966    use by examining the UNIT_ATTABLE flag on the supplied unit.  If the flag is
967    present, then a Telnet port attach/detach is attempted.  If the flag is
968    missing, then a serial port is assumed.  Multiline multiplexers therefore
969    will specify UNIT_ATTABLE on the poll unit and not on the line units.
970 
971    Serial port connections are only supported on multiplexer lines that appear
972    in the line connection order array.  If the line is not present, or the
973    default value (i.e., -1 indicating all lines are to be connected) is not set,
974    the attachment is rejected with a "Unit not attachable" error.
975 
976 
977    Implementation notes:
978 
979     1. The system RESTORE command does not restore devices having the DEV_NET
980        flag.  This flag indicates that the device employs host-specific port
981        names that are non-transportable across RESTOREs.
982 
983        If a multiplexer specifies DEV_NET, the device connection state will not
984        be altered when a RESTORE is done.  That is, all current connections,
985        including Telnet sessions, will remain untouched, and connections
986        specified at the time of the SAVE will not be reestablished during the
987        RESTORE.  If DEV_NET is not specified, then the system will attempt to
988        restore the attachment state present at the time of the SAVE, including
989        Telnet listening and serial ports.  Telnet client sessions on individual
990        multiplexer lines cannot be reestablished by RESTORE and must be
991        reestablished manually.
992 
993     2. Single-line multiplexers must set or clear UNIT_ATTABLE on the unit
994        representing the line dynamically, depending on whether a numeric
995        (listening port) or non-numeric (serial port) value is specified.
996        Multiline unit-per-line multiplexers should not have UNIT_ATTABLE on the
997        units representing the lines.  UNIT_ATTABLE does not affect the
998        attachability when VM-specific attach routines are employed, although it
999        does determine which type of port is to be attached.  UNIT_ATTABLE also
1000        controls the reporting of attached units for the SHOW <dev> command.
1001 
1002        A single-line device will be either detached, attached to a listening
1003        port, or attached to a serial port.  With UNIT_ATTABLE, the device will
1004        be reported as "not attached," "attached to 1050" (e.g.), or "attached to
1005        COM1" (e.g.), which is desirable.
1006 
1007        A unit-per-line device will report the listening port as attached to the
1008        device (or to a separate device).  The units representing lines either
1009        will be connected to a Telnet session or attached to a serial port.
1010        Telnet sessions are not reported by SHOW <dev>, so having UNIT_ATTABLE
1011        present will cause each non-serial line to be reported as "not attached,"
1012        even if there may be a current Telnet connection.  This will be confusing
1013        to users.  Without UNIT_ATTABLE, attachment status will be reported only
1014        if the line is attached to a serial port, which is preferable.
1015 */
1016 
1017 
1018 /* Global terminal multiplexer extension routines */
1019 
1020 
1021 /* Attach a network or serial port.
1022 
1023    This extension for "tmxr_attach" attempts to attach the network or serial
1024    port name specified by "cptr" to the multiplexer line associated with mux
1025    descriptor pointer "mp" and unit pointer "uptr".  The unit is implicitly
1026    associated with the line number corresponding to the position of the unit in
1027    the zero-based array of units belonging to the associated device.
1028 
1029    The validity of the attachment is determined by the presence or absence of
1030    the UNIT_ATTABLE ("unit is attachable") flag on the unit indicated by "uptr".
1031    The Telnet poll unit will have this flag; the individual line units will not.
1032    The presence or absence of the flag determines the type of attachment to
1033    attempt.
1034 
1035    If a device is referenced, the poll unit specified by the "pptr" parameter is
1036    attached instead of the referenced unit.  This is because a device reference
1037    passes a pointer to unit 0 (i.e., ATTACH MUX and ATTACH MUX0 both set "uptr"
1038    to point at unit 0).  If the poll unit is not defined, then the device attach
1039    is rejected.
1040 
1041    An attempt to attach the poll unit directly via a unit reference will be
1042    rejected by the "ex_tmxr_attach_line" routine because the unit does not
1043    correspond to a multiplexer port.
1044 */
1045 
ex_tmxr_attach_unit(TMXR * mp,UNIT * pptr,UNIT * uptr,char * cptr)1046 t_stat ex_tmxr_attach_unit (TMXR *mp, UNIT *pptr, UNIT *uptr, char *cptr)
1047 {
1048 t_stat status;
1049 
1050 if (sim_ref_type == REF_DEVICE)                         /* if this is a device reference */
1051     if (pptr == NULL)                                   /*   then if the poll unit is not defined */
1052         return SCPE_NOATT;                              /*     then report that the attach failed */
1053     else                                                /*   otherwise */
1054         uptr = pptr;                                    /*     substitute the poll unit */
1055 
1056 if (mp == NULL || uptr == NULL)                         /* if the descriptor or unit pointer is null */
1057     status = SCPE_IERR;                                 /*   then report an internal error */
1058 
1059 else if (sim_ref_type != REF_UNIT                       /* otherwise if this is a device or null reference */
1060   && uptr->flags & UNIT_ATTABLE)                        /*   and the poll unit is attachable */
1061     status = tmxr_attach (mp, uptr, cptr);              /*     then try to attach a listening port */
1062 
1063 else                                                    /* otherwise it's a unit reference */
1064     status = ex_tmxr_attach_line (mp, uptr, cptr);      /*   so try to attach a serial port */
1065 
1066 return status;                                          /* return the status of the attachment */
1067 }
1068 
1069 
1070 /* Detach a network or serial port.
1071 
1072    This extension for "tmxr_detach" attempts to detach the network or serial
1073    port from the multiplexer line associated with mux descriptor pointer "mp"
1074    and unit pointer "uptr".  The unit is implicitly associated with the line
1075    number corresponding to the position of the unit in the zero-based array of
1076    units belonging to the associated device.
1077 
1078    The validity of the detachment is determined by the presence or absence of
1079    the UNIT_ATTABLE ("unit is attachable") flag on the unit indicated by "uptr".
1080    The Telnet poll unit will have this flag; the individual line units will not.
1081    The presence or absence of the flag determines the type of detachment to
1082    attempt.
1083 
1084    If a device is referenced, the poll unit specified by the "pptr" parameter is
1085    detached instead of the referenced unit.  This is because a device reference
1086    passes a pointer to unit 0 (i.e., DETACH MUX and DETACH MUX0 both set "uptr"
1087    to point at unit 0).  If the poll unit is not defined, then the device detach
1088    is rejected.
1089 
1090    An attempt to detach the poll unit directly via a unit reference will be
1091    rejected by the "ex_tmxr_detach_line" routine because the unit does not
1092    correspond to a multiplexer port.
1093 */
1094 
ex_tmxr_detach_unit(TMXR * mp,UNIT * pptr,UNIT * uptr)1095 t_stat ex_tmxr_detach_unit (TMXR *mp, UNIT *pptr, UNIT *uptr)
1096 {
1097 t_stat status;
1098 
1099 if (sim_ref_type == REF_DEVICE)                         /* if this is a device reference */
1100     if (pptr == NULL)                                   /*   then if the poll unit is not defined */
1101         return SCPE_NOATT;                              /*     then report that the attach failed */
1102     else                                                /*   otherwise */
1103         uptr = pptr;                                    /*     substitute the poll unit */
1104 
1105 if (mp == NULL || uptr == NULL)                         /* if the descriptor or unit pointer is null */
1106     status = SCPE_IERR;                                 /*   then report an internal error */
1107 
1108 else if (sim_ref_type != REF_UNIT                       /* otherwise if this is a device or null reference */
1109   && uptr->flags & UNIT_ATTABLE)                        /*   and the poll unit is attachable */
1110     status = tmxr_detach (mp, uptr);                    /*     then try to detach a listening port */
1111 
1112 else                                                    /* otherwise it's a line unit */
1113     status = ex_tmxr_detach_line (mp, uptr);            /*   so try to detach a serial port */
1114 
1115 return status;                                          /* return the status of the detachment */
1116 }
1117 
1118 
1119 /* Detach a line from serial port.
1120 
1121    This extension routine disconnects and detaches a line of the multiplexer
1122    associated with mux descriptor pointer "mp" and unit pointer "uptr" from its
1123    serial port. The line number is given by the position of the unit in the
1124    zero-based array of units belonging to the associated device.  For example,
1125    if "uptr" points to unit 3 in a given device, then line 3 will be detached.
1126 
1127    If the specified unit does not correspond with a multiplexer line, then
1128    SCPE_NOATT is returned.  If the line is not connected to a serial port, then
1129    SCPE_UNATT is returned.  Otherwise, the port is disconnected, and SCPE_OK is
1130    returned.
1131 
1132 
1133    Implementation notes:
1134 
1135     1. If the serial connection had been completed, we disconnect the line,
1136        which drops DTR to ensure that a modem will disconnect.
1137 */
1138 
ex_tmxr_detach_line(TMXR * mp,UNIT * uptr)1139 t_stat ex_tmxr_detach_line (TMXR *mp, UNIT *uptr)
1140 {
1141 TMLN    *lp;
1142 EX_TMLN *exlp;
1143 
1144 if (uptr == NULL)                                       /* if this is a console reference */
1145     lp = mp->ldsc;                                      /*   point at the (only) line */
1146 else                                                    /* otherwise */
1147     lp = tmxr_find_ldsc (uptr, mp->lines, mp);          /*   determine the line from the unit */
1148 
1149 if (lp == NULL)                                         /* if the unit does not correspond to a line */
1150     return SCPE_NOATT;                                  /*   then report that the unit is not attachable */
1151 else                                                    /* otherwise */
1152     exlp = serial_line (lp);                            /*   get the serial line extension */
1153 
1154 if (exlp == NULL)                                       /* if the line is not a serial line */
1155     return SCPE_UNATT;                                  /*   then report that the unit is unattached */
1156 
1157 if (lp->conn)                                           /* if the connection has been completed */
1158     tmxr_disconnect_line (lp);                          /*   then disconnect the line */
1159 
1160 sim_close_serial (exlp->serport);                       /* close the serial port */
1161 free (exlp->sername);                                   /*   and free the port name */
1162 
1163 exlp->serport = INVALID_HANDLE;                         /* reinitialize the structure */
1164 exlp->sername = NULL;                                   /*   to show it is not connected */
1165 
1166 if (uptr != NULL) {                                     /* if this is not a console detach */
1167     uptr->filename = NULL;                              /*   then clear the port name pointer */
1168 
1169     uptr->flags &= ~UNIT_ATT;                           /* mark the unit as unattached */
1170     }
1171 
1172 return SCPE_OK;                                         /* return success */
1173 }
1174 
1175 
1176 /* Control a terminal line.
1177 
1178    This extension routine controls a multiplexer line, specified by the "lp"
1179    parameter, as though it were connected to a modem.  The caller designates
1180    that the line's Data Terminal Ready (DTR) and Request To Send (RTS) signals
1181    should be asserted or denied as specified by the "control" parameter.  If the
1182    line is connected to a Telnet session, dropping DTR will disconnect the
1183    session.  If the line is connected to a serial port, the signals are sent to
1184    the device connected to the hardware port, which reacts in a device-dependent
1185    manner.
1186 
1187    Calling this routine establishes VM control over the multiplexer line.
1188    Control is only relevant when a line is attached to a serial port.
1189 
1190    Initially, a line is uncontrolled.  In this state, attaching a line to a
1191    serial port automatically asserts DTR and RTS, and detaching the line drops
1192    both signals.  After this routine has been called, this default action no
1193    longer occurs, and it is the responsibility of the VM to raise and lower DTR
1194    and RTS explicitly.
1195 
1196    The caller may reset a line to the uncontrolled state by calling the routine
1197    with the "control" parameter set to the "Reset_Control" value.  Typically,
1198    this is only necessary if a RESET -P (i.e., power-on reset) is performed.
1199 
1200    If a null pointer is passed for the "lp" parameter, the routine returns
1201    SCPE_IERR.  If the line extension structure has not been allocated when the
1202    routine is called, it is allocated here.  If the allocation fails, SCPE_MEM
1203    is returned.  If the line is attached to a serial port, the serial control
1204    routine status (SCPE_OK or SCPE_IOERR) is returned.  Otherwise, the routine
1205    returns SCPE_OK.
1206 
1207 
1208    Implementation notes:
1209 
1210     1. The TMCKT and SERCIRCUIT types are renamings of the underlying
1211        RS232_SIGNAL type, so a type cast is valid.
1212 */
1213 
ex_tmxr_control_line(TMLN * lp,TMCKT control)1214 t_stat ex_tmxr_control_line (TMLN *lp, TMCKT control)
1215 {
1216 EX_TMLN *exlp;
1217 t_stat  status = SCPE_OK;
1218 
1219 if (lp == NULL)                                         /* if the line pointer is invalid */
1220     return SCPE_IERR;                                   /*   then report an internal error */
1221 else                                                    /* otherwise */
1222     exlp = (EX_TMLN *) lp->exptr;                       /*   point to the descriptor extension */
1223 
1224 if (exlp == NULL) {                                     /* if the extension has not been allocated */
1225     lp->exptr = malloc (sizeof (EX_TMLN));              /*   then allocate it now */
1226 
1227     if (lp->exptr == NULL)                              /* if the memory allocation failed */
1228         return SCPE_MEM;                                /*   then report the failure */
1229 
1230     else {                                              /* otherwise */
1231         exlp = (EX_TMLN *) lp->exptr;                   /*   point to the new descriptor extension */
1232 
1233         exlp->serport = INVALID_HANDLE;                 /* clear the serial port handle */
1234         exlp->sername = NULL;                           /*   and the port name */
1235         }
1236     }
1237 
1238 if (control == Reset_Control) {                         /* if a reset is requested */
1239     exlp->controlled = FALSE;                           /*   then mark the line as uncontrolled */
1240 
1241     if (lp->conn == 0)                                  /* if the line is currently disconnected */
1242         exlp->signals = No_Signals;                     /*   then default to no control signals */
1243     else                                                /* otherwise */
1244         exlp->signals = DTR_Control | RTS_Control;      /*   default to the connected control signals */
1245     }
1246 
1247 else {                                                  /* otherwise signal control is requested */
1248     exlp->controlled = TRUE;                            /*   so mark the line as controlled */
1249     exlp->signals = control;                            /*     and record the requested signal states */
1250 
1251     if (exlp->serport != INVALID_HANDLE)                /* if the line is connected to a serial port */
1252         status = sim_control_serial (exlp->serport,     /*   then let the hardware handle it */
1253                                      (SERCIRCUIT) control);
1254 
1255     else if (lp->conn != 0                              /* otherwise if the Telnet line is currently connected */
1256       && (control & DTR_Control) == 0)                  /*   and DTR is being dropped */
1257         tmxr_disconnect_line (lp);                      /*     then disconnect the line */
1258     }
1259 
1260 return status;                                          /* return the operation status */
1261 }
1262 
1263 
1264 /* Get a terminal line's status.
1265 
1266    This extension routine returns the status of a multiplexer line, specified by
1267    the "lp" parameter.  If the line is connected to a serial port, the hardware
1268    port status is returned.  If the line is connected to a Telnet port,
1269    simulated modem status (Data Set Ready, Clear To Send, and Data Carrier
1270    Detect) is returned.  If the line is not connected, no signals are returned.
1271 
1272    If a null pointer is passed for the "lp" parameter, the routine returns the
1273    Error_Status value.
1274 
1275 
1276    Implementation notes:
1277 
1278     1. The TMCKT and SERCIRCUIT types are renamings of the underlying
1279        RS232_SIGNAL type, so a type cast is valid.
1280 */
1281 
ex_tmxr_line_status(TMLN * lp)1282 TMCKT ex_tmxr_line_status (TMLN *lp)
1283 {
1284 EX_TMLN *exlp;
1285 
1286 if (lp == NULL)                                         /* if the line pointer is invalid */
1287     return Error_Status;                                /*   then report an internal error */
1288 else                                                    /* otherwise */
1289     exlp = (EX_TMLN *) lp->exptr;                       /*   point to the descriptor extension */
1290 
1291 if (exlp != NULL && exlp->serport != INVALID_HANDLE)    /* if the line is connected to a serial port */
1292     return (TMCKT) sim_status_serial (exlp->serport);   /*   then return the hardware port status */
1293 
1294 else if (lp->conn != 0)                                 /* otherwise if the line is connected to a Telnet port */
1295     return DSR_Status | CTS_Status | DCD_Status;        /*   then simulate a connected modem */
1296 
1297 else                                                    /* otherwise */
1298     return No_Signals;                                  /*   simulate a disconnected modem */
1299 }
1300 
1301 
1302 /* Poll for a new network or serial connection.
1303 
1304    This shim for "tmxr_poll_conn" polls for new Telnet or serial connections for
1305    the multiplexer descriptor indicated by "mp".  If a Telnet or serial
1306    connection is made, the routine returns the line number of the new
1307    connection.  Otherwise, the routine returns 0.  If a serial connection and a
1308    Telnet connection are both pending, the serial connection takes precedence.
1309 
1310 
1311    Implementation notes:
1312 
1313     1. When a serial port is attached to a line, the connection is made pending
1314        until we are called to poll for new connections.  This is because VM
1315        multiplexer service routines recognize new connections only as a result
1316        of calls to this routine.
1317 
1318     2. A pending serial (re)connection may be deferred.  This is needed when a
1319        line clear drops DTR, as DTR must remain low for a period of time in
1320        order to be recognized by the serial device.  If the "cnms" value
1321        specifies a time in the future, the connection is deferred until that
1322        time is reached.  This leaves DTR low for the necessary time.
1323 
1324     3. If the serial line is uncontrolled, the default control signals (i.e.,
1325        DTR and RTS) will be asserted.  Otherwise, the last signals set by the VM
1326        will be used.
1327 */
1328 
ex_tmxr_poll_conn(TMXR * mp)1329 int32 ex_tmxr_poll_conn (TMXR *mp)
1330 {
1331 TMLN    *lp;
1332 EX_TMLN *exlp;
1333 int32   line;
1334 uint32  current_time;
1335 
1336 if (mp == NULL)                                         /* if the mux descriptor is invalid */
1337     return 0;                                           /*   then return "no connection" status */
1338 
1339 current_time = sim_os_msec ();                          /* get the current time */
1340 
1341 for (line = 0; line < mp->lines; line++) {              /* check each line in sequence for connections */
1342     lp = mp->ldsc + line;                               /* get a pointer to the line descriptor */
1343     exlp = serial_line (lp);                            /*   and to the serial line extension */
1344 
1345     if (exlp != NULL && lp->conn == 0                   /* if the line is a serial line but not yet connected */
1346       && current_time >= lp->cnms) {                    /*   and the connection time has been reached */
1347         tmxr_init_line (lp);                            /*     then initialize the line state */
1348 
1349         if (exlp->controlled == FALSE)                  /* if the line as uncontrolled */
1350             exlp->signals = DTR_Control | RTS_Control;  /*   then default to the connected control signals */
1351 
1352         sim_control_serial (exlp->serport, exlp->signals);  /* connect the line as directed */
1353 
1354         lp->conn = 1;                                   /* mark the line as now connected */
1355         lp->cnms = current_time;                        /* record the time of connection */
1356 
1357         tmxr_report_connection (mp, lp, line);          /* report the connection to the connected device */
1358         return line;                                    /*   and return the line number */
1359         }
1360     }
1361 
1362 return tmxr_poll_conn (mp);                             /* there are no serial connections, so check for Telnet */
1363 }
1364 
1365 
1366 /* Determine if a line is free.
1367 
1368    If the line described by the line descriptor pointer "lp" is not connected to
1369    either a Telnet session or a serial port, this routine returns TRUE.
1370    Otherwise, it returns FALSE.  A TRUE return, therefore, indicates that the
1371    line is not in use.
1372 */
1373 
ex_tmxr_line_free(TMLN * lp)1374 t_bool ex_tmxr_line_free (TMLN *lp)
1375 {
1376 if (lp == NULL || lp->conn != 0)                        /* if the line is invalid or is connected */
1377     return FALSE;                                       /*   then mark the line as busy */
1378 else                                                    /* otherwise */
1379     return serial_line (lp) == NULL;                    /*   the line is free if it's not a serial line */
1380 }
1381 
1382 
1383 /* Determine if a multiplexer is free.
1384 
1385    If the multiplexer described by the mux descriptor pointer "mp" is not
1386    listening for new Telnet connections and has no lines that are connected to
1387    serial ports, then this routine returns TRUE.  Otherwise, it returns FALSE.
1388    A TRUE return, therefore, indicates that the multiplexer is not in use.
1389 
1390 
1391    Implementation notes:
1392 
1393     1.  If the listening network socket is detached, then no Telnet sessions can
1394         exist, so we only need to check for serial connections on the lines.
1395 */
1396 
ex_tmxr_mux_free(TMXR * mp)1397 t_bool ex_tmxr_mux_free (TMXR *mp)
1398 {
1399 int32 line;
1400 TMLN  *lp;
1401 
1402 if (mp == NULL || mp->master != 0)                      /* if the descriptor is invalid or the socket is open */
1403     return FALSE;                                       /*   then the multiplexer is not free */
1404 
1405 lp = mp->ldsc;                                          /* point at the first line descriptor */
1406 
1407 for (line = 0; line < mp->lines; line++, lp++)          /* check each line for a serial connection */
1408     if (ex_tmxr_line_free (lp) == FALSE)                /* if a serial port is open */
1409         return FALSE;                                   /*   then the multiplexer is not free */
1410 
1411 return TRUE;                                            /* the mux is free, as there are no connections */
1412 }
1413 
1414 
1415 
1416 /* Hooked terminal multiplexer replacement extension routines */
1417 
1418 
1419 /* Read from a multiplexer line.
1420 
1421    This hook routine reads up to "length" characters into the character buffer
1422    associated with line descriptor pointer "lp".  The actual number of
1423    characters read is returned.  If no characters are available, 0 is returned.
1424    If an error occurred while reading, -1 is returned.
1425 
1426    If the line is connected to a serial port, a serial read is issued.
1427    Otherwise, the read routine in the TMXR library is called to read from a
1428    network port.
1429 
1430    If a line break was detected on serial input, the associated receive break
1431    status flag in the line descriptor will be set.  Line break indication for
1432    Telnet connections is embedded in the Telnet protocol and must be determined
1433    externally.
1434 
1435 
1436    Implementation notes:
1437 
1438     1. It is up to the caller to ensure that the line is connected and a read of
1439        the specified length fits in the buffer with the current buffer index.
1440 */
1441 
ex_tmxr_read(TMLN * lp,int32 length)1442 static int32 ex_tmxr_read (TMLN *lp, int32 length)
1443 {
1444 EX_TMLN *exlp;
1445 
1446 if (lp == NULL)                                         /* if the descriptor pointer is not set */
1447     return -1;                                          /*   then return failure */
1448 else                                                    /* otherwise */
1449     exlp = serial_line (lp);                            /*   get the serial line extension */
1450 
1451 if (exlp == NULL)                                       /* if the line is not a serial line */
1452     return tmxr_base_read (lp, length);                 /*   then call the standard library routine */
1453 
1454 else                                                    /* otherwise */
1455     return sim_read_serial (exlp->serport,              /*   call the serial read routine */
1456                             lp->rxb + lp->rxbpi,        /*     with the buffer pointer */
1457                             length,                     /*       and maximum read length */
1458                             lp->rbr + lp->rxbpi);       /*         and the break array pointer */
1459 }
1460 
1461 
1462 /* Write to a multiplexer line.
1463 
1464    This hook routine writes up to "length" characters from the character buffer
1465    associated with line descriptor pointer "lp".  The actual number of
1466    characters written is returned.  If an error occurred while writing, -1 is
1467    returned.
1468 
1469    If the line is connected to a serial port, a serial write is issued.
1470    Otherwise, the write routine in the TMXR library is called to write to a
1471    network port.
1472 
1473 
1474    Implementation notes:
1475 
1476     1. It is up to the caller to ensure that the line is connected and a write
1477        of the specified length is contained in the buffer with the current
1478        buffer index.
1479 */
1480 
ex_tmxr_write(TMLN * lp,int32 length)1481 static int32 ex_tmxr_write (TMLN *lp, int32 length)
1482 {
1483 EX_TMLN *exlp;
1484 
1485 if (lp == NULL)                                         /* if the descriptor pointer is not set */
1486     return -1;                                          /*   then return failure */
1487 else                                                    /* otherwise */
1488     exlp = serial_line (lp);                            /*   get the serial line extension */
1489 
1490 if (exlp == NULL)                                       /* if the line is not a serial line */
1491     return tmxr_base_write (lp, length);                /*   then call the standard library routine */
1492 
1493 else                                                    /* otherwise */
1494     return sim_write_serial (exlp->serport,             /*   call the serial write routine */
1495                              lp->txb + lp->txbpr,       /*     with the buffer pointer */
1496                              length);                   /*       and write length */
1497 }
1498 
1499 
1500 /* Show a multiplexer line connection.
1501 
1502    This hook routine is called from the "tmxr_fconns" to display the line
1503    connection status, typically in response to a SHOW <mux> CONNECTIONS command.
1504    Depending on the line connection type, the Telnet IP address or serial port
1505    name is displayed.
1506 */
1507 
ex_tmxr_show(TMLN * lp,FILE * stream)1508 static void ex_tmxr_show (TMLN *lp, FILE *stream)
1509 {
1510 EX_TMLN *exlp;
1511 
1512 if (lp == NULL)                                         /* if the descriptor pointer is not set */
1513     return;                                             /*   then a programming error has occurred */
1514 else                                                    /* otherwise */
1515     exlp = serial_line (lp);                            /*   get the serial line extension */
1516 
1517 if (exlp == NULL)                                       /* if the line is not a serial line */
1518     tmxr_base_show (lp, stream);                        /*   then call the standard library routine */
1519 else                                                    /* otherwise */
1520     fprintf (stream, "Serial port %s", exlp->sername);  /*   print the serial port name */
1521 
1522 return;
1523 }
1524 
1525 
1526 /* Close a multiplexer line connection.
1527 
1528    This hook routine disconnects the Telnet or serial session associated with
1529    line descriptor "lp".  If the line is connected to a Telnet port, the close
1530    routine in the TMXR library is called to close and deallocate the port.
1531    Otherwise, if the line is connected to an uncontrolled serial port, DTR and
1532    RTS are dropped to disconnect the attached serial device; the port remains
1533    connected to the line, which is scheduled for reconnection after a short
1534    delay for DTR recognition.  If the line is controlled, this routine takes no
1535    action; it is up to the VM to decide how to proceed.
1536 
1537 
1538    Implementation notes:
1539 
1540     1. The base close routine does not return a value, so we cannot report an
1541        error when a null line descriptor pointer was passed.
1542 */
1543 
ex_tmxr_close(TMLN * lp)1544 static void ex_tmxr_close (TMLN *lp)
1545 {
1546 EX_TMLN *exlp;
1547 
1548 if (lp == NULL)                                         /* if the descriptor pointer is not set */
1549     return;                                             /*   then a programming error has occurred */
1550 else                                                    /* otherwise */
1551     exlp = serial_line (lp);                            /*   get the serial line extension */
1552 
1553 if (exlp == NULL)                                       /* if the line is not a serial line */
1554     tmxr_base_close (lp);                               /*   then call the standard library routine */
1555 
1556 else if (exlp->controlled == FALSE) {                   /* otherwise if the line is uncontrolled */
1557     sim_control_serial (exlp->serport, No_Signals);     /*   then disconnect the line by dropping DTR */
1558     lp->cnms = sim_os_msec () + 500;                    /*     and schedule reconnection 500 msec from now */
1559     }
1560 
1561 return;
1562 }
1563 
1564 
1565 /* Determine if a line is extended.
1566 
1567    This hook routine returns TRUE if the line described by the line descriptor
1568    pointer "lp" is controlled by this extension module and FALSE if it is not.
1569    A line is extended only if it is connected to a serial port; the presence of
1570    a non-null TMLN extension structure pointer is not sufficient, as that
1571    pointer may be set if a line control call is made for a Telnet port.
1572 
1573    Returning FALSE indicates to the caller that the line should receive the
1574    standard operations.  Returning TRUE indicates that this extension module
1575    will operate the line.
1576 */
1577 
ex_tmxr_extended(TMLN * lp)1578 static t_bool ex_tmxr_extended (TMLN *lp)
1579 {
1580 return serial_line (lp) != NULL;                        /* return TRUE if it's a serial line */
1581 }
1582 
1583 
1584 
1585 /* Local terminal multiplexer extension routines */
1586 
1587 
1588 /* Attach a line to a serial port.
1589 
1590    Attach a line of the multiplexer associated with mux descriptor pointer "mp"
1591    and unit pointer "uptr" to the serial port name indicated by "cptr".  The
1592    unit is implicitly associated with the line number corresponding to the
1593    position of the unit in the zero-based array of units belonging to the
1594    associated device.  For example, if "uptr" points to unit 3 in a given
1595    device, and "cptr" points to the string "COM1", then line 3 will be attached
1596    to serial port "COM1".
1597 
1598    An optional configuration string may be present after the port name.  If
1599    present, it must be separated from the port name with a semicolon and has
1600    this form:
1601 
1602       <rate>-<charsize><parity><stopbits>
1603 
1604    where:
1605 
1606      rate     = communication rate in bits per second
1607      charsize = character size in bits (5-8, including optional parity)
1608      parity   = parity designator (N/E/O/M/S for no/even/odd/mark/space parity)
1609      stopbits = number of stop bits (1, 1.5, or 2)
1610 
1611    As an example:
1612 
1613      9600-8n1
1614 
1615    The supported rates, sizes, and parity options are host-specific.  If a
1616    configuration string is not supplied, then host system defaults for the
1617    specified port are used.
1618 
1619    If the serial port allocation is successful, then the port name is stored in
1620    the UNIT structure, the UNIT_ATT flag is set, and the routine returns
1621    SCPE_OK.  If it fails, the error code is returned.
1622 
1623    Implementation notes:
1624 
1625     1. If the device associated with the unit referenced by "uptr" does not have
1626        the DEV_NET flag set, then the optional configuration string is saved
1627        with the port name in the UNIT structure.  This allows a RESTORE to
1628        reconfigure the attached serial port during reattachment.  The combined
1629        string will be displayed when the unit is SHOWed.
1630 
1631        If the unit has the DEV_NET flag, the optional configuration string is
1632        removed before the attached port name is saved in the UNIT structure, as
1633        RESTORE will not reattach the port, and so reconfiguration is not needed.
1634 
1635     2. The "exptr" field of the line descriptor will be set on entry if a call
1636        to the "ex_tmxr_control_line" routine has preceded this call.  If the
1637        structure has not been allocated, it is allocated here and is set to the
1638        uncontrolled state; a subsequent call to "ex_tmxr_control_line" will
1639        establish VM control if desired.
1640 
1641     3. Attempting to attach line that does not appear in the connection order
1642        array will be rejected.  This ensures that an omitted line will receive
1643        neither a Telnet connection nor a serial connection.
1644 */
1645 
ex_tmxr_attach_line(TMXR * mp,UNIT * uptr,char * cptr)1646 static t_stat ex_tmxr_attach_line (TMXR *mp, UNIT *uptr, char *cptr)
1647 {
1648 TMLN      *lp;
1649 EX_TMLN   *exlp;
1650 DEVICE    *dptr;
1651 char      *pptr, *sptr, *tptr;
1652 SERHANDLE serport;
1653 t_stat    status;
1654 int32     cntr, line;
1655 char      portname [1024];
1656 t_bool    arg_error = FALSE;
1657 SERCONFIG config = { 0 };
1658 
1659 if (uptr == NULL)                                       /* if this is a console reference */
1660     lp = mp->ldsc;                                      /*   point at the (only) line */
1661 else                                                    /* otherwise */
1662     lp = tmxr_find_ldsc (uptr, mp->lines, mp);          /*   determine the line from the unit */
1663 
1664 if (lp == NULL)                                         /* if the unit does not correspond to a line */
1665     return SCPE_NXUN;                                   /*   then report that the unit does not exist */
1666 
1667 else if (lp->conn)                                      /* otherwise if the line is connected via Telnet */
1668     return SCPE_NOFNC;                                  /*   then the command is not allowed */
1669 
1670 else if (cptr == NULL)                                  /* otherwise if the port name is missing */
1671     return SCPE_2FARG;                                  /*   then report a missing argument */
1672 
1673 else {                                                  /* otherwise get the multiplexer line number */
1674     line = (int32) (lp - mp->ldsc);                     /*   implied by the line descriptor */
1675 
1676     if (mp->lnorder != NULL && mp->lnorder [0] >= 0) {  /* if the line order exists and is not defaulted */
1677         for (cntr = 0; cntr < mp->lines; cntr++)        /*   then see if the line to attach */
1678             if (line == mp->lnorder [cntr])             /*     is present in the */
1679                 break;                                  /*       connection order array */
1680 
1681         if (cntr == mp->lines)                          /* if the line was not found */
1682             return SCPE_NOATT;                          /*   then report that the line is not attachable */
1683         }
1684     }
1685 
1686 pptr = get_glyph_nc (cptr, portname, ';');              /* separate the port name from the optional configuration */
1687 
1688 if (*pptr != '\0') {                                        /* if a parameter string is present */
1689     config.baudrate = (uint32) strtotv (pptr, &sptr, 10);   /*   then parse the baud rate */
1690     arg_error = (pptr == sptr);                             /*     and check for a bad argument */
1691 
1692     if (*sptr != '\0')                                      /* if a separator is present */
1693         sptr++;                                             /*   then skip it */
1694 
1695     config.charsize = (uint32) strtotv (sptr, &tptr, 10);   /* parse the character size */
1696     arg_error = arg_error || (sptr == tptr);                /*   and check for a bad argument */
1697 
1698     if (*tptr != '\0')                                      /* if the parity character is present */
1699         config.parity = (char) toupper (*tptr++);           /*   then save it */
1700 
1701     config.stopbits = (uint32) strtotv (tptr, &sptr, 10);   /* parse the number of stop bits */
1702     arg_error = arg_error || (tptr == sptr);                /*   and check for a bad argument */
1703 
1704     if (arg_error)                                          /* if any parse failure occurred */
1705         return SCPE_ARG;                                    /*   then report an invalid argument error */
1706 
1707     else if (strcmp (sptr, ".5") == 0)                      /* otherwise if 1.5 stop bits are requested */
1708         config.stopbits = 0;                                /*   then recode the request */
1709     }
1710 
1711 serport = sim_open_serial (portname);                   /* open the named serial port */
1712 
1713 if (serport == INVALID_HANDLE)                          /* if the port name is invalid or in use */
1714     return SCPE_OPENERR;                                /*   then report the attach failure */
1715 
1716 else {                                                  /* otherwise we have a good serial port */
1717     if (*pptr) {                                        /* if the parameter string was specified */
1718         status = sim_config_serial (serport, config);   /*   then set the serial configuration */
1719 
1720         if (status != SCPE_OK) {                        /* if configuration failed */
1721             sim_close_serial (serport);                 /*   then close the port */
1722             return status;                              /*     and report the error */
1723             }
1724         }
1725 
1726     dptr = find_dev_from_unit (uptr);                   /* find the device that owns the unit */
1727 
1728     if (dptr != NULL && dptr->flags & DEV_NET)          /* if RESTORE will be inhibited */
1729         cptr = portname;                                /*   then save just the port name */
1730 
1731     if (mp->dptr == NULL)                               /* if the device has not been set in the descriptor */
1732         mp->dptr = dptr;                                /*   then set it now */
1733 
1734     tptr = (char *) malloc (strlen (cptr) + 1);         /* get a buffer for the port name and configuration */
1735 
1736     if (tptr == NULL) {                                 /* if the memory allocation failed */
1737         sim_close_serial (serport);                     /*   then close the port */
1738         return SCPE_MEM;                                /*     and report the failure */
1739         }
1740 
1741     else                                                /* otherwise */
1742         strcpy (tptr, cptr);                            /*   copy the port name into the buffer */
1743 
1744     exlp = (EX_TMLN *) lp->exptr;                       /* point to the descriptor extension */
1745 
1746     if (exlp == NULL) {                                 /* if the extension has not been allocated */
1747         lp->exptr = malloc (sizeof (EX_TMLN));          /*   then allocate it now */
1748 
1749         if (lp->exptr == NULL) {                        /* if the memory allocation failed */
1750             free (tptr);                                /*   then free the port name buffer */
1751             sim_close_serial (serport);                 /*     and close the port */
1752 
1753             return SCPE_MEM;                            /* report the failure */
1754             }
1755 
1756         else {                                          /* otherwise */
1757             exlp = (EX_TMLN *) lp->exptr;               /*   point to the new descriptor extension */
1758 
1759             exlp->controlled = FALSE;                   /* mark the line as uncontrolled */
1760             exlp->signals = No_Signals;                 /*   and set the unconnected control signals */
1761             }
1762         }
1763 
1764     exlp->serport = serport;                            /* save the serial port handle */
1765     exlp->sername = tptr;                               /*   and the port name */
1766 
1767     if (uptr != NULL) {                                 /* if this is not a console attach */
1768         uptr->filename = tptr;                          /*   then save the port name pointer in the UNIT */
1769         uptr->flags |= UNIT_ATT;                        /*     and mark the unit as attached */
1770         }
1771 
1772     tmxr_init_line (lp);                                /* initialize the line state */
1773 
1774     lp->cnms = 0;                                       /* schedule for an immediate connection */
1775     lp->conn = 0;                                       /*   and indicate that there is no connection yet */
1776     }
1777 
1778 return SCPE_OK;                                         /* the line has been connected */
1779 }
1780 
1781 
1782 /* Get the extension pointer for a serial line.
1783 
1784    This routine returns a pointer to the TMLN extension structure if it exists
1785    and is currently in use for a serial line.  Otherwise, it returns NULL.  A
1786    non-null return therefore indicates that the line is connected to a serial
1787    port and a serial operation should be performed instead of a Telnet
1788    operation.
1789 */
1790 
serial_line(TMLN * lp)1791 static EX_TMLN *serial_line (TMLN *lp)
1792 {
1793 EX_TMLN *exlp;
1794 
1795 if (lp == NULL)                                         /* if the line pointer is invalid */
1796     return NULL;                                        /*   then the line cannot be a serial line */
1797 else                                                    /* otherwise */
1798     exlp = (EX_TMLN *) lp->exptr;                       /*   point at the corresponding line extension */
1799 
1800 if (exlp != NULL && exlp->serport != INVALID_HANDLE)    /* if it's allocated to a serial port */
1801     return exlp;                                        /*   then return the extension pointer */
1802 else                                                    /* otherwise */
1803     return NULL;                                        /*   it's not a serial line */
1804 }
1805 
1806 
1807 
1808 /* *********************  String Breakpoint Extensions  ************************
1809 
1810    This module extends the following existing routines in "sim_console.c" and
1811    "scp.c":
1812 
1813      sim_putchar   -- write a character to the console window
1814      sim_putchar_s -- write a character to the console window and stall if busy
1815 
1816      sim_brk_test  -- test for a breakpoint at the current program location
1817 
1818    The console output routines are extended to match output characters to
1819    pending string breakpoints, and the breakpoint test routine is extended to
1820    check for triggered string breakpoints.
1821 
1822    If a string breakpoint for the console is set, each output character is
1823    matched to the breakpoint string.  This matching takes place only if the
1824    console input has not been redirected to the command buffer, i.e., if it is
1825    in "Console" mode and not "Command" mode.  If the output characters form a
1826    matching string, the breakpoint is triggered, which will cause the next call
1827    to "sim_brk_test" to return the string breakpoint type.  The VM typically
1828    makes such a call once per instruction.
1829 
1830    If a breakpoint has a delay specified, triggering is not enabled until the
1831    delay time has expired.  The state of a breakpoint -- not triggered
1832    (pending), trigger delayed, or triggered -- is indicated by the "trigger"
1833    field of the breakpoint structure.  If the value is negative, the breakpoint
1834    is not triggered.  Otherwise, the value indicates the simulator global time
1835    at which the breakpoint transitions from trigger delayed to triggered.
1836    Comparison to the global time therefore indicates the trigger state.
1837 */
1838 
1839 
1840 /* Global string breakpoint extension routines */
1841 
1842 
1843 /* Put a character to the console.
1844 
1845    This shim for "sim_putchar" outputs the character designated by "c" to the
1846    console window.  If the keyboard is in Console mode, and a string breakpoint
1847    is set, the character is matched to the current breakpoint.  The matching is
1848    not done in Command mode to prevent a breakpoint from triggering due to
1849    command echoing or output.
1850 
1851 
1852    Implementation notes:
1853 
1854     1. If the output character cannot be written due to a stall, it is ignored.
1855        Output is normally stalled when the keyboard is in Command mode, so any
1856        characters output via calls to this routine while in Command mode will be
1857        lost.
1858 */
1859 
ex_sim_putchar(int32 c)1860 t_stat ex_sim_putchar (int32 c)
1861 {
1862 if (keyboard_mode == Console) {                         /* if we are in console mode */
1863     if (sb_list != NULL)                                /*   then if string breakpoints exist */
1864         test_breakpoint (c);                            /*     then test for a match */
1865 
1866     return sim_putchar (c);                             /* output the character */
1867     }
1868 
1869 else if (sim_con_tmxr.master != 0)                      /* otherwise if the consoles are separate */
1870     return sim_putchar (c);                             /*   then output the character */
1871 
1872 else                                                    /* otherwise we're in unified command mode */
1873     return SCPE_OK;                                     /*   so discard the output */
1874 }
1875 
1876 
1877 /* Put a character to the console with stall detection.
1878 
1879    This shim for "sim_putchar_s" outputs the character designated by "c" to the
1880    console window.  If the keyboard is in Console mode, and a string breakpoint
1881    is set, the character is matched to the current breakpoint.  The matching is
1882    not done in Command mode to prevent a breakpoint from triggering due to
1883    command echoing or output.
1884 
1885 
1886    Implementation notes:
1887 
1888     1. If the output character cannot be written due to a stall, SCPE_STALL is
1889        returned.  The calling routine should detect this condition and
1890        reschedule the output.  Output is normally stalled when the keyboard is
1891        in Command mode, so any characters output via calls to this routine while
1892        in Command mode should be rescheduled for output when the keyboard
1893        returns to Console mode.
1894 */
1895 
ex_sim_putchar_s(int32 c)1896 t_stat ex_sim_putchar_s (int32 c)
1897 {
1898 if (keyboard_mode == Console) {                         /* if we are in console mode */
1899     if (sb_list != NULL)                                /*   then if string breakpoints exist */
1900         test_breakpoint (c);                            /*     then test for a match */
1901 
1902     return sim_putchar_s (c);                           /* output the character */
1903     }
1904 
1905 else if (sim_con_tmxr.master != 0)                      /* otherwise if the consoles are separate */
1906     return sim_putchar_s (c);                           /*   then output the character */
1907 
1908 else                                                    /* otherwise we're in unified command mode */
1909     return SCPE_STALL;                                  /*   so stall the output */
1910 }
1911 
1912 
1913 /* Test for a breakpoint at the current location.
1914 
1915    This shim for "sim_brk_test" checks for a triggered string breakpoint or a
1916    numeric breakpoint of type "type" at the address designated by "location".
1917    If a breakpoint is detected, the type of the breakpoint is returned.
1918 
1919    The "type" parameter is the union of all numeric breakpoint types that are
1920    valid for this address location.  To be triggered, a numeric breakpoint must
1921    match both the location and one of the specified types.
1922 
1923    String breakpoints are always tested, as only one type of string breakpoint
1924    is defined -- the default string breakpoint type.  Therefore, it is not
1925    necessary for the VM to add the string breakpoint type to the "type"
1926    parameter when calling this routine.
1927 
1928 
1929    Implementation notes:
1930 
1931     1. For numeric breakpoint types, a type's presence in "sim_brk_summ"
1932        indicates that one or more breakpoints of that type are pending and must
1933        be checked for triggering during this routine.  For string and timed
1934        breakpoints, presence of the BP_STRING or BP_TIMED type indicates that a
1935        breakpoint of that type has already triggered, and no further check is
1936        required.
1937 
1938     2. String and timed breakpoints are triggered by their respective service
1939        routines when the breakpoint delay expires.  There would be no need to
1940        handle triggering here if there were a global breakpoint SCP status
1941        value, as the service routine could simply return that hypothetical
1942        SCPE_BREAK code to stop instruction execution.  Unfortunately,
1943        "breakpoint triggered" is a VM-specific status code (e.g., STOP_BRKPNT),
1944        so we must use this routine to return a breakpoint indication to the VM,
1945        which then stops execution and returns its own VM-specific status.
1946 */
1947 
ex_sim_brk_test(t_addr location,uint32 type)1948 uint32 ex_sim_brk_test (t_addr location, uint32 type)
1949 {
1950 static char tempbuf [CBUFSIZE];
1951 uint32 result;
1952 BRKTAB *bp;
1953 
1954 if (sim_brk_summ & BP_EXTENDED) {                       /* if an extended breakpoint has triggered */
1955     result = sim_brk_summ & BP_EXTENDED;                /*   then set the match type(s) */
1956     sim_brk_summ &= ~BP_EXTENDED;                       /*     and clear the code(s) */
1957     }
1958 
1959 else {                                                  /* otherwise */
1960     result = sim_brk_test (location, type);             /*   test for a numeric breakpoint */
1961 
1962     if (result != 0) {                                  /* if the breakpoint fired */
1963         bp = sim_brk_fnd (location);                    /*   then find it */
1964 
1965         if (bp != NULL && bp->typ & SWMASK ('T')) {         /* if the breakpoint is temporary */
1966             if (bp->act != NULL)                            /*   then if actions are defined for it */
1967                 sim_brk_act = strcpy (tempbuf, bp->act);    /*     then copy the action string */
1968 
1969             sim_brk_clr (location, bp->typ);                /* clear the breakpoint */
1970             }
1971         }
1972     }
1973 
1974 return result;                                          /* return the test result */
1975 }
1976 
1977 
1978 /* Local string breakpoint SCP support routines */
1979 
1980 
1981 /* Return the name of an extended breakpoint unit.
1982 
1983    The unit that implements string breakpoint delays does not have a
1984    corresponding device, so the SHOW QUEUE command will fail to find the unit in
1985    the list of devices.  To provide the name, we set the "sim_vm_unit_name" hook
1986    in the one-time initialization to point at this routine, which supplies the
1987    name when passed the break delay unit as the parameter.  If the unit is not
1988    the break delay unit, then we let the VM-defined name routine handle it, if
1989    one was defined.  Otherwise, we return NULL to indicate that we did not
1990    recognize it.
1991 
1992 
1993    Implementation notes:
1994 
1995     1. The timed execution unit ("timed_bp_unit") need not appear here because
1996        the unit is scheduled at the start of an execution run and is always
1997        canceled at the end of the run, so it will never appear in a SHOW QUEUE
1998        listing.
1999 */
2000 
breakpoint_name(const UNIT * uptr)2001 static char *breakpoint_name (const UNIT *uptr)
2002 {
2003 if (uptr == string_bp_unit)                             /* if the request is for the string break delay unit */
2004     return "Break delay timer";                         /*   then return the descriptive name */
2005 else if (vm_unit_name_handler)                          /* otherwise if the VM defined a name handler */
2006     return vm_unit_name_handler (uptr);                 /*   then call it to process the request */
2007 else                                                    /* otherwise */
2008     return NULL;                                        /*   report that we do not recognize the unit */
2009 }
2010 
2011 
2012 /* Service a string breakpoint.
2013 
2014    A matched breakpoint remains in the trigger-delayed state until any specified
2015    delay elapses.  When a pending breakpoint is satisfied, service is scheduled
2016    on the breakpoint unit to wait until the delay expires.  This service routine
2017    then triggers the breakpoint and handles removal of the allocated structure
2018    if it is temporary or resetting the breakpoint if it is permanent.
2019 
2020    On entry, one of the breakpoints in the linked list will be ready to trigger.
2021    We scan the list looking for the first breakpoint whose trigger time has
2022    passed.  The scan is done only if no prior trigger is waiting to be
2023    acknowledged.  This condition could occur if the VM has not called
2024    "sim_brk_test" since the earlier breakpoint triggered.  In that case, we
2025    postpone our check, so that we do not have two triggered breakpoints at the
2026    same time (as only one of the two sets of breakpoint actions can be
2027    performed).
2028 
2029    When a breakpoint triggers, the BP_STRING type is set in the "sim_brk_summ"
2030    variable to tell the "sim_brk_test" routine to pass a breakpoint indication
2031    back to the VM for action.  Then we clean up the breakpoint structure,
2032    copying the action string into a local static buffer if the breakpoint was
2033    temporary; it will be executed from there.
2034 
2035    When we are called, there may be additional breakpoints in the trigger-
2036    delayed state, i.e., waiting for their respective delays to expire before
2037    triggering.  While we are processing the breakpoint list, we also look for
2038    the earliest breakpoint in that state and schedule our service to reactivate
2039    when that delay expires.  If no additional breakpoints are delayed, the
2040    service stops.
2041 
2042 
2043    Implementation notes:
2044 
2045     1. If a second breakpoint is waiting, the delay until that breakpoint
2046        triggers could be zero -- if, for example, a breakpoint with a delay of
2047        2000 has already waited for a period of 1000 when a second breakpoint
2048        with a delay of 1000 is satisfied.  After servicing the first breakpoint,
2049        we cannot reactivate the service with a zero time, because we would be
2050        reentered before the VM had a chance to call "sim_brk_test" and so the
2051        first would still be pending, which would hold off recognizing the
2052        second.  This would result in another zero service time, and the VM would
2053        never get an opportunity to recognize any breakpoints.  So we arbitrarily
2054        reschedule with a delay of one, which allows the VM to recognize the
2055        first triggered breakpoint.
2056 
2057     2. It is safe to use a single static breakpoint action buffer for temporary
2058        breakpoints, because a triggered breakpoint causes the VM to exit the
2059        instruction loop, and reentry (a necessary condition for a second
2060        breakpoint to trigger) clears any pending actions.
2061 */
2062 
string_bp_service(UNIT * uptr)2063 static t_stat string_bp_service (UNIT *uptr)
2064 {
2065 static char tempbuf [CBUFSIZE];
2066 SBPTR  bp, prev;
2067 int32  delay;
2068 const  double entry_time = sim_gtime ();                /* the global simulation time at entry */
2069 double next_time = DBL_MAX;                             /* the time at which the next breakpoint triggers */
2070 
2071 bp   = sb_list;                                         /* start searching at the head of the breakpoint list */
2072 prev = NULL;                                            /*   for a triggered breakpoint */
2073 
2074 while (bp != NULL) {                                    /* loop until the list is exhausted */
2075     if (bp->trigger >= 0.0)                             /*   looking for a triggered breakpoint */
2076         if ((sim_brk_summ & BP_STRING) == 0             /* if no outstanding service request exists */
2077           && entry_time >= bp->trigger) {               /*   and this breakpoint is now triggered */
2078             sim_brk_summ |= BP_STRING;                  /*     then indicate that a breakpoint stop is required */
2079 
2080             if (bp->type & SWMASK ('T')) {                  /* if a temporary breakpoint just triggered */
2081                 sim_brk_act = strcpy (tempbuf, bp->action); /*   then copy the action string */
2082 
2083                 if (prev != NULL) {                     /* if there is a previous node */
2084                     prev->next = bp->next;              /*   then link it to the next one, */
2085                     free (bp);                          /*     free the current node */
2086                     bp = prev->next;                    /*       and make the next node current */
2087                     }
2088 
2089                 else {                                  /* otherwise we're clearing the first node */
2090                     sb_list = bp->next;                 /*   so point the list header at the next one, */
2091                     free (bp);                          /*     free the current node */
2092                     bp = sb_list;                       /*       and make the next node current */
2093                     }
2094 
2095                 continue;                               /* continue the search with the next node */
2096                 }
2097 
2098             else {                                      /* otherwise it's a persistent breakpoint */
2099                 sim_brk_act = bp->action;               /*   so copy the action string pointer */
2100                 bp->mptr = bp->match;                   /*     and reset the match pointer */
2101                 bp->trigger = -1.0;                     /*       and the trigger */
2102                 }
2103             }
2104 
2105         else if (bp->trigger < next_time)               /* otherwise obtain the earliest trigger time */
2106             next_time = bp->trigger;                    /*   of all of the trigger-delayed breakpoints */
2107 
2108     prev = bp;                                          /* the current node becomes the prior one */
2109     bp = bp->next;                                      /*   and the next node becomes current */
2110     }
2111 
2112 if (next_time < DBL_MAX) {                              /* if another triggered breakpoint was seen */
2113     delay = (int32) (next_time - entry_time);           /*   then get the relative delay */
2114 
2115     if (delay < 1 && sim_brk_summ & BP_STRING)          /* if a breakpoint was triggered in this pass */
2116         delay = 1;                                      /*   ensure that the VM has time to process it */
2117 
2118     sim_activate (string_bp_unit, delay);               /* reschedule the service to handle the next breakpoint */
2119     }
2120 
2121 return SCPE_OK;                                         /* return with success */
2122 }
2123 
2124 
2125 /* Local string breakpoint extension routines */
2126 
2127 
2128 /* Cancel all string breakpoints.
2129 
2130    This routine cancels all of the string breakpoints.  It is called in response
2131    to the NOBREAK "" and NOBREAK ALL commands.  It walks the linked list headed
2132    by "sb_list" and releases each string breakpoint structure encountered.
2133    Before returning, it clears the list head pointer and cancels the breakpoint
2134    delay time, in case a breakpoint had entered the trigger-delayed state.
2135 */
2136 
free_breakpoints(void)2137 static void free_breakpoints (void)
2138 {
2139 SBPTR bp, node;
2140 
2141 bp = sb_list;                                           /* start at the list head */
2142 
2143 while (bp != NULL) {                                    /* if the current node exists */
2144     node = bp;                                          /*   then save a node pointer */
2145     bp = bp->next;                                      /* point at the next node */
2146     free (node);                                        /*   and free the current one */
2147     }
2148 
2149 sb_list = NULL;                                         /* clear the breakpoint list header */
2150 sim_cancel (string_bp_unit);                            /*   and cancel the breakpoint timer */
2151 
2152 return;
2153 }
2154 
2155 
2156 /* Find a string breakpoint.
2157 
2158    This routine looks through the list of string breakpoints for one matching
2159    the supplied string.  If a matching breakpoint is found, a pointer to the
2160    structure is returned.  Otherwise, NULL is returned.  In either case, a
2161    pointer to the prior (or last, if no match) structure is returned via the
2162    second function parameter.
2163 
2164    A case-sensitive match is performed.
2165 */
2166 
find_breakpoint(char * match,SBPTR * prev)2167 static SBPTR find_breakpoint (char *match, SBPTR *prev)
2168 {
2169 SBPTR bp = sb_list;                                     /* start at the list head */
2170 
2171 *prev = NULL;                                           /* initialize the previous node pointer */
2172 
2173 while (bp != NULL)                                      /* if the current node exists */
2174     if (strcmp (match, bp->match) == 0)                 /*   and it matches the search string */
2175         break;                                          /*     then the search is over */
2176 
2177     else {                                              /* otherwise */
2178         *prev = bp;                                     /*   save the node pointer */
2179         bp = bp->next;                                  /*     and point at the next one */
2180         }
2181 
2182 return bp;                                              /* return the matching node pointer or NULL */
2183 }
2184 
2185 
2186 /* Test for a string breakpoint.
2187 
2188    This routine is called when string breakpoints exist and a character is to be
2189    output.  It checks for a match between that character (the test character)
2190    and the next character in each match string in the linked list of breakpoint
2191    structures.  If a match string is completed, the breakpoint enters the
2192    trigger-delayed state, and the break delay timer is scheduled if it is not
2193    running.  Matching a particular breakpoint does not inhibit matching against
2194    all of the other breakpoints.
2195 
2196    Within each STRING_BREAKPOINT structure in the linked list of breakpoints,
2197    the "match" field contains the string to match, and the "mptr" field points
2198    at the next character to check (initially, the first character of the match
2199    string).
2200 
2201    If the test character equals the match character, then the match pointer is
2202    advanced.  If the pointer now points at the end of the match string, then the
2203    breakpoint enters the trigger delayed state.  The "trigger" field is set to
2204    the trigger activation time.  This is normally the current time but will be a
2205    time in the future if a breakpoint delay was specified.  If no other
2206    breakpoint is in this state, the break delay timer will not be active, and so
2207    the routine will activate it with the specified delay.  If the timer is
2208    already active, the remaining delay time is compared to the delay time for
2209    the newly matched breakpoint.  If the remaining time is greater, the timer is
2210    reset to the shorter delay, as the new breakpoint will trigger before the
2211    existing one.  Otherwise, the timer continues with the remaining time.
2212 
2213    If the test character does not equal the current match character, then a
2214    check is made to see if it matches any prior characters in the match string.
2215    If it does not, then the match pointer is reset to the start of the string.
2216    However, if it does, then it's possible that the characters output so
2217    far match a prior substring of the match string.
2218 
2219    Consider a match string of "ABABC" that has been matched through the second
2220    "B", so that the match pointer points at the "C".  We are called with an "A"
2221    as the test character.  It does not match "C", but in looking backward, we
2222    see that it does match the second "A".  So we search backward from there to
2223    see if the output characters match the earlier part of the match string.  In
2224    this case, the last three characters output match the leading substring "ABA"
2225    of the match string, so the match pointer is reset to point at the fourth
2226    character, rather than the second.  Then, if "BC" is subsequently output, the
2227    match will succeed.
2228 
2229    Conceptually, this search begins with:
2230 
2231                    match pointer
2232                          |
2233              A  B  A  B  C      match characters
2234              A  B  A  B  A      output characters
2235                          |
2236                   test character
2237 
2238    Because the characters do not match, we "slide" the output string to the left
2239    to see if we can find a trailing output substring that matches a leading
2240    match substring.  We start with the right-most match character that equals
2241    the test character
2242 
2243          first matching character
2244                    |
2245              A  B  A  B  C      match characters
2246        A  B  A  B  A            output characters
2247              |  |  |
2248               match
2249 
2250    Here, the last three output string characters match the first three match
2251    string characters, so we reset the match pointer to the fourth character:
2252 
2253                 match pointer
2254                       |
2255              A  B  A  B  C      match characters
2256 
2257    Now if an additional "B" and "C" are output (i.e., the entire output is
2258    "ABABABC"), then the breakpoint will trigger on receipt of the "C".
2259 
2260              A  B  A  B  C      match characters
2261        A  B  A  B  A  B  C      output characters
2262              |  |  |  |  |
2263                  match
2264 
2265    Now consider a match string of "ABAB" that has matched through the second
2266    "A", and we are called with a test character of "A":
2267 
2268                     match pointer
2269                           |
2270                  A  B  A  B     match characters
2271                  A  B  A  A     output characters
2272                           |
2273                    test character
2274 
2275    The first substring test does not match:
2276 
2277              first matching character
2278                        |
2279                  A  B  A  B     match characters
2280               A  B  A  A        output characters
2281                  |  |
2282                no match
2283 
2284    So we search backward for another test character match and try again, and
2285    this one succeeds:
2286 
2287      second matching character
2288                  |
2289                  A  B  A  B     match characters
2290         A  B  A  A              output characters
2291                  |
2292                match
2293 
2294    The match pointer is reset to point at the following "B", and the breakpoint
2295    will trigger if the subsequent output produces "ABAABAB".
2296 
2297    Effectively, the search starts with the longest possible substring and then
2298    attempts shorter and shorter substrings until either a match occurs or no
2299    substring matches.  In the first case, the match pointer is reset
2300    appropriately, and partial matching continues.  In the second, the match
2301    pointer is reset to the beginning of the match string, and a new match is
2302    sought.
2303 
2304    Searching for the longest substring that matches the output stream would
2305    appear to require an output history buffer.  However, the fact that all of
2306    the prior output characters until the current one have matched means that the
2307    match string itself IS the history of the relevant part of the output stream.
2308    We need only search for substrings that equal the substring of the match
2309    string that ends with the last-matched character.
2310 
2311    This matching process is repeated for each node in the list of breakpoints.
2312 */
2313 
test_breakpoint(int32 test_char)2314 static void test_breakpoint (int32 test_char)
2315 {
2316 char  *history, *hptr, *sptr;
2317 int32 trigger_time;
2318 SBPTR bp = sb_list;                                     /* start at the list head */
2319 
2320 while (bp != NULL) {                                    /* if the current node exists */
2321     if (*bp->mptr != '\0')                              /*   then if the search string is not exhausted */
2322         if (*bp->mptr == test_char) {                   /*     then if the search character matches */
2323             bp->mptr++;                                 /*       then point at the next search character */
2324 
2325             if (*bp->mptr == '\0') {                    /* if the search string is completely matched */
2326                 bp->trigger =                           /*   then set the trigger time */
2327                   sim_gtime () + bp->delay;             /*     to the current time plus any delay */
2328 
2329                 trigger_time = sim_is_active (string_bp_unit);  /* get any remaining delay time */
2330 
2331                 if (trigger_time == 0 || trigger_time > bp->delay)  /* if it's not running or the delay is too long */
2332                     sim_activate_abs (string_bp_unit, bp->delay);   /*   then reschedule the timer to the shorter time */
2333                 }
2334             }
2335 
2336         else if (bp->mptr != bp->match) {               /* otherwise if we have a partial match */
2337             history = --bp->mptr;                       /*   then save a pointer to the output history */
2338 
2339             do {                                        /* search for a substring match */
2340                 while (bp->mptr >= bp->match            /* while still within the match string */
2341                   && *bp->mptr != test_char)            /*   and the search character doesn't match */
2342                     bp->mptr--;                         /*     back up until a matching character is found */
2343 
2344                 if (bp->mptr < bp->match) {             /* if no matching character was found */
2345                     bp->mptr = bp->match;               /*   then reset the search pointer to the start */
2346                     sptr = NULL;                        /*     and exit the substring search */
2347                     }
2348 
2349                 else {                                  /* otherwise there is a potential substring match */
2350                     hptr = history;                     /*   so set up the output history */
2351                     sptr = bp->mptr - 1;                /*     and matching substring pointers */
2352 
2353                     while (sptr >= bp->match            /* test for a substring match */
2354                       && *sptr == *hptr) {              /*   in reverse */
2355                         sptr--;                         /*     until a match fails */
2356                         hptr--;                         /*       or the entire substring matches */
2357                         }
2358 
2359                     if (sptr < bp->match) {             /* if a matching substring was found */
2360                         bp->mptr++;                     /*   then point at the next character to match */
2361                         sptr = NULL;                    /*     and exit the substring search */
2362                         }
2363 
2364                     else                                /* otherwise the substring did not match */
2365                         bp->mptr = sptr;                /*   so try the next shorter substring */
2366                     }
2367                 }
2368             while (sptr);                               /* continue testing until a match or exhaustion */
2369             }
2370 
2371     bp = bp->next;                                      /* point at the next breakpoint node */
2372     }                                                   /*   and continue until all nodes are checked */
2373 
2374 return;
2375 }
2376 
2377 
2378 
2379 /* *************  Concurrent Console Mode and Reply Extensions ****************
2380 
2381    This module extends the following existing routines in "hp----_cpu.c" and
2382    "sim_console.c":
2383 
2384      sim_instr    -- execute simulated machine instructions
2385 
2386      sim_poll_kbd -- poll the console keyboard for input
2387 
2388    The instruction execution routine is extended to process commands entered in
2389    concurrent console mode.  The keyboard poll routine is extended to allow
2390    entry of commands concurrently with instruction execution and also to supply
2391    previously established character string replies automatically.
2392 
2393    In the normal console mode, entry of SCP commands first requires instruction
2394    execution to be stopped by entering the WRU character (default is CTRL+E).
2395    This prints "Simulation stopped" on the console, terminates instruction
2396    execution, and returns to the "sim>" prompt.  At this point, a command such
2397    as a tape image attachment may be entered, and then execution may be resumed.
2398 
2399    The problem with this is that while instruction execution is stopped, the
2400    simulated time-of-day clock is also stopped.  It that clock had been set
2401    accurately at target OS startup, it will lose time each time an SCP command
2402    must be entered.
2403 
2404    To alleviate this, a "concurrent" console mode may be established.  In this
2405    mode, entering CTRL+E does not terminate instruction execution but rather
2406    diverts console keystrokes into a separate command buffer instead of
2407    returning them to the VM.  During command entry, instructions continue to
2408    execute, so the simulated time-of-day clock remains accurate.  When the
2409    command is terminated with ENTER, the VM's "sim_instr" routine returns to the
2410    extension shim.  The shim executes the command and then automatically resumes
2411    instruction execution.  The simulated clock is stopped for the command
2412    execution time, but that time is usually shorter than one clock tick and so
2413    is absorbed by the clock calibration routine.
2414 
2415    The extended keyboard poll routine switches between "Console" mode, where the
2416    characters are delivered to the VM, and "Command" mode, where the characters
2417    are delivered to a command buffer.  Entry into command mode is made by
2418    sensing CTRL+E, and exit from command mode is made by sensing ENTER.  Limited
2419    editing is provided in Command mode because the poll routine obtains
2420    characters, not keystrokes, and so can't sense the non-character keys such as
2421    the arrow keys.
2422 
2423    Concurrent console mode is an option, set by the SET CONSOLE [NO]CONCURRENT
2424    command.
2425 */
2426 
2427 
2428 /* Global concurrent console and reply extension routines */
2429 
2430 
2431 /* Execute CPU instructions.
2432 
2433    This shim for the virtual machine's "sim_instr" routine detects commands
2434    entered in concurrent console mode, executes them, and then calls "sim_instr"
2435    again.  This loop continues until a simulation stop condition occurs.
2436 
2437 
2438    Implementation notes:
2439 
2440     1. On Unix systems, WRU is registered as the SIGINT character, so CTRL+E
2441        does not arrive via the keyboard poll.  Instead, the SIGINT handler
2442        installed by the "run_cmd" routine is called, which sets the "stop_cpu"
2443        flag.  This value is tested in "sim_process_event", which returns
2444        SCPE_STOP in response.  This causes the VM's "sim_instr" to stop
2445        execution and print "Simulation stopped" before returning.  We cannot
2446        test "stop_cpu" in our "ex_sim_poll_kbd" routine to trigger the
2447        concurrent mode prompt because "sim_process_event" is called after every
2448        machine instruction, so it will be seen there before we can act on it in
2449        our keyboard poll.  So instead we must replace the installed SIGINT
2450        handler with one of our own that sets a local "stop_requested" flag that
2451        is tested in our keyboard poll.
2452 
2453     2. When the system console is connected to a serial port, we fake a Telnet
2454        connection by setting "sim_con_tmxr.master" non-zero during instruction
2455        execution.  This tricks the "sim_poll_kbd" and "sim_putchar[_s]" routines
2456        in "sim_console.c" into calling the terminal multiplexer routines, which
2457        will read from or write to the serial console.
2458 
2459     3. Leading spaces must be skipped before calling "get_glyph" to parse the
2460        command keyword, as that routine uses spaces to mark the end of the
2461        keyword.  If a leading space is present, "get_glyph" will return a null
2462        string as the keyword, and "find_cmd" (called via "get_command") will
2463        return a successful match with the first entry in the command table.
2464 
2465     4. The routine must restore the console to "command mode" to reenable text
2466        mode on the console log, which is necessary to obtain the correct line
2467        ends on logged commands.  It must also save and restore the command line
2468        switches in effect at entry, so that any switches present in the entered
2469        command line aren't incorrectly used by the VM's "sim_instr" routine.
2470 
2471     5. With one exception, entered commands must be "unrestricted", i.e., must
2472        not interfere with the partial unwinding of the VM run state.  The
2473        exception is the DO command.  To allow command files to contain
2474        prompt/response actions, we handle DO specially by setting the global
2475        "concurrent_do_ptr" to point at the DO command line and then stop
2476        execution to unwind the run state.  The DO command is then handled in the
2477        "ex_run_cmd" routine by executing the command file and then automatically
2478        reentering this routine to resume instruction execution.
2479 
2480     7. We can't simply return SCPE_EXIT in response to an EXIT command because
2481        the standard "run_cmd" routine ignores the return status and always
2482        returns SCPE_OK to the command loop.  To get the loop to exit, we could
2483        either set a global here and return SCPE_EXIT from "ex_run_cmd" in
2484        response, or set up EXIT as a "breakpoint" action to be executed upon
2485        return.  The latter option is implemented.
2486 */
2487 
sim_instr(void)2488 t_stat sim_instr (void)
2489 {
2490 SIG_HANDLER prior_handler;
2491 char        *cptr, gbuf [CBUFSIZE], tbuf [CBUFSIZE];
2492 t_stat      status, reason;
2493 int32       saved_switches;
2494 CTAB        *cmdp;
2495 
2496 prior_handler = signal (SIGINT, wru_handler);           /* install our WRU handler in place of the current one */
2497 
2498 if (prior_handler == SIG_ERR)                           /* if installation failed */
2499     status = SCPE_SIGERR;                               /*   then report an error */
2500 
2501 else do {                                               /* otherwise */
2502     stop_requested = FALSE;                             /*   clear any pending WRU stop */
2503 
2504     if (serial_line (sim_con_tmxr.ldsc) != NULL)        /* if the system console is on a serial port */
2505         sim_con_tmxr.master = 1;                        /*   then fake a Telnet connection */
2506 
2507     status = vm_sim_instr ();                           /* call the instruction executor */
2508 
2509     if (serial_line (sim_con_tmxr.ldsc) != NULL)        /* if the system console is on a serial port */
2510         sim_con_tmxr.master = 0;                        /*   then clear the fake Telnet connection */
2511 
2512     if (status == SCPE_EXEC) {                          /* if a concurrent command was entered */
2513         cptr = cmd_buf;                                 /*   then point at the command buffer */
2514 
2515         ex_substitute_args (cptr, tbuf, sizeof cmd_buf, NULL);  /* substitute variables in the command line */
2516 
2517         while (isspace (*cptr))                         /* remove any leading spaces */
2518             cptr++;                                     /*   that would confuse the "get_glyph" routine */
2519 
2520         if (*cptr == '\0')                              /* if the command was entirely blank */
2521             continue;                                   /*   then ignore it */
2522 
2523         sim_ttcmd ();                                   /* return the console to command state */
2524 
2525         if (sim_log)                                    /* if the console is being logged */
2526             fprintf (sim_log, "\nscp> %s\n", cptr);     /*   then echo the command to the log file */
2527 
2528         if (*cptr == ';') {                             /* if a comment was entered */
2529             sim_ttrun ();                               /*   then return the console to run mode */
2530             continue;                                   /*     and ignore the command */
2531             }
2532 
2533         saved_switches = sim_switches;                  /* save the switches currently in effect */
2534         sim_switches = 0;                               /*   and reset them to avoid interference */
2535 
2536         cptr = get_glyph (cptr, gbuf, 0);               /* parse the command keyword */
2537 
2538         reason = get_command (gbuf, &cmdp);             /* get the command descriptor */
2539 
2540         if (cmdp != NULL                                /* if the command is valid */
2541           && cmdp->action == ex_do_handler) {           /*   and is a DO command */
2542             concurrent_do_ptr = cptr;                   /*     then point at the parameters */
2543             status = SCPE_OK;                           /*       and stop execution */
2544             }
2545 
2546         else {                                              /* otherwise */
2547             if (reason == SCPE_OK)                          /*   if the command is legal */
2548                 reason = cmdp->action (cmdp->arg, cptr);    /*     then execute it */
2549 
2550             if (reason != SCPE_OK)                          /* if an error is indicated */
2551                 if (reason == SCPE_EXIT) {                  /*   the if the command was EXIT (or QUIT or BYE) */
2552                     sim_brk_act = "exit";                   /*     then set execute an EXIT command on return */
2553                     status = SCPE_STOP;                     /*       and stop execution */
2554                     }
2555 
2556                 else if (cmdp != NULL                       /* otherwise if the command is known */
2557                   && cmdp->action == ex_restricted_cmd      /*   and is */
2558                   && cmdp->arg == EX_ABORT) {               /*     an ABORT command */
2559                     stop_requested = TRUE;                  /*       then set the flag */
2560                     status = SCPE_STOP;                     /*         and handle it as a simulation stop */
2561                     }
2562 
2563                 else {                                      /* otherwise report the error */
2564                     printf ("%s\n", sim_error_text (reason));
2565 
2566                     if (sim_log)                            /* if the console is being logged */
2567                         fprintf (sim_log, "%s\n",           /*   then report it to the log as well */
2568                                  sim_error_text (reason));
2569                     }
2570 
2571             if (sim_vm_post != NULL)                        /* if the VM wants command notification */
2572                 (*sim_vm_post) (TRUE);                      /*   then let it know we executed a command */
2573             }
2574 
2575         sim_ttrun ();                                   /* return the console to run mode */
2576         sim_switches = saved_switches;                  /*   and restore the original switches */
2577         }
2578     }
2579 
2580 while (status == SCPE_EXEC);                            /* continue execution if stopped for a command */
2581 
2582 sim_cancel (timed_bp_unit);                             /* stop any timed breakpoint in progress */
2583 
2584 if (status != SCPE_SIGERR)                              /* if the signal handler was set up properly */
2585     signal (SIGINT, prior_handler);                     /*   then restore the prior handler */
2586 
2587 return status;                                          /* return the result of instruction execution */
2588 }
2589 
2590 
2591 /* Poll the console keyboard.
2592 
2593    This shim for "sim_poll_kbd" polls the console keyboard for keystrokes and
2594    delivers the resulting characters to the caller.  The routine extends the
2595    standard one to supply automatic responses for the REPLY command and to
2596    enable a "concurrent" command mode that allows SCP commands to be entered
2597    without stopping simulation execution.
2598 
2599    During simulator execution, the system console is connected to the simulation
2600    console by default.  While it is so connected, keystrokes entered at the
2601    simulation console are delivered to the system console device.
2602 
2603    With a SET CONSOLE TELNET command, the system console may be redirected to a
2604    Telnet port.  After this separation, two console windows exist: the
2605    simulation console remains attached to the originating command window, while
2606    the system console is attached to the Telnet client window.  When the system
2607    console window has the input focus, keystrokes are delivered to the system
2608    console device.  When the simulation console window has the focus, keystrokes
2609    are delivered to SCP.
2610 
2611    In non-concurrent mode, SCP responds only to CTRL+E and ignores all other
2612    keystrokes.  If concurrent mode is enabled with a SET CONSOLE CONCURRENT
2613    command, then the simulation console becomes interactive after a CTRL+E while
2614    simulator execution continues.  During this time, system console operation
2615    depends on whether the simulation and system consoles are joined or
2616    separated.
2617 
2618    In concurrent mode, the simulation console is in one of two states: Console
2619    or Command.  It starts in Console state.  If the simulation and system
2620    consoles are joined, then keystrokes are delivered to the system console.  If
2621    they are separated, then keystrokes are ignored.
2622 
2623    Pressing CTRL+E prints an "scp> " prompt on the simulation console, and the
2624    console switches to the Command state.  While execution continues, keystrokes
2625    forming an SCP command are placed in a command buffer; the command is
2626    executed when ENTER is pressed.  Limited editing is provided in the Command
2627    state.  Pressing BACKSPACE deletes the last character entered, and pressing
2628    ESCAPE clears all characters.  Pressing ENTER with no characters present
2629    terminates the Command state and returns to the Console state.
2630 
2631    In the Command state, if the simulation and system console are joined, then
2632    the system console device will not receive any keyboard input, and any
2633    console output call will return with an SCPE_STALL result.  To the user,
2634    pressing CTRL+E pauses any output in progress to the system console, which
2635    resumes automatically after the entered command is executed.  If the
2636    simulation and system console are separate, then the system console continues
2637    to function normally while the command is being entered at the simulation
2638    console.
2639 
2640    Pressing CTRL+E while in the Command state causes a simulation stop, just as
2641    it does in non-concurrent mode.  The console is returned to Console state,
2642    and any partially entered command is abandoned.
2643 
2644    While in Console mode, this routine supplies characters from a prior REPLY
2645    command as though they were typed by the user.  This allows command files to
2646    contain automated prompt/response pairs for console interaction.
2647 
2648    If a reply exists, a check is made to ensure that any specified reply delay
2649    has been met.  If it hasn't, then keyboard polling is performed normally.  If
2650    it has, or if we are in the middle of a reply, the next character in the
2651    reply string is returned to the caller.  If the next character is a NUL, then
2652    the reply is exhausted, and the reply context is cleared.
2653 
2654 
2655    Implementation notes:
2656 
2657     1. It would be nice to have better editing capability in Command mode, e.g.,
2658        to be able to recall from a command history and edit the resulting line.
2659        Unfortunately, the standard "sim_poll_kbd" routine returns characters and
2660        not keystrokes, so it is impossible to detect, e.g., an up-arrow key.  It
2661        might be easy to implement a one-line recall command by restoring the
2662        command buffer to the state just prior to ENTER, though then only the
2663        usual editing (BS and typing replacements) would be available.
2664 
2665     2. Currently, only a reply to a single device (the console) is allowed, so
2666        the reply list head pointer is used directly.  In the future, a linked
2667        list of reply structures will be used, and the routine will have to
2668        search the list for the one matching the console unit.
2669 */
2670 
ex_sim_poll_kbd(void)2671 t_stat ex_sim_poll_kbd (void)
2672 {
2673 RPPTR  rp;
2674 int32  reply_char;
2675 t_stat key_char;
2676 
2677 rp = rp_list;                                           /* start searching at the head of the reply list */
2678 
2679 if (keyboard_mode == Console) {                         /* if we are in console mode */
2680     if (rp != NULL) {                                   /*   then if a REPLY is pending */
2681         if (rp->rptr > rp->reply                        /*     then if we are already replying */
2682           || sim_gtime () >= rp->trigger) {             /*       or the delay time has been met */
2683             reply_char = (int32) *rp->rptr++;           /*         then get the reply next character */
2684 
2685             if (reply_char == 0)                        /* if it's the end-of-string NUL */
2686                 rp_list = NULL;                         /*   then clear the reply */
2687 
2688             else if (reply_char == sim_brk_char)        /* otherwise if it's the break character */
2689                 return SCPE_BREAK;                      /*   then report the break */
2690 
2691             else                                        /* otherwise */
2692                 return reply_char | SCPE_KFLAG;         /*   return the reply character */
2693             }
2694         }
2695 
2696     if (stop_requested) {                               /* if WRU was detected via a signal */
2697         key_char = SCPE_STOP;                           /*   then indicate a simulator stop */
2698         stop_requested = FALSE;                         /*     and clear the request */
2699         }
2700 
2701     else                                                /* otherwise */
2702         key_char = sim_poll_kbd ();                     /*   poll the keyboard for a key */
2703 
2704     if (key_char == SCPE_STOP && concurrent_mode) {     /* if it's the sim stop character and in concurrent mode */
2705         keyboard_mode = Command;                        /*   then switch to command mode */
2706 
2707         put_string ("\r\nscp> ");                       /* print the concurrent command prompt */
2708 
2709         cmd_ptr = cmd_buf;                              /* reset the command buffer pointer */
2710         *cmd_ptr = '\0';                                /*   and clear any previous command */
2711 
2712         return SCPE_OK;                                 /* return while absorbing the character */
2713         }
2714 
2715     else                                                /* otherwise */
2716         return key_char;                                /*   return the character */
2717     }
2718 
2719 else {                                                  /* otherwise we're in command mode */
2720     if (stop_requested) {                               /* if WRU was detected via a signal */
2721         key_char = SCPE_STOP;                           /*   then indicate a simulator stop */
2722         stop_requested = FALSE;                         /*     and clear the request */
2723         }
2724 
2725     else                                                /* otherwise */
2726         key_char = sim_os_poll_kbd ();                  /*   poll the simulation console keyboard for a key */
2727 
2728     if (key_char == SCPE_STOP) {                        /* if it's the sim stop character */
2729         keyboard_mode = Console;                        /*   then return to console mode */
2730 
2731         put_string ("\r\n");                            /* skip to the next line */
2732 
2733         cmd_ptr = cmd_buf;                              /* reset the command buffer pointer */
2734         *cmd_ptr = '\0';                                /*   and clear any pending command */
2735 
2736         return SCPE_STOP;                               /* stop the simulator */
2737         }
2738 
2739     else if (key_char & SCPE_KFLAG) {                   /* otherwise if a character was obtained */
2740         key_char = key_char & 0177;                     /*   then mask to just the value */
2741 
2742         if (key_char == CR || key_char == LF) {         /* if the character is carriage return or line feed */
2743             keyboard_mode = Console;                    /*   then return to console mode */
2744 
2745             put_string ("\r\n");                        /* skip to the next line */
2746 
2747             if (cmd_ptr != cmd_buf) {                   /* if the buffer is occupied */
2748                 *cmd_ptr = '\0';                        /*   then terminate the command buffer */
2749                 return SCPE_EXEC;                       /*     and execute the command */
2750                 }
2751             }
2752 
2753         else if (key_char == BS || key_char == DEL) {   /* otherwise if the character is backspace or delete */
2754             if (cmd_ptr > cmd_buf) {                    /*   then if the buffer contains characters */
2755                 cmd_ptr--;                              /*     then drop the last one */
2756                 put_string ("\b \b");                   /*       and clear it from the screen */
2757                 }
2758             }
2759 
2760         else if (key_char == ESC)                       /* otherwise if the character is escape */
2761             while (cmd_ptr > cmd_buf) {                 /*   then while characters remain in the buffer */
2762                 cmd_ptr--;                              /*     then drop them one by one */
2763                 put_string ("\b \b");                   /*       and clear them from the screen */
2764                 }
2765 
2766         else {                                          /* otherwise it's a normal character */
2767             *cmd_ptr++ = (char) (key_char);             /*   so add it to the buffer and advance the pointer */
2768             sim_os_putchar (key_char);                  /*     and echo it to the screen */
2769             }
2770         }
2771 
2772     if (sim_con_tmxr.master != 0)                       /* if the consoles are separate */
2773         return sim_poll_kbd ();                         /*   then poll the system console keyboard */
2774     else                                                /* otherwise we're in unified command mode */
2775         return SCPE_OK;                                 /*   so return with any obtained character absorbed */
2776     }
2777 }
2778 
2779 
2780 
2781 /* Local concurrent console and reply extension routines */
2782 
2783 
2784 /* Signal handler for CTRL+E.
2785 
2786    This routine is a SIGINT handler that is installed to detect CTRL+E on Unix
2787    systems and CTRL+C on others.  It is used in place of the standard
2788    "int_handler" routine.  That routine sets the global "stop_cpu" flag, which
2789    is tested in "sim_process_event" and causes simulated execution to stop.  For
2790    concurrent console operation, we must detect the condition without setting
2791    the global flag.  So this routine sets a local flag that is tested by our
2792    "ex_sim_poll_kbd" routine to switch from Console to Command mode.
2793 
2794    It is also used in our "execute_file" command to abort a DO command file that
2795    may be stuck in an infinite loop.
2796 
2797 
2798    Implementation notes:
2799 
2800     1. On Unix systems, WRU is registered as the SIGINT character, so CTRL+E
2801        does not arrive via the keyboard poll.  Instead, a SIGINT handler is
2802        used, which is why we need this handler to detect initiation of Command
2803        mode on Unix.
2804 */
2805 
wru_handler(int sig)2806 static void wru_handler (int sig)
2807 {
2808 stop_requested = TRUE;                                  /* indicate that WRU was seen */
2809 return;                                                 /*   and continue execution */
2810 }
2811 
2812 
2813 /* Write a string of characters to the console */
2814 
put_string(const char * cptr)2815 static void put_string (const char *cptr)
2816 {
2817 while (*cptr != '\0')                                   /* write characters to the console */
2818     sim_os_putchar (*cptr++);                           /*   until the end of the string */
2819 
2820 return;
2821 }
2822 
2823 
2824 /* Get a command descriptor.
2825 
2826    This routine searches for the command whose name is indicated by the "cptr"
2827    parameter and copies the corresponding command descriptor (CTAB) entry
2828    pointer into the pointer variable designated by the "cmdp" pointer.  If the
2829    command is not found, the routine sets the pointer variable to NULL and
2830    returns SCPE_UNK (Unknown command).  If the command is found but is
2831    restricted, the routine returns SCPE_NOFNC (Command not allowed).  Otherwise,
2832    the routine returns SCPE_OK.
2833 
2834    This routine is similar to the standard "find_cmd" routine, except that it
2835    also checks to see if the simulator is currently running and, if so, that the
2836    command is in the list of unrestricted commands.  A command entered while the
2837    simulator is running must not interfere with execution; those that do are
2838    deemed "restricted" commands.
2839 
2840 
2841    Implementation notes:
2842 
2843     1. The unrestricted ("allowed") command list is structured as a simple
2844        string, with the command names preceded and followed by spaces.  This
2845        allows a simple "strstr" search to look for a match.
2846 
2847     2. We search the list of unrestricted commands for the full command name to
2848        avoid false matches by, e.g., searching for the entered command "R"
2849        instead of "RUN".  We also add leading and trailing blanks before
2850        searching to ensure that we haven't matched a substring of an
2851        unrestricted command (although currently there are no restricted commands
2852        that are substrings of unrestricted commands).
2853 
2854     3. The "cptr" parameter cannot be declared "const" because it is passed to
2855        "find_cmd", which takes a non-const pointer.
2856 */
2857 
2858 static const char allowed_cmds [] = " "                         /* the list of unrestricted commands */
2859     "RESET "    "EXAMINE "  "DEPOSIT "  "EVALUATE " "BREAK "    /*   standard commands */
2860     "NOBREAK "  "ATTACH "   "DETACH "   "ASSIGN "   "DEASSIGN "
2861     "EXIT "     "QUIT "     "BYE "      "SET "      "SHOW "
2862     "DO "       "ECHO "     "ASSERT "   "HELP "
2863 
2864     "REPLY "    "NOREPLY "  "IF "       "DELETE "   "ABORT "    /*   extended commands */
2865     "FLUSH "
2866 
2867     "POWER ";                                                   /*   simulator-specific commands */
2868 
get_command(char * cptr,CTAB ** cmdp)2869 static t_stat get_command (char *cptr, CTAB **cmdp)
2870 {
2871 char   cmd_name [80];
2872 t_stat status;
2873 
2874 *cmdp = find_cmd (cptr);                                /* search for the command */
2875 
2876 if (*cmdp == NULL)                                      /* if the command is not valid */
2877     status = SCPE_UNK;                                  /*   then report it as unknown */
2878 
2879 else if (sim_is_running) {                              /* otherwise if commands are currently restricted */
2880     cmd_name [0] = ' ';                                 /*   then surround */
2881     strcpy (cmd_name + 1, (*cmdp)->name);               /*     the command name */
2882     strcat (cmd_name, " ");                             /*       with leading and trailing blanks */
2883 
2884     if (strstr (allowed_cmds, cmd_name) == NULL)        /* if the command keyword was not found in the list */
2885         status = SCPE_NOFNC;                            /*   then the command is restricted */
2886     else                                                /* otherwise */
2887         status = SCPE_OK;                               /*   the command is allowed */
2888     }
2889 
2890 else                                                    /* otherwise commands are not restricted */
2891     status = SCPE_OK;                                   /*   so the command is valid */
2892 
2893 return status;                                          /* return the search result */
2894 }
2895 
2896 
2897 
2898 /* ****************************  SCP Extensions  *******************************
2899 
2900    This module extends the following existing global routines in "scp.c":
2901 
2902      attach_unit -- attach a file to a unit
2903 
2904    ...and extends the following existing commands:
2905 
2906      RUN      -- reset and start simulation
2907      GO       -- start simulation
2908      STEP     -- execute <n> instructions
2909      CONTINUE -- continue simulation
2910      BOOT     -- bootstrap simulation
2911      BREAK    -- set breakpoints
2912      NOBREAK  -- clear breakpoints
2913      DO       -- execute commands in a file
2914      SET      -- set simulator options
2915      SHOW     -- show simulator options
2916 
2917    ...and adds the following new commands:
2918 
2919      REPLY   -- send characters to the console
2920      NOREPLY -- cancel a pending reply
2921      IF      -- execute commands only if a condition is TRUE
2922      DELETE  -- delete a file
2923      PAUSE   -- delay command file execution for a specified time
2924      GOTO    -- transfer control to a labeled line
2925      CALL    -- call a labeled subroutine
2926      RETURN  -- return control from a subroutine
2927      ABORT   -- abort nested command files
2928      FLUSH   -- flush all buffered files
2929 
2930    The ATTACH command, via the "attach_unit" routine, is extended to require the
2931    file to be attached to exist for read-only units (e.g., a paper tape reader)
2932    and to seek to the end of the file for write-only units (e.g., a line
2933    printer).  The existing behavior is to create an empty file or to write over
2934    the current file content, respectively.
2935 
2936    The RUN and GO commands are enhanced to add an UNTIL option that sets a
2937    temporary breakpoint, and all of the simulated execution commands are
2938    enhanced to save and restore an existing SIGINT handler that may be in effect
2939    within a DO command file.  The BREAK and NOBREAK commands are enhanced to
2940    provide temporary and string breakpoints.  DO is enhanced to provide GOTO,
2941    CALL, RETURN, and ABORT commands that affect the flow of control.  SET and
2942    SHOW are enhanced to provide access to environment variables, to provide
2943    serial support to the system console, and to provide a concurrent command
2944    mode during simulated execution.
2945 
2946    The new REPLY and NOREPLY commands enable and disable automated responses
2947    from the system console.  IF provides conditional command execution.  DELETE
2948    provides a host-independent method of deleting a file, such as a temporary or
2949    scratch file.  PAUSE suspends command file execution for a user-specified
2950    time.
2951 
2952    Also, an extension is provided to add optional binary data interpretation to
2953    the existing octal, decimal, and hexadecimal command-line overrides.
2954 
2955    In addition, command-line parameter substitution is extended to provide a set
2956    of substitution variables that yield the current date and time in various
2957    formats, as well as environment variable values.  Combined with the new SET
2958    ENVIRONMENT and IF commands, arbitrary variable values may be set, tested,
2959    and used to affect command file execution.
2960 */
2961 
2962 
2963 /* Global SCP extension routines */
2964 
2965 
2966 /* Attach a file or to a unit.
2967 
2968    This shim for "attach_unit" is called to attach a file to a specified unit.
2969    It modifies the standard attach behavior for read-only and write-only
2970    sequential units.
2971 
2972    Default behavior for read-only units is to create an empty file if the
2973    specified file does not exist.  Attaching an empty file is pointless and is
2974    often the result of specifying the file name incorrectly, requiring a
2975    subsequent detach/delete/reattach command sequence to correct.  To avoid
2976    this, the user must always add the -E (must exist) switch.  The enhanced
2977    behavior adds this switch automatically, producing a "File open error"
2978    message if the file does not exist.
2979 
2980    For write-only units, standard behavior leaves an existing file positioned at
2981    the start of the file.  This is almost never desirable, as it means that if
2982    the new content is shorter, a partial remainder of the prior output is left.
2983    While merely undesirable in the case of a line printer, it is erroneous in
2984    the case of a paper tape punch producing an executable memory image.  The
2985    enhanced behavior ensures that new content is appended by seeking to the end
2986    of the attached file before returning.
2987 
2988    The routine returns the result of the operation, which will be SCPE_OK if the
2989    attach and optional EOF seek succeed, SCPE_IOERR if the attach succeeds but
2990    the EOF seek fails (in this case, the file will be detached before
2991    returning), or the appropriate status code if the attach fails.
2992 
2993 
2994    Implementation notes:
2995 
2996     1. "attach_unit" opens the file using one of the following modes:
2997 
2998          - "r" (reading) if the -R (read-only) switch or UNIT_RO is present or
2999            the file is marked read-only by the host file system
3000 
3001          - "w+" (truncate and update) if the -N (new) switch is present or the
3002            file does not exist
3003 
3004          - "r+" (update) otherwise
3005 
3006     2. Reopening with mode "a" or "a+" doesn't have the desired semantics.
3007        These modes permit writing only at the EOF, but we want to permit
3008        overwriting if the user explicitly sets POS.  The resulting "fseek" on
3009        resumption would be ignored if the mode is "a" or "a+".  Therefore, we
3010        accept mode "r+" and explicitly "fseek" to the end of file here.
3011 
3012     3. If we are called during a RESTORE command to reattach a file previously
3013        attached when the simulation was SAVEd, the file position is not altered.
3014 
3015     4. It is not necessary to report the device or unit if the 'fseek" fails, as
3016        this routine is called only is response to a user command that specifies
3017        the unit to attach.  Nor is a "clearerr" call needed, as the unit is
3018        detached before returning.
3019 */
3020 
ex_attach_unit(UNIT * uptr,char * cptr)3021 t_stat ex_attach_unit (UNIT *uptr, char *cptr)
3022 {
3023 const uint32 unit_type  = UNIT_SEQ | UNIT_ROABLE;       /* mask for unit type flags */
3024 const uint32 unit_read  = UNIT_SEQ | UNIT_ROABLE;       /* flags for a read-only unit */
3025 const uint32 unit_write = UNIT_SEQ;                     /* flags for a write-only unit */
3026 t_stat       result;
3027 t_offset     position;
3028 char         *error = NULL;
3029 
3030 if ((uptr->flags & unit_type) == unit_read)             /* if the unit is read-only */
3031     sim_switches |= SWMASK ('E');                       /*   then ensure that the file exists */
3032 
3033 result = attach_unit (uptr, cptr);                      /* attach the specified image file */
3034 
3035 if (result == SCPE_OK                                   /* if the attach was successful */
3036   && not (sim_switches & SIM_SW_REST)                   /*   and we are not being called for RESTORE */
3037   && not (uptr->dynflags & UNIT_PIPE)                   /*   and the file is not a pipe */
3038   && (uptr->flags & unit_type) == unit_write) {         /*   and the unit is write-only */
3039     if (sim_fseek (uptr->fileref, 0, SEEK_END) != 0)    /*     then append by seeking to the EOF */
3040         error = "seek";                                 /* record an error if the seek failed */
3041 
3042     else {                                              /* otherwise */
3043         position = sim_ftell (uptr->fileref);           /*   get the new position */
3044 
3045         if (position == -1)                             /* if the retrieval failed */
3046             error = "position";                         /*   then record the error */
3047         else                                            /* otherwise */
3048             uptr->pos = (t_addr) position;              /*   set the new position */
3049         }
3050 
3051     if (error) {                                        /* if an error occurred */
3052         printf ("%s simulator %s error: %s\n",          /*   the report it to the console */
3053                 sim_name, error, strerror (errno));
3054 
3055         if (sim_log)                                            /* if the console log is defined */
3056             fprintf (sim_log, "%s simulator %s error: %s\n",    /*   then report the error to the log also */
3057                      sim_name, error, strerror (errno));
3058 
3059         detach_unit (uptr);                             /* detach the unit */
3060         result = SCPE_IOERR;                            /*   and report that repositioning failed */
3061         }
3062     }
3063 
3064 return result;
3065 }
3066 
3067 
3068 /* Global SCP command extension handler routines */
3069 
3070 
3071 /* Execute the BREAK and NOBREAK commands.
3072 
3073    This command processing routine extends the existing BREAK and NOBREAK
3074    commands to provide temporary and string breakpoints.  The routine processes
3075    commands of the form:
3076 
3077      BREAK { -T } <address-list> { ; <action> ... }
3078      BREAK { -T } <quoted-string> { ; <action> ... }
3079      BREAK { -T } <quoted-string> DELAY <interval> { ; <action> ... }
3080      BREAK DELAY <interval>
3081 
3082      NOBREAK <quoted-string>
3083      NOBREAK ""
3084      NOBREAK ALL
3085 
3086    Where:
3087 
3088      -T       = indicates that the breakpoint is temporary
3089 
3090      interval = the number of event ticks that elapse after the breakpoint is
3091                 satisfied before execution is stopped; the delay is expressed
3092                 either directly by an event tick count, or as a real-time value
3093                 by a count and a time unit, such as MILLISECONDS
3094 
3095    The new "-T" breakpoint type switch indicates that the breakpoint will be set
3096    temporarily.  A temporary breakpoint is removed once it occurs; this is
3097    equivalent to setting the (first) breakpoint action to NOBREAK.  Without
3098    "-T", the breakpoint is persistent and will cause a simulation stop each time
3099    it occurs.
3100 
3101    String breakpoints cause simulator stops when the character sequences are
3102    encountered in the system console output stream; these are similar to stops
3103    that occur when CPU execution reaches specified memory addresses.
3104 
3105    The first string form sets a breakpoint that stops simulator execution when
3106    the content of the quoted string appears in the system console output.  By
3107    default, the simulator stops immediately after the final character of the
3108    quoted string is output.  The second string form may be used to insert a
3109    delay of the specified interval before execution stops.  The delay is set
3110    temporarily for that breakpoint; it then reverts to the default delay for
3111    subsequent breakpoints.
3112 
3113    If all of the string breakpoints for a program require a delay, it may be set
3114    as the new default by using the BREAK DELAY command.
3115 
3116    Time units may be MICROSECOND[S], MILLISECOND[S], or SECOND[S]; the
3117    abbreviations USEC[S] and MSEC[S] may be used interchangeably for the first
3118    two units, respectively.
3119 
3120    The optional action commands are executed when the breakpoint occurs.  If the
3121    breakpoint is temporary, the actions are executed once; otherwise, they
3122    execute each time the breakpoint occurs.
3123 
3124    The first NOBREAK command form cancels the string breakpoint specified by the
3125    quoted string.  The second form cancels all string breakpoints; the empty
3126    quoted string is required to differentiate between canceling string
3127    breakpoints and canceling the current address breakpoint.  The NOBREAK ALL
3128    command cancels all string breakpoints in addition to canceling all address
3129    breakpoints.
3130 
3131    Specifying a quoted string in a BREAK command that matches an existing
3132    breakpoint replaces the delay and actions of that breakpoint with the values
3133    specified in the new BREAK command.  It does not create a second breakpoint
3134    with the same string.
3135 
3136 
3137    Implementation notes:
3138 
3139     1. Currently, only one type of string breakpoint is defined (the implicit
3140        "BP_STRING" type), although we include all command-line switches in the
3141        "type" field of the breakpoint structure for future use.
3142 
3143     2. A NOBREAK command specifying a breakpoint string that does not match any
3144        existing breakpoint succeeds with no warning message to be consistent
3145        with the behavior of NOBREAK <address> that does match an existing
3146        breakpoint.
3147 
3148     3. If a command contains valid break addresses followed by an invalid one,
3149        the SCP break handler will set the valid ones before returning an
3150        "Invalid argument" error.  To the user, it appears that the command
3151        failed, while actually one or more breakpoints were set successfully.
3152        Arguably, it would be better if the SCP routine set either all or none,
3153        but we make no attempt to fix the issue here.
3154 
3155     4. A BREAK DELAY comamnd to set the global delay value is rejected if any
3156        switches are specified.  This helps the RUN/GO handler, which then does
3157        not have to check explicitly for the invalid RUN UNTIL DELAY <interval>
3158        case.
3159 */
3160 
3161 #define SIM_SW_ALPHA        ((1u << 26) - 1)                /* all alpha switches */
3162 #define SIM_BREAK_MASK      (SIM_SW_ALPHA & ~SWMASK ('T'))  /* mask for the alpha switches except "T" */
3163 
ex_break_cmd(int32 flag,char * cptr)3164 static t_stat ex_break_cmd (int32 flag, char *cptr)
3165 {
3166 SBPTR  bp, prev;
3167 char   *aptr, *optr, mbuf [CBUFSIZE];
3168 int32  delay;
3169 t_stat status;
3170 
3171 cptr = get_sim_sw (cptr);                               /* get any command-line switches */
3172 
3173 if (cptr == NULL)                                       /* if an invalid switch was present */
3174     return SCPE_INVSW;                                  /*   then report it */
3175 else                                                    /* otherwise */
3176     optr = cptr;                                        /*   save the original command-line pointer */
3177 
3178 if (flag == SSH_ST) {                                   /* if this might be a BREAK DELAY command */
3179     status = parse_time ("DELAY", &cptr, &delay);       /*   then attempt to parse a DELAY clause */
3180 
3181     if (status == SCPE_OK)                              /* if the delay was given */
3182         if (*cptr != '\0')                              /*   then if more characters follow */
3183             return SCPE_2MARG;                          /*     then too many arguments were given */
3184 
3185         else if (sim_switches & SIM_SW_ALPHA)           /* otherwise if any switches are present */
3186             return SCPE_INVSW;                          /*   then report that they are not allowed */
3187 
3188         else {                                          /*   otherwise */
3189             break_delay = delay;                        /*     set the global delay value */
3190             return SCPE_OK;                             /*       and we're done */
3191             }
3192 
3193     else if (status != SCPE_FMT)                        /* otherwise if the numeric parse failed */
3194         return status;                                  /*   then return the error status */
3195     }
3196 
3197 if (*cptr == '\'' || *cptr == '"') {                        /* if a quoted string is present then parse it */
3198     cptr = parse_quoted_string (cptr, mbuf, FALSE, TRUE);   /*   without upshifting and with decoding */
3199 
3200     if (cptr == NULL)                                   /* if the string is not terminated */
3201         return SCPE_ARG;                                /*   then report a bad argument */
3202 
3203     else                                                /* otherwise the string is valid */
3204         if (flag == SSH_CL)                             /*   so if this is a NOBREAK command */
3205             if (*cptr != '\0')                          /*     then if there are extraneous characters */
3206                 return SCPE_2MARG;                      /*       then report too many arguments */
3207 
3208             else if (mbuf [0] == '\0') {                /*     otherwise if the string is empty */
3209                 free_breakpoints ();                    /*       then free all of the string breakpoints */
3210                 return SCPE_OK;                         /*         and we're done */
3211                 }
3212 
3213             else {                                      /*     otherwise */
3214                 bp = find_breakpoint (mbuf, &prev);     /*       find the specified breakpoint */
3215 
3216                 if (bp != NULL) {                       /* if it is present */
3217                     if (prev != NULL)                   /*   then if there is a previous node */
3218                         prev->next = bp->next;          /*     then link it to the next one */
3219                     else                                /*   otherwise we're clearing the first node */
3220                         sb_list = bp->next;             /*     so point the header at the next one */
3221 
3222                     free (bp);                          /* free the current node */
3223                     }
3224 
3225                 return SCPE_OK;                         /* either way, we're done */
3226                 }
3227 
3228         else {                                          /*   otherwise this is a BREAK command */
3229             aptr = strchr (cptr, ';');                  /*     so search for actions */
3230 
3231             if (aptr != NULL)                           /* if actions are present */
3232                 *aptr++ = '\0';                         /*   then separate the actions from the breakpoints */
3233 
3234             if (*cptr == '\0')                          /* if no DELAY clause follows */
3235                 delay = break_delay;                    /*   then use the global delay value */
3236 
3237             else {                                              /* otherwise */
3238                 status = parse_time ("DELAY", &cptr, &delay);   /*   attempt to parse a DELAY clause */
3239 
3240                 if (status == SCPE_FMT)                 /* if the keyword is not present */
3241                     return SCPE_ARG;                    /*   then the syntax is bad */
3242 
3243                 else if (status != SCPE_OK)             /* otherwise if the numeric parse failed */
3244                     return status;                      /*   then return the error status */
3245 
3246                 else if (*cptr != '\0')                 /* otherwise if more characters follow */
3247                     return SCPE_2MARG;                  /*   then too many arguments were given */
3248                 }
3249 
3250             bp = find_breakpoint (mbuf, &prev);         /* see if the string matches an existing breakpoint */
3251 
3252             if (bp == NULL) {                                       /* if it does not */
3253                 bp = (SBPTR) malloc (sizeof (STRING_BREAKPOINT));   /*   then allocate a new breakpoint */
3254 
3255                 if (bp == NULL)                         /* if the allocation failed */
3256                     return SCPE_MEM;                    /*   then report the error */
3257 
3258                 else if (prev == NULL)                  /* otherwise if this is the first breakpoint */
3259                     sb_list = bp;                       /*   then set the list header to point at it */
3260 
3261                 else                                    /* otherwise */
3262                     prev->next = bp;                    /*   add it to the end of the existing list */
3263                 }
3264 
3265             bp->next = NULL;                            /* set the next node pointer */
3266             bp->uptr = vm_console_output_unit;          /*   and the output unit pointer */
3267 
3268             strcpy (bp->match, mbuf);                   /* copy the match string */
3269             bp->mptr = bp->match;                       /*   and set the match pointer to the start */
3270 
3271             bp->type    = sim_switches | BP_STRING;     /* add the "string breakpoint" flag */
3272             bp->count   = 0;                            /* clear the count */
3273             bp->delay   = delay;                        /* set the delay value */
3274             bp->trigger = -1.0;                         /*   and clear the trigger time */
3275 
3276             if (aptr == NULL)                           /* if no actions were specified */
3277                 bp->action [0] = '\0';                  /*   then clear the action buffer */
3278 
3279             else {                                      /* otherwise */
3280                 while (isspace (*aptr))                 /*   skip any leading blanks */
3281                     aptr++;                             /*     that might precede the first action */
3282 
3283                 strcpy (bp->action, aptr);              /* copy the action string */
3284                 }
3285 
3286             return SCPE_OK;                             /* return with success */
3287             }
3288     }
3289 
3290 else {                                                          /* otherwise */
3291     if (flag == SSH_ST && (sim_switches & SIM_BREAK_MASK) == 0) /*   if no breakpoint type switches are set */
3292         sim_switches |= sim_brk_dflt;                           /*     then use the specified default types */
3293 
3294     status = break_handler (flag, optr);                /* process numeric breakpoints */
3295 
3296     if (status == SCPE_OK && flag == SSH_CL) {          /* if the NOBREAK succeeded */
3297         get_glyph (cptr, mbuf, 0);                      /*   then parse out the next glyph */
3298 
3299         if (strcmp (mbuf, "ALL") == 0)                  /* if this was a NOBREAK ALL command */
3300             free_breakpoints ();                        /*   then clear all string breakpoints too */
3301         }
3302 
3303     return status;                                      /* return the command status */
3304     }
3305 }
3306 
3307 
3308 /* Execute the REPLY and NOREPLY commands.
3309 
3310    This command processing routine adds new REPLY and NOREPLY commands to
3311    automate replies through the system console when programmatic input is next
3312    requested by the target OS.  The routine processes commands of the form:
3313 
3314      REPLY <quoted-string>
3315      REPLY <quoted-string> DELAY <interval>
3316      REPLY DELAY <interval>
3317 
3318      NOREPLY
3319 
3320    Where:
3321 
3322      interval = the number of event ticks that must elapse before the first
3323                 character of the reply is sent; the delay is expressed either
3324                 directly by an event tick count, or as a real-time value by a
3325                 count and a time unit, such as MILLISECONDS
3326 
3327    The first form supplies the content of the quoted string to the system
3328    console, character by character, as though entered by pressing keys on the
3329    keyboard.  By default, the first character is supplied to the console device
3330    immediately after simulation is resumed with a GO or CONTINUE command.  The
3331    second form may be used to insert a delay of the specified interval before
3332    the first character is supplied.
3333 
3334    If the second form is used, the delay is set temporarily for that reply; it
3335    then reverts to the default delay for subsequent replies.  If all of the
3336    replies to a program require a delay, it may be set as the new default by
3337    using the third form.
3338 
3339    Time units may be MICROSECOND[S], MILLISECOND[S], or SECOND[S]; the
3340    abbreviations USEC[S] and MSEC[S] may be used interchangeably for the first
3341    two units, respectively.
3342 
3343    The NOREPLY command cancels any pending reply.  Replies are also effectively
3344    canceled when they are consumed.
3345 
3346 
3347    Implementation notes:
3348 
3349     1. Currently, only a reply to a single device (the console) is allowed, so
3350        the reply list head pointer is set to point at a static structure.  In
3351        the future, the structures will be allocated and deallocated dynamically.
3352 */
3353 
ex_reply_cmd(int32 flag,char * cptr)3354 static t_stat ex_reply_cmd (int32 flag, char *cptr)
3355 {
3356 char   rbuf [CBUFSIZE];
3357 int32  delay;
3358 t_stat status;
3359 
3360 if (flag) {                                             /* if this is a NOREPLY command */
3361     rp_list = NULL;                                     /*   then clear any pending reply */
3362     return SCPE_OK;                                     /*     and we're done */
3363     }
3364 
3365 else if (*cptr == '\0')                                 /* otherwise if a REPLY has no quoted string */
3366     return SCPE_MISVAL;                                 /*   then report it as missing */
3367 
3368 else                                                    /* otherwise */
3369     status = parse_time ("DELAY", &cptr, &delay);       /*   attempt to parse a DELAY clause */
3370 
3371 if (status == SCPE_OK)                                  /* if the delay was given */
3372     if (*cptr != '\0')                                  /*   then if more characters follow */
3373         return SCPE_2MARG;                              /*     then too many arguments were given */
3374 
3375     else {                                              /*   otherwise */
3376         reply_delay = delay;                            /*     set the global delay value */
3377         return SCPE_OK;                                 /*       and we're done */
3378         }
3379 
3380 else if (status != SCPE_FMT)                            /* otherwise if the numeric parse failed */
3381     return status;                                      /*   then return the error status */
3382 
3383 if (*cptr == '\'' || *cptr == '"') {                        /* if a quoted string is present then parse it */
3384     cptr = parse_quoted_string (cptr, rbuf, FALSE, TRUE);   /*   without upshifting and with decoding */
3385 
3386     if (cptr == NULL)                                   /* if the string is not terminated */
3387         return SCPE_ARG;                                /*   then report a bad argument */
3388 
3389     else {                                              /* otherwise the string is valid */
3390         if (*cptr == '\0')                              /* if no DELAY clause follows */
3391             delay = reply_delay;                        /*   then use the global delay value */
3392 
3393         else {                                              /* otherwise */
3394             status = parse_time ("DELAY", &cptr, &delay);   /*   attempt to parse a DELAY clause */
3395 
3396             if (status == SCPE_FMT)                     /* if the keyword is not present */
3397                 return SCPE_ARG;                        /*   then the syntax is bad */
3398 
3399             else if (status != SCPE_OK)                 /* otherwise if the numeric parse failed */
3400                 return status;                          /*   then return the error status */
3401 
3402             else if (*cptr != '\0')                     /* otherwise if more characters follow */
3403                 return SCPE_2MARG;                      /*   then too many arguments were given */
3404             }
3405 
3406         rp_list = &rpx;                                 /* point at the new reply structure */
3407 
3408         rp_list->uptr = vm_console_input_unit;          /* set the input unit pointer */
3409 
3410         strcpy (rp_list->reply, rbuf);                  /* copy the reply string */
3411         rp_list->rptr = rp_list->reply;                 /*   and point at the starting character */
3412 
3413         rp_list->trigger = sim_gtime () + delay;        /* set the trigger time delay */
3414 
3415         return SCPE_OK;                                 /* return success */
3416         }
3417     }
3418 
3419 else                                                    /* otherwise something other than */
3420     return SCPE_ARG;                                    /*   a quoted string is present */
3421 }
3422 
3423 
3424 /* Execute the RUN, GO, STEP, CONTINUE, and BOOT commands.
3425 
3426    This command processing routine enhances the existing RUN and GO commands to
3427    provide optional temporary breakpoints.  The routine processes RUN and GO
3428    commands of the form:
3429 
3430      GO UNTIL <stop-address> { ; <action> ... }
3431      GO UNTIL <quoted-string> { ; <action> ... }
3432      GO UNTIL <quoted-string> DELAY <interval> { ; <action> ... }
3433 
3434      GO <start-address> UNTIL <stop-address> { ; <action> ... }
3435      GO <start-address> UNTIL <quoted-string> { ; <action> ... }
3436      GO <start-address> UNTIL <quoted-string> DELAY <interval> { ; <action> ... }
3437 
3438      GO FOR <interval> { ; <action> ... }
3439      GO <start-address> FOR <interval> { ; <action> ... }
3440 
3441    Where:
3442 
3443      interval = the number of event ticks that elapse after the breakpoint is
3444                 satisfied (GO UNTIL) or after execution begins (GO FOR) before
3445                 execution is stopped; the delay is expressed either directly by
3446                 an event tick count, or as a real-time value by a count and a
3447                 time unit, such as MILLISECONDS
3448 
3449    The "GO UNTIL" command for <stop-address> and <quoted-string> is equivalent
3450    to "BREAK -T" and "GO".  Multiple <stop-address>es, separated by commas, may
3451    be specified.  For example, "GO 5 UNTIL 10,20" sets temporary breakpoints at
3452    addresses 10 and 20 and then resumes simulator execution at address 5.  As
3453    with the BREAK command, specifying a DELAY value sets a temporary delay of
3454    the specified interval before execution stops.  If a DELAY value is not
3455    given, the breakpoint uses the default delay set by an earlier BREAK DELAY
3456    command, or a zero delay if the default has not been overridden.
3457 
3458    The "GO FOR" command stops execution after the specified interval.
3459    Specifying an event tick count directly is equivalent to STEP <count>.  A
3460    specified time interval is converted internally into the equivalent number of
3461    event ticks, based on the CPU execution rate.  Time units may be
3462    MICROSECOND[S], MILLISECOND[S], or SECOND[S]; the abbreviations USEC[S] and
3463    MSEC[S] may be used interchangeably for the first two units, respectively.
3464 
3465    The time delay specified is a "realistic time," i.e., represents wall-clock
3466    time when using the original hardware.  Although the simulator typically runs
3467    an order of magnitude faster than the original hardware did, a given time
3468    delay will allow the CPU to execute approximately the same number of machine
3469    instructions as would be executed on hardware.
3470 
3471    The STEP, CONTINUE, and BOOT commands are unaltered.  They are handled here
3472    so that all execution commands may set the "SIM_SW_HIDE" command switch to
3473    suppress step and breakpoint messages while executing in command files.  This
3474    allows automated prompt/response pairs to be displayed without cluttering up
3475    the output with intervening "Step completed" and "Breakpoint" messages.
3476 
3477 
3478    Implementation notes:
3479 
3480     1. The DO executor sets a SIGINT handler to permit interrupting an infinite
3481        command loop.  We save and restore this handler around the call to the
3482        standard RUN command handler because that routine restores the default
3483        handler instead of the previous handler when it completes.  This action
3484        would cancel the DO executor's handler installation if we did not save
3485        and restore it here.
3486 
3487        We save the prior handler by installing the default handler, which is the
3488        condition the standard RUN handler expects on entry.
3489 
3490     2. A DO command in concurrent mode must be handled outside of the VM's
3491        instruction execution routine.  This is because we want to permit
3492        unrestricted commands, such as GO UNTIL, to enable prompt/response
3493        entries in the command file.  But we cannot execute the DO command after
3494        exiting our routine, e.g., by setting up the command as a breakpoint
3495        action and then returning, because if we've been called by an enclosing
3496        command file, returning will advance the file pointer, so that the
3497        command that invoked us won't be reexecuted.  Instead, we must call the
3498        DO command processor here and then reenter the instruction execution
3499        routine if no error exists.
3500 
3501     3. The DO command handler clears "sim_switches" for each command invocation,
3502        so we must save the run switches and restore them after handling a
3503        concurrent DO command.
3504 
3505     4. The global "concurrent_run" flag may be examined within a DO command file
3506        executing in concurrent mode.  The flag is initially FALSE.  We save and
3507        restore the flag on entry and exit to ensure that it remains TRUE if
3508        simulation is stopped within the DO file (if we set it FALSE on exit, it
3509        would show FALSE when examined after a command file breakpoint (e.g.),
3510        even though we were still executing within the context of a
3511        currently-running session, which would resume when the DO file is
3512        finished.
3513 
3514     5. All errors from a concurrent DO file invocation are reported but do not
3515        stop CPU execution.  The exception is the EXIT command, which not only
3516        stops the CPU but also exits the simulator (the alternative would be to
3517        treat EXIT as a NOP, but then the DO file would exhibit different
3518        behavior, depending on whether or not it was invoked in concurrent mode).
3519 
3520     6. The concurrent execution loop passes the incoming operation flag and
3521        specified new P value to the original run handler.  We must change the
3522        flag to indicate a CONTINUE for the second and subsequent iterations.
3523        Otherwise, a RUN command would reset all devices before resuming, and a
3524        GO command with a new P value would reset P before returning.  We do not
3525        need to remove a new P value because a CONTINUE will ignore that parameter.
3526 
3527     7. A "GO UNTIL DELAY <interval>" command is invalid because it does not
3528        specify a breakpoint.  It will be rejected by the breakpoint handler we
3529        call to process it because we set the -T switch before the call.  The
3530        breakpoint handler rejects all (alpha) switches when setting the global
3531        delay value with SCPE_INVSW (Invalid switch).  We change that to SCPE_ARG
3532        (Invalid argument), as that will make more sense to the user, who may not
3533        have specified a switch.
3534 
3535     8. SCP breakpoint syntax permits the semicolon used to introduce the action
3536        list to be adjacent to the time interval without an intervening space.
3537        In parsing the interval, the semicolon could be specified as the
3538        "optional end of glyph character" to obtain proper separation.  However,
3539        the "get_glyph" routine will then advance the character pointer past the
3540        semicolon, and there would be no indication to this routine that the
3541        character was actually present.  Therefore, we must explicitly scan for a
3542        semicolon to determine the presence of an action list before parsing time
3543        intervals.  As we have to do this anyway, we also change the semicolon to
3544        a NUL to make interval parsing simpler.
3545 */
3546 
ex_run_cmd(int32 flag,char * cptr)3547 static t_stat ex_run_cmd (int32 flag, char *cptr)
3548 {
3549 SIG_HANDLER  prior_handler;
3550 char         *aptr, gbuf [CBUFSIZE], pbuf [CBUFSIZE];
3551 int32        delay;
3552 t_stat       status;
3553 t_bool       entry_concurrency = concurrent_run;        /* save the concurrent run status on entry */
3554 int32        entry_switches = sim_switches;             /* save a copy of the entry switches */
3555 
3556 keyboard_mode = Console;                                /* always start in console mode */
3557 
3558 if (flag == RU_RUN || flag == RU_GO) {                  /* if this is a RUN or GO command */
3559     if (sim_strncasecmp ("UNTIL", cptr, 5) != 0         /*   then if it is not followed by UNTIL */
3560       && sim_strncasecmp ("FOR", cptr, 3) != 0)         /*     or by FOR */
3561         cptr = get_glyph (cptr, pbuf, 0);               /*       then get the new starting address */
3562     else                                                /*   otherwise */
3563         pbuf [0] = '\0';                                /*     clear the new starting address */
3564 
3565     if (*cptr == '\0')                                  /* if nothing follows */
3566         cptr = pbuf;                                    /*   then point at the new P value, if any */
3567 
3568     else {                                              /* otherwise more follows */
3569         aptr = get_glyph (cptr, gbuf, 0);               /*   so get the next glyph */
3570 
3571         if (strcmp (gbuf, "UNTIL") == 0)                /* if this is an UNTIL clause */
3572             if (*aptr == '\0')                          /*   then if nothing follows */
3573                 return SCPE_MISVAL;                     /*     then report that the parameter is missing */
3574 
3575             else {                                      /* otherwise */
3576                 sim_switches |= SWMASK ('T');           /*   add the temporary breakpoint flag */
3577 
3578                 status = ex_break_cmd (SSH_ST, aptr);   /* process and set the numeric or string breakpoint */
3579 
3580                 sim_switches = entry_switches;          /* restore the original switches */
3581 
3582                 if (status == SCPE_INVSW)               /* if an invalid switch is reported */
3583                     return SCPE_ARG;                    /*   then report that GO UNTIL DELAY is not allowed */
3584 
3585                 else if (status != SCPE_OK)             /* otherwise if the breakpoint parsed incorrectly */
3586                     return status;                      /*   then report it */
3587                 }
3588 
3589         else {                                          /* otherwise parse a potential timed breakpoint */
3590             aptr = strchr (cptr, ';');                  /*   by searching for the start of the action list */
3591 
3592             if (aptr != NULL)                           /* if actions are present */
3593                 *aptr++ = '\0';                         /*   then separate the actions from the breakpoints */
3594 
3595             status = parse_time ("FOR", &cptr, &delay); /* attempt to parse a FOR clause */
3596 
3597             if (status == SCPE_FMT)                     /* if the keyword is not present */
3598                 return SCPE_ARG;                        /*   then the syntax is bad */
3599 
3600             else if (status != SCPE_OK)                 /* otherwise if the numeric parse failed */
3601                 return status;                          /*   then return the error status */
3602 
3603             else if (*cptr != '\0')                     /* otherwise if more characters follow */
3604                 return SCPE_2MARG;                      /*   then too many arguments were given */
3605 
3606             else {                                      /* otherwise the numeric parse succeeded */
3607                 if (aptr != NULL)                       /*   so if actions are present */
3608                     strcpy (timed_bp_actions, aptr);    /*     then copy the action string */
3609 
3610                 else                                    /*   otherwise */
3611                     timed_bp_actions [0] = '\0';        /*     clear any old actions */
3612 
3613                 sim_activate_abs (timed_bp_unit, delay);    /* schedule the timed breakpoint */
3614                 }
3615             }
3616 
3617         cptr = pbuf;                                    /* point at the new P value, if any */
3618         }
3619     }
3620 
3621 prior_handler = signal (SIGINT, SIG_DFL);               /* install the default handler and save the current one */
3622 
3623 if (prior_handler == SIG_ERR)                           /* if installation failed */
3624     status = SCPE_SIGERR;                               /*   then report an error */
3625 
3626 else {                                                  /* otherwise */
3627     concurrent_run = TRUE;                              /*   mark the VM as running */
3628 
3629     do {                                                /* loop to process concurrent DO commands */
3630         concurrent_do_ptr = NULL;                       /* clear the DO pointer */
3631 
3632         status = run_handler (flag, cptr);              /* call the base handler to run the simulator */
3633 
3634         if (concurrent_do_ptr == NULL)                  /* if a DO command was not entered */
3635             break;                                      /*   then fall out of the loop */
3636 
3637         else {                                          /* otherwise a concurrent DO command was entered */
3638             strcpy (gbuf, concurrent_do_ptr);           /*   so copy the command parameters locally */
3639             status = ex_do_handler (1, gbuf);           /*     and execute the DO command */
3640 
3641             if (status != SCPE_OK && status != SCPE_EXIT) { /* if the command failed */
3642                 printf ("%s\n", sim_error_text (status));   /*   then print the error message */
3643 
3644                 if (sim_log)                                /* if the console is logging */
3645                     fprintf (sim_log, "%s\n",               /*   then write it to the log file as well */
3646                              sim_error_text (status));
3647 
3648                 status = SCPE_OK;                       /* continue execution unless it was an EXIT command */
3649                 }
3650 
3651             if (sim_vm_post != NULL)                    /* if the VM wants command notification */
3652                 (*sim_vm_post) (TRUE);                  /*   then let it know we executed a command */
3653 
3654             flag = RU_CONT;                             /* set run mode to CONTINUE */
3655             sim_switches = entry_switches;              /*   and restore the original switches */
3656             }
3657         }
3658     while (status == SCPE_OK);                          /* continue to execute in the absence of errors */
3659 
3660     concurrent_run = entry_concurrency;                 /* return to the previous VM-running state */
3661 
3662     signal (SIGINT, prior_handler);                     /* restore the prior handler */
3663     }
3664 
3665 return status;                                          /* return the command status */
3666 }
3667 
3668 
3669 /* Execute the DO command.
3670 
3671    This command processing routine enhances the existing DO command to permit
3672    CTRL+C to abort a command file or a nested series of command files.  The
3673    actual command file processing is handled by a subsidiary routine.
3674 
3675    It also executes commands in a new global initialization file at system
3676    startup.  It looks for the file "simh.ini" in the current directory; if not
3677    found, it then looks for the file in the HOME or USERPROFILE directory.  The
3678    file is optional; if it exists, it is executed before the command-line file
3679    or the simulator-specific file.
3680 
3681    Therefore, the search order for "simh.ini" is the current directory first,
3682    then the HOME directory if the HOME variable exists, or else the USERPROFILE
3683    directory if that variable exists.  So a user may override a "simh.ini" file
3684    present in the HOME or USERPROFILE directories by one in the current
3685    directory.
3686 
3687    An error encountered in the global initialization file is reported and stops
3688    execution of that file but does not inhibit execution of the simulator-
3689    specific initialization file.
3690 
3691    On entry, the "cptr" parameter points at the invocation string after the DO
3692    keyword.  The "file" parameter is NULL to indicate that the routine is to
3693    open the filename present at the start of the "cptr" string.  The "flag"
3694    parameter indicates the source of the call and nesting level and contains one
3695    of these values:
3696 
3697      < 0 = initialization file (no alternate if not found)
3698        0 = startup command line file
3699        1 = "DO" command
3700      > 1 = nested DO or CALL command
3701 
3702    For a nested command call, "flag" contains the nesting level in bits 0-3 and
3703    the value of the invoking switches in bits 4-29.  This allows the switch
3704    settings to propagate to nested command files.
3705 
3706 
3707    Implementation notes:
3708 
3709     1. This routine is always called during system startup before the main
3710        command loop is entered.  It is called to execute the command file
3711        specified on the command line, or, if there is none, the command file
3712        associated with the simulator (e.g., "hp2100.ini").  The call occurs even
3713        if neither of these files exist.  We detect this initial call and execute
3714        the global initialization file before either of these command files.
3715 
3716     2. We save the command-line switches before executing the global
3717        initialization file, so that they will apply to the command-line file or
3718        the local initialization file.  Otherwise, execution of a command in the
3719        global file would reset the switches before the second file is executed.
3720 
3721     3. The invocations of the global and command/simulator files are considered
3722        to be separate executions.  Consequently, an ABORT in the global file
3723        terminates that execution but does not affect execution of the
3724        command/simulator file.
3725 
3726     4. If the simulator was invoked with command-line parameters, we pass them
3727        to the global initialization file.  So, for example, "%1" will be the
3728        command filename that will be executed, "%2" will be the first parameter
3729        passed to that file, etc.  In this case, the "flag" parameter will be 0.
3730        If we are called to execute the simulator-specific initialization file,
3731        then there must not have been any command-line parameters, and "flag"
3732        will be -1.
3733 
3734     5. We use a filename buffer of twice the standard size, because the home
3735        path and the command-line parameter string may each be up to almost a
3736        full standard buffer size in length.
3737 */
3738 
ex_do_cmd(int32 flag,char * cptr)3739 static t_stat ex_do_cmd (int32 flag, char *cptr)
3740 {
3741 static t_bool first_call = TRUE;                        /* TRUE if this is the first DO call of the session */
3742 SIG_HANDLER   prior_handler;
3743 t_stat        status;
3744 int32         entry_switches;
3745 char          separator, filename [CBUFSIZE * 2], *home;
3746 
3747 prior_handler = signal (SIGINT, wru_handler);           /* install our WRU handler in place of the current one */
3748 
3749 if (prior_handler == SIG_ERR)                           /* if installation failed */
3750     status = SCPE_SIGERR;                               /*   then report an error */
3751 
3752 else {                                                  /* otherwise */
3753     if (first_call) {                                   /*   if this is the startup call */
3754         first_call = FALSE;                             /*     then clear the flag for subsequent calls */
3755         entry_switches = sim_switches;                  /*       and save the command-line switches */
3756 
3757         strcpy (filename, "simh.ini ");                 /* start with the filename in the working directory */
3758 
3759         if (flag == 0)                                  /* if command-line parameters were specified */
3760             strcat (filename, cptr);                    /*   then append them to the filename */
3761 
3762         status = ex_do_handler (-1, filename);          /* try to execute the global startup file */
3763 
3764         if (status == SCPE_OPENERR) {                   /* if it was not found */
3765             home = getenv ("HOME");                     /*   then get the home directory */
3766 
3767             if (home == NULL)                           /* if it's not defined */
3768                 home = getenv ("USERPROFILE");          /*   then try the profile directory */
3769 
3770             if (home != NULL) {                             /* if it's defined */
3771                 separator = home [strcspn (home, "/\\")];   /*   then look for a directory separator */
3772 
3773                 if (separator == '\0')                  /* if there isn't one */
3774                     separator = '/';                    /*   then guess that "/" will do */
3775 
3776                 sprintf (filename, "%s%csimh.ini %s",   /* form the home path and global filename */
3777                          home, separator,               /*   and add any command-line parameters */
3778                          (flag == 0 ? cptr : ""));      /*     if they are present */
3779 
3780                 status = ex_do_handler (-1, filename);  /* try to execute the global startup file */
3781                 }
3782             }
3783 
3784         sim_switches = entry_switches;                  /* restore the entry switches */
3785         }
3786 
3787     status = execute_file (NULL, flag, cptr);           /* execute the indicated command file */
3788 
3789     if (status == SCPE_ABORT && flag <= 1)              /* if an abort occurred and we're at the outermost level */
3790         status = SCPE_OK;                               /*   then clear the error to suppress the abort message */
3791 
3792     signal (SIGINT, prior_handler);                     /* restore the prior handler */
3793     }
3794 
3795 return status;                                          /* return the command status */
3796 }
3797 
3798 
3799 /* Execute the IF command.
3800 
3801    This command processing routine adds a new IF command to test a condition and
3802    execute the associated command(s) if the condition is true.  The routine
3803    processes commands of the form:
3804 
3805      IF { -I } <comparative-expression> <action> { ; <action> ... }
3806 
3807    Where the comparative expression forms are:
3808 
3809      <Boolean-expression>
3810      <Boolean-expression> <logical> <comparative-expression>
3811 
3812    ...and the Boolean expression forms are:
3813 
3814      <quoted-string> <equality> <quoted-string>
3815      <quoted-string> IN <quoted-string> { , <quoted-string> ...}
3816      <quoted-string> NOT IN <quoted-string> { , <quoted-string> ...}
3817      EXIST <quoted-string>
3818      NOT EXIST <quoted-string>
3819 
3820    The logical operators are && (And) and || (Or).  The equality operators are
3821    == (equal to) and != (not equal to).
3822 
3823    The IN operation returns true if the first quoted string is equal to any of
3824    the listed quoted strings, and the NOT IN operation returns true if the first
3825    quoted string is not equal to any of the listed strings.
3826 
3827    The EXIST operation returns true if the file specified by the quoted string
3828    exists.  The NOT EXIST operation returns true if the file does not exist.
3829 
3830    If the comparative expression is true, the associated actions are executed.
3831    If the expression is false, the actions have no effect.  Adding the "-I"
3832    switch causes the comparisons to be made case-insensitively.  Evaluation is
3833    strictly from left to right; embedded parentheses to change the evaluation
3834    order are not accepted.
3835 
3836    Typically, one quoted-string is a percent-enclosed substitution variable and
3837    the other is a literal string.  Comparisons are always textual, so, for
3838    example, "3" is not equal to "03".
3839 
3840 
3841    Implementation notes:
3842 
3843     1. For a true comparison, the action part of the IF command line is copied
3844        to a temporary buffer, and the break action pointer is set to point at
3845        the buffer.  This is done instead of simply setting "sim_brk_act" to
3846        "cptr" because the "memcpy" and "strncpy" functions that are used to copy
3847        each command into the command buffer produce implementation-defined
3848        results if the buffers overlap ("cptr" points into the command buffer, so
3849        "sim_brk_act" would be copying a command within the buffer to the start
3850        of the same buffer).
3851 
3852     2. An IF command may appear as an action within a breakpoint command or
3853        another IF command.  To support this, any actions of a true IF command
3854        are prefixed to any remaining breakpoint or IF actions by concatenating
3855        the two sets of actions in a temporary buffer.  Were this not done, the
3856        remaining actions would be lost when "sim_brk_act" is pointed at the new
3857        IF actions.
3858 
3859     3. Any unexecuted actions must be copied to a separate buffer before
3860        prefixing, as they will reside in the same buffer that will hold the
3861        prefix if they are the result of a prior IF command.
3862 */
3863 
3864 typedef enum {                                  /* test operators */
3865     Comparison,                                 /*   == or != operator */
3866     Existence,                                  /*   EXIST or NOT EXIST operator */
3867     Inclusion                                   /*   IN or NOT IN operator */
3868     } TEST_OP;
3869 
3870 typedef enum {                                  /* logical operators */
3871     Assign,                                     /*   null operator */
3872     And,                                        /*   AND operator */
3873     Or                                          /*   OR operator */
3874     } LOGICAL_OP;
3875 
ex_if_cmd(int32 flag,char * cptr)3876 static t_stat ex_if_cmd (int32 flag, char *cptr)
3877 {
3878 static char tempbuf [CBUFSIZE];
3879 struct stat statbuf;
3880 int         result, condition = 0;                      /* silence spurious gcc-9.2.0 compiler warning */
3881 char        abuf [CBUFSIZE], bbuf [CBUFSIZE], *tptr;
3882 int32       bufsize;
3883 t_bool      upshift, invert, decode;
3884 TEST_OP     test;
3885 LOGICAL_OP  logical = Assign;
3886 t_bool      not_done = TRUE;                            /* TRUE if more comparisons are present */
3887 
3888 cptr = get_sim_sw (cptr);                               /* get a possible case-sensitivity switch */
3889 
3890 if (cptr == NULL)                                       /* if an invalid switch was present */
3891     return SCPE_INVSW;                                  /*   then report it */
3892 
3893 else if (*cptr == '\0')                                 /* otherwise if the first operand is missing */
3894     return SCPE_2FARG;                                  /*   then report it */
3895 
3896 upshift = (sim_switches & SWMASK ('I')) != 0;           /* TRUE if the comparison is case-insensitive */
3897 decode  = (sim_switches & SWMASK ('E')) != 0;           /* TRUE if escape sequences should be decoded */
3898 
3899 do {                                                    /* loop until all conditionals are processed */
3900     test = Comparison;                                  /* assume a comparison until proven otherwise */
3901 
3902     if (*cptr == '\'' || *cptr == '"') {                /* if a quoted string is present */
3903         cptr = parse_quoted_string (cptr, abuf,         /*   then parse the first operand */
3904                                     upshift, decode);   /*     with upshifting and decoding as directed */
3905 
3906         if (cptr == NULL)                               /* if the operand isn't quoted properly */
3907             return SCPE_ARG;                            /*   then report a bad argument */
3908 
3909         else if (*cptr == '\0')                         /* otherwise if the operator is missing */
3910             return SCPE_2FARG;                          /*   then report it */
3911 
3912         else {                                          /* otherwise */
3913             cptr = get_glyph (cptr, bbuf, 0);           /*   parse the next token */
3914 
3915             if (strcmp (bbuf, "==") == 0)               /* if the operator is "equal to" */
3916                 invert = FALSE;                         /*   then we want a true test */
3917 
3918             else if (strcmp (bbuf, "!=") == 0)          /* otherwise if the operator is "not equal to" */
3919                 invert = TRUE;                          /*   then we want an inverted test */
3920 
3921             else {                                      /* otherwise */
3922                 invert = (strcmp (bbuf, "NOT") == 0);   /*   a NOT operator inverts the result */
3923 
3924                 if (invert)                             /* if it was NOT */
3925                     cptr = get_glyph (cptr, bbuf, 0);   /*   then get another token */
3926 
3927                 if (strcmp (bbuf, "IN") == 0) {         /* if it is IN */
3928                     test = Inclusion;                   /*   then this is a membership test */
3929                     result = invert;                    /*     so set the initial matching condition */
3930                     }
3931 
3932                 else                                    /* otherwise the operator is invalid */
3933                     return SCPE_ARG;                    /*   so report it */
3934                 }
3935             }
3936         }
3937 
3938     else {                                              /* otherwise it may be a unary operator */
3939         cptr = get_glyph (cptr, abuf, 0);               /*   so get the next token */
3940 
3941         invert = (strcmp (abuf, "NOT") == 0);           /* a NOT operator inverts the result */
3942 
3943         if (invert)                                     /* if it was NOT */
3944             cptr = get_glyph (cptr, abuf, 0);           /*   then get another token */
3945 
3946         if (strcmp (abuf, "EXIST") == 0)                /* if it is EXIST */
3947             test = Existence;                           /*   then this is a file existence check */
3948         else                                            /* otherwise */
3949             return SCPE_ARG;                            /*   the operator is unknown */
3950         }
3951 
3952     do {                                                /* loop for membership tests */
3953         if (*cptr != '\'' && *cptr != '"')              /* if a quoted string is not present */
3954             return SCPE_ARG;                            /*   then report a bad argument */
3955 
3956         cptr = parse_quoted_string (cptr, bbuf,                     /* if a quoted string is present */
3957                                     upshift && test != Existence,   /*   then parse the second operand */
3958                                     decode);                        /*     with upshifting and decoding as directed */
3959 
3960         if (cptr == NULL)                               /* if the operand isn't properly quoted */
3961             return SCPE_ARG;                            /*   then report a bad argument */
3962 
3963         else if (test == Inclusion) {                   /* otherwise if this is a membership test */
3964             if (invert)                                 /*   then */
3965                 result &= (strcmp (abuf, bbuf) != 0);   /*     AND an exclusive check */
3966             else                                        /*   otherwise */
3967                 result |= (strcmp (abuf, bbuf) == 0);   /*     OR an inclusive check */
3968 
3969             if (*cptr == ',')                           /* if the membership list continues */
3970                 while (isspace (*++cptr));              /*   then discard the comma and any trailing spaces */
3971             else                                        /* otherwise */
3972                 test = Comparison;                      /*   exit the membership loop */
3973             }
3974 
3975         else if (test == Existence)                         /* otherwise if this is an existence check */
3976             result = (stat (bbuf, &statbuf) == 0) ^ invert; /*   then test the filename */
3977 
3978         else                                                /* otherwise compare the operands */
3979             result = (strcmp (abuf, bbuf) == 0) ^ invert;   /*   with the appropriate test */
3980         }
3981     while (test == Inclusion);                          /* continue if additional members are present */
3982 
3983     switch (logical) {                                  /* apply the logical operator */
3984         case Assign:                                    /* for a null operator */
3985             condition = result;                         /*   use the condition directly */
3986             break;
3987 
3988         case And:                                       /* for a logical AND operator */
3989             condition = condition & result;             /*   AND the two results */
3990             break;
3991 
3992         case Or:                                        /* for a logical OR operator */
3993             condition = condition | result;             /*   OR the two results */
3994             break;
3995         }                                               /* all cases are handled */
3996 
3997     if (*cptr == '\0')                                  /* if the rest of the command is missing */
3998         return SCPE_2FARG;                              /*   then report it */
3999 
4000     else if (strncmp (cptr, "&&", 2) == 0) {            /* otherwise if an AND operator is present */
4001         logical = And;                                  /*   then record it */
4002         cptr++;                                         /*     and skip over it and continue */
4003         }
4004 
4005     else if (strncmp (cptr, "||", 2) == 0) {            /* otherwise if an OR operator is present */
4006         logical = Or;                                   /*   then record it */
4007         cptr++;                                         /*     and skip over it and continue */
4008         }
4009 
4010     else {                                              /* otherwise */
4011         not_done = FALSE;                               /*   this is the end of the condition */
4012         cptr--;                                         /*     so back up to point at the action string */
4013         }
4014 
4015     while (isspace (*++cptr));                          /* discard any trailing spaces */
4016     }
4017 while (not_done);                                       /* continue to process logical comparisons until done */
4018 
4019 if (condition)                                          /* if the comparison is true */
4020     if (sim_brk_act == NULL)                            /*   then if no unexecuted actions remain */
4021         sim_brk_act = strcpy (tempbuf, cptr);           /*     then copy our action string to a buffer */
4022 
4023     else {                                              /* otherwise */
4024         strcpy (abuf, sim_brk_act);                     /*   save the unexecuted actions in a separate buffer */
4025 
4026         tptr = tempbuf;                                 /* point at the action buffer */
4027         bufsize = CBUFSIZE;                             /*   and initialize the remaining size */
4028 
4029         copy_string (&tptr, &bufsize, cptr, 0);         /* copy the current actions as a prefix */
4030 
4031         if (bufsize > 1) {                              /* if space remains */
4032             *tptr++ = ';';                              /*   then append the action separator */
4033             bufsize = bufsize - 1;                      /*     and account for the space */
4034 
4035             copy_string (&tptr, &bufsize, abuf, 0);     /* copy the unexecuted actions */
4036             }
4037 
4038         sim_brk_act = tempbuf;                          /* point at the concatenated action list */
4039         }
4040 
4041 return SCPE_OK;                                         /* either way, the command succeeded */
4042 }
4043 
4044 
4045 /* Execute the DELETE command.
4046 
4047    This command processing routine adds a new DELETE command to delete the
4048    specified file.  The routine processes commands of the form:
4049 
4050      DELETE <filename>
4051 
4052    It provides a platform-independent way to delete files from a command file
4053    (e.g., temporary files created by a diagnostic program).
4054 */
4055 
ex_delete_cmd(int32 flag,char * cptr)4056 static t_stat ex_delete_cmd (int32 flag, char *cptr)
4057 {
4058 if (*cptr == '\0')                                      /* if the filename is missing */
4059     return SCPE_2FARG;                                  /*   then report it */
4060 
4061 else if (remove (cptr) == 0)                            /* otherwise if the delete succeeds */
4062     return SCPE_OK;                                     /*   then return success */
4063 
4064 else                                                    /* otherwise */
4065     return SCPE_OPENERR;                                /*   report that the file could not be opened */
4066 }
4067 
4068 
4069 /* Execute the FLUSH command.
4070 
4071    This command processing routine adds a new FLUSH command to flush all
4072    buffered files to disc.  The routine processes commands of the form:
4073 
4074      FLUSH
4075 
4076    For files that are open, the command flushes the console and debug log files,
4077    the files attached to all of the units of all devices, and log files
4078    associated with terminal multiplexer lines.
4079 
4080    The command provides a way to flush buffered file contents to disc without
4081    having to stop and restart simulated execution.  As such, it is useful when
4082    the console is in concurrent mode.  In nonconcurrent mode, stopping the
4083    simulator to enter the FLUSH command will automatically flush all open files,
4084    so the command is redundant in that case.
4085 */
4086 
ex_flush_cmd(int32 flag,char * cptr)4087 static t_stat ex_flush_cmd (int32 flag, char *cptr)
4088 {
4089 if (*cptr != '\0')                                      /* if something follows */
4090     return SCPE_2MARG;                                  /*   then report extraneous characters */
4091 
4092 else {                                                  /* otherwise */
4093     fflush (NULL);                                      /*   flush all open files */
4094     return SCPE_OK;
4095     }
4096 }
4097 
4098 
4099 /* Execute the PAUSE command.
4100 
4101    This command processing routine adds a new PAUSE command that pauses command
4102    file execution by a specified time interval.  The routine processes commands
4103    of the form:
4104 
4105      PAUSE <time> <units>
4106 
4107    The <time> is an unsigned decimal count, and the <units> may be
4108    MICROSECOND[S], MILLISECOND[S], or SECOND[S]; the abbreviations USEC[S] and
4109    MSEC[S] may be used interchangeably for the first two units, respectively.
4110    Time intervals less than one millisecond are rejected with an "Invalid
4111    argument" error.
4112 
4113    To permit the user to abort the PAUSE command, waits of up to 100
4114    milliseconds each are performed in a loop.  At each pass through the loop,
4115    the keyboard is polled for a CTRL+E, and the wait is abandoned if one if
4116    seen.  If the user aborts the wait, "Command not completed" status is
4117    returned.  Otherwise, the command completes with SCPE_OK if the pause
4118    extended for the full time interval.
4119 
4120 
4121    Implementation notes:
4122 
4123     1. UNIX systems do not pass CTRL+E through the keyboard interface but
4124        instead signal SIGINT.  To catch these, a SIGINT handler is installed
4125        that sets the "stop_requested" variable TRUE if it is called.  The
4126        variable is checked in the loop, and the pause is abandoned if it is set.
4127 
4128     2. The console must be changed to non-blocking mode in order to obtain the
4129        CTRL+E keystroke without requiring a newline terminator.  Bracketing
4130        calls to "sim_ttrun" and "sim_ttcmd" are used to do this, even though
4131        they also drop the simulator priority, which is unnecessary.
4132 */
4133 
ex_pause_cmd(int32 flag,char * cptr)4134 static t_stat ex_pause_cmd (int32 flag, char *cptr)
4135 {
4136 const int32 interval = 100;                     /* number of milliseconds between CTRL+E checks */
4137 t_stat      status;
4138 int32       pause;
4139 SIG_HANDLER prior_handler;
4140 
4141 status = parse_time (NULL, &cptr, &pause);              /* parse the time interval */
4142 
4143 if (status == SCPE_OK)                                  /* if the parse succeeded */
4144     if (pause == 0)                                     /*   but the time is too short */
4145         status = SCPE_ARG;                              /*     then report that the argument is invalid */
4146 
4147     else {                                              /*   otherwise */
4148         stop_requested = FALSE;                         /*     clear any prior SIGINT stop */
4149 
4150         prior_handler = signal (SIGINT, wru_handler);   /* install our WRU handler in place of the current one */
4151 
4152         if (prior_handler == SIG_ERR)                   /* if installation failed */
4153             status = SCPE_SIGERR;                       /*   then report an error */
4154 
4155         else {                                          /* otherwise */
4156             status = sim_ttrun ();                      /*   switch the console to non-blocking mode */
4157 
4158             if (status != SCPE_OK)                      /* if the switch failed */
4159                return status;                           /*   then report the error and quit */
4160 
4161             do {                                        /* otherwise sleep for the specified time */
4162                 sim_os_ms_sleep (pause < interval       /*    but no longer than the maximum time between checks */
4163                                    ? pause : interval);
4164 
4165                 if (not stop_requested) {               /* if SIGINT was not signaled */
4166                     status = sim_os_poll_kbd ();        /*   then check for a CTRL+E keypress */
4167 
4168                     if (status != SCPE_STOP)            /* if there was none */
4169                         pause = pause - interval;       /*   then drop the remaining time by the time waited */
4170                     }
4171                 }
4172             while (not (pause <= 0                      /* continue to wait until the pause expires */
4173               || stop_requested                         /*   or the pause is aborted by the user */
4174               || status == SCPE_STOP));                 /*     or CTRL+E is entered */
4175 
4176             if (pause > 0)                              /* if the wait was aborted by the user */
4177                 status = SCPE_INCOMP;                   /*   then report that the command did not complete */
4178             else                                        /* otherwise */
4179                 status = SCPE_OK;                       /*   report success */
4180 
4181             sim_ttcmd ();                               /* restore the console to blocking mode */
4182             }
4183 
4184         if (status != SCPE_SIGERR)                      /* if the signal handler was set up properly */
4185             signal (SIGINT, prior_handler);             /*   then restore the prior handler */
4186         }
4187 
4188 return status;                                          /* return the result */
4189 }
4190 
4191 
4192 /* Execute a restricted command.
4193 
4194    This command processing routine is called when the user attempts to execute
4195    from the command line a command that is restricted to command files.
4196    Commands such as GOTO have no meaning when executed interactively, so we
4197    simply return "Command not allowed" status here.
4198 */
4199 
ex_restricted_cmd(int32 flag,char * ptr)4200 static t_stat ex_restricted_cmd (int32 flag, char *ptr)
4201 {
4202 return SCPE_NOFNC;                                      /* the command is not allowed interactively */
4203 }
4204 
4205 
4206 /* Execute the SET command.
4207 
4208    This command processing routine enhances the existing SET command to add
4209    setting environment variables and to extend console modes to include
4210    concurrent command execution and serial port support.  The routine processes
4211    commands of the form:
4212 
4213      SET ENVIRONMENT ...
4214      SET CONSOLE ...
4215 
4216    The other SET commands are handled by the standard handler.
4217 */
4218 
4219 static CTAB ex_set_table [] = {                 /* the SET extension table */
4220     { "ENVIRONMENT", &ex_set_environment, 0 },  /*   SET ENVIRONMENT */
4221     { "CONSOLE",     &ex_set_console,     0 },  /*   SET CONSOLE */
4222     { NULL,          NULL,                0 }
4223     };
4224 
ex_set_cmd(int32 flag,char * cptr)4225 static t_stat ex_set_cmd (int32 flag, char *cptr)
4226 {
4227 char   *tptr, gbuf [CBUFSIZE];
4228 CTAB   *cmdp;
4229 
4230 tptr = get_glyph (cptr, gbuf, 0);                       /* get the SET target */
4231 
4232 cmdp = find_ctab (ex_set_table, gbuf);                  /* find the associated command handler */
4233 
4234 if (cmdp == NULL)                                       /* if the target is not one of ours */
4235     return set_handler (flag, cptr);                    /*   then let the base handler process it */
4236 else                                                    /* otherwise */
4237     return cmdp->action (cmdp->arg, tptr);              /*   call our handler */
4238 }
4239 
4240 
4241 /* Execute the SHOW command.
4242 
4243    This command processing routine enhances the existing SHOW command to add
4244    pending string breakpoint and reply displays and to extend console modes to
4245    display the concurrent command execution mode.  The routine processes
4246    commands of the form:
4247 
4248      SHOW BREAK ...
4249      SHOW REPLY ...
4250      SHOW DELAYS
4251      SHOW CONSOLE ...
4252 
4253    The other SHOW commands are handled by the standard handler.
4254 */
4255 
4256 static SHTAB ex_show_table [] = {               /* the SHOW extension table */
4257     { "BREAK",   &ex_show_break,   0 },         /*   SHOW BREAK */
4258     { "REPLY",   &ex_show_reply,   0 },         /*   SHOW REPLY */
4259     { "DELAYS",  &ex_show_delays,  0 },         /*   SHOW DELAYS */
4260     { "CONSOLE", &ex_show_console, 0 },         /*   SHOW CONSOLE */
4261     { NULL,      NULL,             0 }
4262     };
4263 
ex_show_cmd(int32 flag,char * cptr)4264 static t_stat ex_show_cmd (int32 flag, char *cptr)
4265 {
4266 char   *tptr, gbuf [CBUFSIZE];
4267 SHTAB  *cmdp;
4268 t_stat status;
4269 
4270 cptr = get_sim_sw (cptr);                               /* get any command-line switches */
4271 
4272 if (cptr == NULL)                                       /* if an invalid switch was present */
4273     return SCPE_INVSW;                                  /*   then report it */
4274 
4275 else {                                                  /* otherwise */
4276     tptr = get_glyph (cptr, gbuf, 0);                   /*   get the SHOW target */
4277 
4278     cmdp = find_shtab (ex_show_table, gbuf);            /* find the associated command handler */
4279 
4280     if (cmdp == NULL)                                   /* if the target is not one of ours */
4281         return show_handler (flag, cptr);               /*   then let the base handler process it */
4282 
4283     else {                                              /* otherwise */
4284         status = cmdp->action (stdout, NULL, NULL,      /*   report the option on the console */
4285                                cmdp->arg, tptr);
4286 
4287         if (sim_log != NULL)                            /* if a console log is defined */
4288             cmdp->action (sim_log, NULL, NULL,          /*   then report again on the log file */
4289                           cmdp->arg, tptr);
4290         }
4291 
4292     return status;                                      /* return the command status */
4293     }
4294 }
4295 
4296 
4297 /* Execute the SET ENVIRONMENT command.
4298 
4299    This command processing routine adds a new SET ENVIRONMENT command to create,
4300    set, and clear variables in the host system's environment.  The routine
4301    processes commands of the form:
4302 
4303      SET ENVIRONMENT <name>=<value>
4304      SET ENVIRONMENT <name>=
4305 
4306    ...where <name> is the name of the variable, and value is the (unquoted)
4307    string value to be set.  If the value is missing, the variable is cleared.
4308    Legal names and values are host-system dependent.
4309 
4310 
4311    Implementation notes:
4312 
4313     1. MSVC does not offer the "setenv" function, so we use their "_putenv"
4314        function instead.  However, that function takes the full "name=value"
4315        string, rather than separate name and value parameters.
4316 
4317     2. We explicitly check for an equals sign before calling "get_glyph" because
4318        while that function will separate the name and value correctly at the
4319        equals sign, it will also allow the separation character to be a space.
4320        That would be confusing; for example, "SET ENV a b c" would set variable
4321        "a" to value "b c".
4322 
4323     3. While the 4.x version of the SET ENVIRONMENT processor calls "get_glyph",
4324        which forces all variable names to uppercase, the POSIX standard allows
4325        lowercase environment variable names and requires them to be distinct
4326        from uppercase names.  This can lead to unexpected behavior on POSIX
4327        systems.  Therefore, we call "get_glyph_nc" to preserve the case of the
4328        variable name.  Note that Windows treats environment variable names
4329        case-insensitively, so this change only affects POSIX systems.
4330 */
4331 
ex_set_environment(int32 flag,char * cptr)4332 static t_stat ex_set_environment (int32 flag, char *cptr)
4333 {
4334 int  result;
4335 char *bptr;
4336 
4337 if (*cptr == '\0')                                      /* if no name is present */
4338     return SCPE_2FARG;                                  /*   then report a missing argument */
4339 
4340 bptr = cptr + strlen (cptr);                            /* point at the end of the string */
4341 
4342 while (isspace (*--bptr))                               /* if trailing spaces exist */
4343     *bptr = '\0';                                       /*   then remove them */
4344 
4345 #if defined (_MSC_VER)
4346 
4347 result = _putenv (cptr);                                /* enter the equate into the environment */
4348 
4349 #else
4350 
4351 if (cptr [strcspn (cptr, "= ")] != '=')                 /* if there's no equals sign */
4352     result = -1;                                        /*   then report a bad argument */
4353 
4354 else {                                                  /* otherwise */
4355     char vbuf [CBUFSIZE];                               /*   declare a buffer to hold the variable name */
4356 
4357     bptr = get_glyph_nc (cptr, vbuf, '=');              /* split the variable name and value, preserving case */
4358     result = setenv (vbuf, bptr, 1);                    /*   and enter the equate into the environment */
4359     }
4360 
4361 #endif
4362 
4363 if (result == 0)                                        /* if the assignment succeeds */
4364     return SCPE_OK;                                     /*   then report success */
4365 else                                                    /* otherwise */
4366     return SCPE_ARG;                                    /*   report a bad argument */
4367 }
4368 
4369 
4370 /* Execute the SET CONSOLE command.
4371 
4372    This command processing routine enhances the existing SET CONSOLE command to
4373    add configuration for concurrent command execution and serial port support.
4374    The routine processes commands of the form:
4375 
4376      SET CONSOLE CONCURRENT
4377      SET CONSOLE NOCONCURRENT
4378      SET CONSOLE SERIAL ...
4379      SET CONSOLE NOSERIAL
4380 
4381    It also intercepts the SET CONSOLE TELNET command to close an existing serial
4382    connection if a Telnet connection is being established.
4383 
4384    Because the SET CONSOLE command accepts a comma-separated set of options, we
4385    must parse each option and decide whether it is a standard option or an
4386    extension option.
4387 
4388 
4389    Implementation notes:
4390 
4391     1. We parse each option without case conversion and then separate the option
4392        name from its value, if present, with conversion to upper case.  This is
4393        necessary because the option name must be uppercase to match the command
4394        table, but the option value might be case-sensitive, such as a serial
4395        port name.
4396 
4397     2. The SET CONSOLE LOG/NOLOG and SET CONSOLE DEBUG/NODEBUG commands print
4398        messages to report logging initiation or completion.  When executing
4399        command files, we normally suppress messages by setting "sim_quiet" to 1.
4400        However, the console messages are useful even in a command-file setting,
4401        so we restore the saved setting before calling the SET processor.  This
4402        is redundant but causes no harm if a command file is not executing.
4403 */
4404 
4405 static CTAB set_console_table [] = {            /* the SET CONSOLE extension table */
4406     { "CONCURRENT",   &ex_set_concurrent, 1 },  /*   SET CONSOLE CONCURRENT */
4407     { "NOCONCURRENT", &ex_set_concurrent, 0 },  /*   SET CONSOLE NOCONCURRENT */
4408     { "SERIAL",       &ex_set_serial,     1 },  /*   SET CONSOLE SERIAL */
4409     { "NOSERIAL",     &ex_set_serial,     0 },  /*   SET CONSOLE NOSERIAL */
4410     { "TELNET",       &ex_set_serial,     2 },  /*   SET CONSOLE TELNET */
4411     { NULL,           NULL,               0 }
4412     };
4413 
ex_set_console(int32 flag,char * cptr)4414 static t_stat ex_set_console (int32 flag, char *cptr)
4415 {
4416 char   *tptr, gbuf [CBUFSIZE], cbuf [CBUFSIZE];
4417 CTAB   *cmdp;
4418 t_stat status = SCPE_OK;
4419 
4420 sim_quiet = ex_quiet;                                   /* restore the global quiet setting */
4421 
4422 if (cptr == NULL || *cptr == '\0')                      /* if no options follow */
4423     return SCPE_2FARG;                                  /*   then report them as missing */
4424 
4425 else while (*cptr != '\0') {                            /* otherwise loop through the argument list */
4426     cptr = get_glyph_nc (cptr, gbuf, ',');              /* get the next argument without altering case */
4427     tptr = get_glyph (gbuf, cbuf, '=');                 /*   and then just the option name in upper case */
4428 
4429     cmdp = find_ctab (set_console_table, cbuf);         /* get the associated command handler */
4430 
4431     if (cmdp == NULL)                                   /* if the target is not one of ours */
4432         status = sim_set_console (flag, gbuf);          /*   then let the base handler process it */
4433     else                                                /* otherwise */
4434         status = cmdp->action (cmdp->arg, tptr);        /*   call our handler */
4435 
4436     if (status != SCPE_OK)                              /* if the command failed */
4437         break;                                          /*   then bail out now */
4438     }
4439 
4440 return status;                                          /* return the resulting status */
4441 }
4442 
4443 
4444 /* Execute the SET CONSOLE CONCURRENT/NOCONCURRENT commands.
4445 
4446    This command processing routine adds new SET CONSOLE [NO]CONCURRENT commands
4447    to enable or disable concurrent command mode.  The routine processes commands
4448    of the form:
4449 
4450      SET CONSOLE CONCURRENT
4451      SET CONSOLE NOCONCURRENT
4452 
4453    The mode is enabled if the "flag" parameter is 1 and disabled if it is 0.
4454 */
4455 
ex_set_concurrent(int32 flag,char * cptr)4456 static t_stat ex_set_concurrent (int32 flag, char *cptr)
4457 {
4458 if (flag == 1)                                          /* if this is the CONCURRENT option */
4459     concurrent_mode = TRUE;                             /*   then enable concurrent mode */
4460 else                                                    /* otherwise */
4461     concurrent_mode = FALSE;                            /*   disable concurrent mode */
4462 
4463 return SCPE_OK;
4464 }
4465 
4466 
4467 /* Execute the SET CONSOLE SERIAL/NOSERIAL commands.
4468 
4469    This command processing routine adds new SET CONSOLE [NO]SERIAL commands to
4470    connect or disconnect the console to or from a serial port.  The routine
4471    processes commands of the form:
4472 
4473      SET CONSOLE SERIAL=<port>[;<configuration>]
4474      SET CONSOLE NOSERIAL
4475 
4476    On entry, the "flag" parameter is set to 1 to connect or 0 to disconnect.  If
4477    connecting, the "cptr" parameter points to the serial port name and optional
4478    configuration string.  If present, the configuration string must be separated
4479    from the port name with a semicolon and has this form:
4480 
4481       <rate>-<charsize><parity><stopbits>
4482 
4483    where:
4484 
4485      rate     = communication rate in bits per second
4486      charsize = character size in bits (5-8, including optional parity)
4487      parity   = parity designator (N/E/O/M/S for no/even/odd/mark/space parity)
4488      stopbits = number of stop bits (1, 1.5, or 2)
4489 
4490    As an example:
4491 
4492      SET CONSOLE SERIAL=com1;9600-8n1
4493 
4494    The supported rates, sizes, and parity options are host-specific.  If a
4495    configuration string is not supplied, then host system defaults for the
4496    specified port are used.
4497 
4498    This routine is also called for the SET CONSOLE TELNET command with the
4499    "flag" parameter set to 2.  In this case, we check for a serial connection
4500    and perform an automatic SET CONSOLE NOSERIAL first before calling the
4501    standard command handler to attach the Telnet port.
4502 */
4503 
ex_set_serial(int32 flag,char * cptr)4504 static t_stat ex_set_serial (int32 flag, char *cptr)
4505 {
4506 t_stat status;
4507 
4508 if (flag == 2) {                                        /* if this is a SET CONSOLE TELNET command */
4509     if (serial_line (sim_con_tmxr.ldsc) != NULL)        /*   then if a serial connection exists */
4510         ex_tmxr_detach_line (&sim_con_tmxr, NULL);      /*     then detach the serial port first */
4511 
4512     status = sim_set_telnet (flag, cptr);               /* call the base handler to set the Telnet connection */
4513     }
4514 
4515 else if (flag == 1) {                                   /* otherwise if this is a SET CONSOLE SERIAL command */
4516     sim_set_notelnet (flag, NULL);                      /*   then detach any existing Telnet connection first */
4517 
4518     if (serial_line (sim_con_tmxr.ldsc) != NULL)        /* if already connected to a serial port */
4519         status = SCPE_ALATT;                            /*   then reject the command */
4520 
4521     else {                                              /* otherwise */
4522         status = ex_tmxr_attach_line (&sim_con_tmxr,    /*   try to attach the serial port */
4523                                       NULL, cptr);
4524 
4525         if (status == SCPE_OK) {                        /* if the attach succeeded */
4526             ex_tmxr_poll_conn (&sim_con_tmxr);          /*   then poll to complete the connection */
4527             sim_con_tmxr.ldsc [0].rcve = 1;             /*     and enable reception */
4528             }
4529         }
4530     }
4531 
4532 else {                                                  /* otherwise this is a SET CONSOLE NOSERIAL command */
4533     status = ex_tmxr_detach_line (&sim_con_tmxr, NULL); /*   so detach the serial port */
4534     sim_con_tmxr.ldsc [0].rcve = 0;                     /*     and disable reception */
4535     }
4536 
4537 return status;                                          /* return the command status */
4538 }
4539 
4540 
4541 /* Execute the SHOW BREAK command.
4542 
4543    This command processing routine enhances the existing SHOW BREAK command to
4544    display string breakpoints.  The routine processes commands of the form:
4545 
4546      SHOW { <types> } BREAK { ALL | <address-list> }
4547 
4548    Where:
4549 
4550      <types> = a dash and one or more letters denoting breakpoint types
4551 
4552    String breakpoints are displayed in this form:
4553 
4554      <unit>:  <types> <quoted-string> { DELAY <delay> } { ; <action> ... }
4555 
4556    ...and are displayed only if the <address-list> is omitted and the <types>
4557    are either omitted or matches the type of the string breakpoint. In practice,
4558    this means that string breakpoints could be displayed for these commands:
4559 
4560      SHOW BREAK { ALL }
4561      SHOW -T BREAK { ALL }
4562      SHOW -T <other-types> BREAK { ALL }
4563 
4564    Persistent string breakpoints are displayed only with the first form.  All
4565    three forms will display temporary string breakpoints.  But a SHOW -N BREAK
4566    command would not display any string breakpoints because none have an "N"
4567    type.
4568 
4569    The ALL keyword is redundant but is accepted for compatibility with the
4570    standard SHOW BREAK command.
4571 
4572 
4573    Implementation notes:
4574 
4575     1. The matching string is stored in the breakpoint structure in decoded
4576        form, i.e., control characters are stored explicitly rather than as a
4577        character escape.  So the string must be encoded for display.
4578 */
4579 
ex_show_break(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4580 static t_stat ex_show_break (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4581 {
4582 char   gbuf [CBUFSIZE];
4583 int32  types;
4584 uint32 sw;
4585 t_bool sep;
4586 SBPTR  bp = sb_list;
4587 
4588 if (sim_switches == 0)                                  /* if no type switches were specified */
4589     types = BP_STRING;                                  /*   then match any string breakpoint */
4590 else                                                    /* otherwise */
4591     types = sim_switches;                               /*   match only the specified type(s) */
4592 
4593 get_glyph (cptr, gbuf, 0);                              /* parse the first option, if any */
4594 
4595 if (*cptr == '\0' || strcmp (gbuf, "ALL") == 0)         /* if no address list or ALL was specified */
4596     while (bp != NULL) {                                /*   then while string breakpoints exist */
4597         if (bp->type & types) {                         /*     then if breakpoint matches the type */
4598             fprintf (stream, "%s:\t",                   /*       then report the associated unit */
4599                              (bp->uptr ? sim_uname (bp->uptr) : "CONS"));
4600 
4601             sep = FALSE;                                /* no separator is needed to start */
4602 
4603             for (sw = 0; sw < 26; sw++)                 /* check the type letters */
4604                 if (bp->type >> sw & 1) {               /* if this type is indicated */
4605                     if (sep)                            /*   then if a separator is needed */
4606                         fprintf (stream, ", ");         /*     then output it first */
4607 
4608                     fputc (sw + 'A', stream);           /* output the type letter */
4609                     sep = TRUE;                         /*   and indicate that a separator will be needed */
4610                     }
4611 
4612             if (bp->count > 0)                          /* if the count is defined */
4613                 fprintf (stream, " [%d]", bp->count);   /*   then output it */
4614 
4615             fprintf (stream, "%s%s%s%.0d",              /* output the breakpoint */
4616                              (sep || bp->count > 0 ? " " : ""),
4617                              encode (bp->match),
4618                              (bp->delay ? " delay " : ""),
4619                              bp->delay);
4620 
4621             if (bp->action [0] != '\0')                 /* if actions are defined */
4622                 fprintf (stream, " ; %s", bp->action);  /*   then output them */
4623 
4624             fprintf (stream, "\n");                     /* terminate the line */
4625             }
4626 
4627         bp = bp->next;                                  /* move on to the next breakpoint */
4628         }
4629 
4630 return show_break (stream, dptr, uptr, flag, cptr);     /* let the base handler show any numeric breakpoints */
4631 }
4632 
4633 
4634 /* Execute the SHOW REPLY command.
4635 
4636    This command processing routine adds a new SHOW REPLY command to display
4637    pending replies.  The routine processes commands of the form:
4638 
4639      SHOW REPLY
4640 
4641    Replies are displayed in this form:
4642 
4643      <unit>:  <quoted-string> { DELAY <delay> }
4644 
4645    If a delay is present, then the value displayed is the remaining delay before
4646    the first character of the string is output.  If a delay was originally
4647    specified but is not displayed, then the reply is already underway.
4648 
4649 
4650    Implementation notes:
4651 
4652     1. The output string is stored in the reply structure in decoded form, i.e.,
4653        control characters are stored explicitly rather than as a character
4654        escape.  So the string must be encoded for display.
4655 */
4656 
ex_show_reply(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4657 static t_stat ex_show_reply (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4658 {
4659 int32 delay;
4660 RPPTR rp = rp_list;
4661 
4662 if (*cptr != '\0')                                      /* if something follows */
4663     return SCPE_2MARG;                                  /*   then report extraneous characters */
4664 
4665 else if (rp == NULL)                                    /* otherwise if no replies are pending */
4666     fprintf (stream, "No replies pending\n");           /*   then report it as such */
4667 
4668 else {                                                  /* otherwise report the replies */
4669     delay = (int32) (rp->trigger - sim_gtime ());       /* get the relative delay time */
4670 
4671     if (delay < 0)                                      /* if the reply has already started */
4672         delay = 0;                                      /*   then suppress reporting the delay */
4673 
4674     fprintf (stream, "%s:\t%s%s%.0d\n",                 /* display the reply */
4675             (rp->uptr ? sim_uname (rp->uptr) : "CONS"),
4676             encode (rp->reply),
4677             (delay ? " delay " : ""), delay);
4678     }
4679 
4680 return SCPE_OK;                                         /* report the success of the command */
4681 }
4682 
4683 
4684 /* Execute the SHOW DELAYS command.
4685 
4686    This command processing routine adds a new SHOW DELAYS command to display the
4687    global delay settings for string breakpoints and replies.  The routine
4688    processes commands of the form:
4689 
4690      SHOW DELAYS
4691 
4692    The delay values are reported in units of event ticks.
4693 */
4694 
ex_show_delays(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4695 static t_stat ex_show_delays (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4696 {
4697 if (*cptr != '\0')                                      /* if something follows */
4698     return SCPE_2MARG;                                  /*   then report extraneous characters */
4699 
4700 else {                                                      /* otherwise */
4701     fprintf (stream, "Break delay = %d\n", break_delay);    /*   report the break */
4702     fprintf (stream, "Reply delay = %d\n", reply_delay);    /*     and reply delays */
4703 
4704     return SCPE_OK;                                     /* return success */
4705     }
4706 }
4707 
4708 
4709 /* Execute the SHOW CONSOLE command.
4710 
4711    This command processing routine enhances the existing SHOW CONSOLE command to
4712    add configuration displays for concurrent command execution and serial port
4713    support.  The routine processes commands of the form:
4714 
4715      SHOW CONSOLE CONCURRENT
4716      SHOW CONSOLE SERIAL
4717 
4718    It also intercepts the SHOW CONSOLE TELNET command to convert it from a
4719    two-state report (i.e., connected to Telnet or console window) to a
4720    three-state report (Telnet, serial, or console window).
4721 
4722    Because the SHOW CONSOLE command accepts a comma-separated set of options, we
4723    must parse each option and decide whether it is a standard option or an
4724    extension option.
4725 
4726 
4727    Implementation notes:
4728 
4729     1. We parse each option without case conversion and then separate the option
4730        name from its value, if present, with conversion to upper case.  This is
4731        necessary because the option name must be uppercase to match the command
4732        table, but the option value might be case-sensitive, such as a serial
4733        port name.
4734 
4735     2. For the SHOW CONSOLE command with no specified options (i.e., SHOW ALL),
4736        we cannot simply call the standard "sim_show_console" routine to display
4737        the base set of values because it calls "sim_show_telnet", which displays
4738        "Connected to console window" if no Telnet connection exists.  This is
4739        incorrect if a serial connection exists.  Instead, we call that routine
4740        with a command line consisting of all options except the TELNET option.
4741        In that way, we get the base display and can then add our own line for
4742        the Telnet/serial/window connection.
4743 
4744        If the base set of options is changed, we must also change the "show_set"
4745        value below to match.
4746 
4747     3. For SHOW CONSOLE (i.e., SHOW ALL), we loop through the extension table to
4748        call the individual SHOW executors.  However, we don't want to call
4749        "ex_show_serial" twice, or we'll get the connection report twice, so we
4750        use the otherwise-unused "arg" values as a flag to skip the call, which
4751        we do after processing the table.
4752 */
4753 
4754 #define SH_SER              -2
4755 #define SH_TEL              -1
4756 #define SH_NONE              0
4757 
4758 static SHTAB show_console_table [] = {              /* the SHOW CONSOLE extension table */
4759     { "CONCURRENT", &ex_show_concurrent,   0    },  /*   SHOW CONSOLE CONCURRENT */
4760     { "SERIAL",     &ex_show_serial,     SH_SER },  /*   SHOW CONSOLE SERIAL */
4761     { "TELNET",     &ex_show_serial,     SH_TEL },  /*   SHOW CONSOLE TELNET */
4762     { NULL,         NULL,                  0    }
4763     };
4764 
4765 static char show_set [] = "WRU,BRK,DEL,PCHAR,LOG,DEBUG";    /* the standard set of options */
4766 
ex_show_console(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4767 static t_stat ex_show_console (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4768 {
4769 char   *tptr, gbuf [CBUFSIZE], cbuf [CBUFSIZE];
4770 SHTAB  *cmdp;
4771 t_stat status = SCPE_OK;
4772 
4773 if (*cptr == '\0') {                                        /* if no options follow */
4774     sim_show_console (stream, NULL, NULL, flag, show_set);  /*   then show the base console options first */
4775 
4776     for (cmdp = show_console_table; cmdp->name != NULL; cmdp++) /* loop through the extension options */
4777         if (cmdp->arg >= 0)                                     /*   and if it's not to be omitted */
4778             cmdp->action (stream, NULL, NULL, cmdp->arg, "");   /*     then show the option */
4779 
4780     ex_show_serial (stream, NULL, NULL, SH_NONE, "");       /* add the console connection status */
4781     }
4782 
4783 else do {                                               /* otherwise loop through the option list */
4784     cptr = get_glyph_nc (cptr, gbuf, ',');              /* get the next option and value without altering case */
4785     tptr = get_glyph (gbuf, cbuf, '=');                 /*   and then just the option name in upper case */
4786 
4787     cmdp = find_shtab (show_console_table, cbuf);       /* get the associated command handler */
4788 
4789     if (cmdp == NULL)                                                   /* if the target is not one of ours */
4790         status = sim_show_console (stream, NULL, NULL, flag, gbuf);     /*   then let the base handler process it */
4791     else                                                                /* otherwise */
4792         status = cmdp->action (stream, NULL, NULL, cmdp->arg, tptr);    /*   call our handler */
4793 
4794     if (status != SCPE_OK)                              /* if the command failed */
4795         break;                                          /*   then bail out now */
4796     }
4797 while (*cptr != '\0');                                  /* continue until all options are processed */
4798 
4799 return status;                                          /* return the resulting status */
4800 }
4801 
4802 
4803 /* Execute the SHOW CONSOLE CONCURRENT command.
4804 
4805    This command processing routine adds a new SHOW CONSOLE CONCURRENT command
4806    to display the concurrent command mode.  The routine processes commands
4807    of the form:
4808 
4809      SHOW CONSOLE CONCURRENT
4810 
4811    The global mode value is reported.
4812 */
4813 
ex_show_concurrent(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4814 static t_stat ex_show_concurrent (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4815 {
4816 if (*cptr != '\0')                                      /* if something follows */
4817     return SCPE_2MARG;                                  /*   then report extraneous characters */
4818 
4819 else {                                                  /* otherwise */
4820     if (concurrent_mode)                                /*   if concurrent mode is enabled */
4821         fprintf (stream, "Concurrent mode enabled\n");  /*     then report it as such */
4822     else                                                /*   otherwise */
4823         fprintf (stream, "Concurrent mode disabled\n"); /*     report that it is disabled */
4824 
4825     return SCPE_OK;                                     /* return command success */
4826     }
4827 }
4828 
4829 
4830 /* Execute the SHOW CONSOLE SERIAL/TELNET command.
4831 
4832    This command processing routine adds a new SHOW CONSOLE SERIAL command and
4833    enhances the existing SHOW CONSOLE TELNET command to display the connection
4834    status of the system console.  The routine processes commands of the form:
4835 
4836      SHOW CONSOLE SERIAL
4837      SHOW CONSOLE TELNET
4838 
4839    It is also called as part of the SHOW CONSOLE processing.  The three calls
4840    are differentiated by the value of the "flag" parameter, as follows:
4841 
4842      Flag     Meaning
4843      -------  -----------
4844      SH_SER   SHOW SERIAL
4845      SH_TEL   SHOW TELNET
4846      SH_NONE  SHOW
4847 
4848    For the SHOW SERIAL and SHOW TELNET commands, if the console is connected to
4849    the specified type of port, the port status (connection and transmit/receive
4850    counts) is printed.  Otherwise, the command simply reports the connection
4851    type.  So, for example, a SHOW SERIAL command reports "Connected to Telnet
4852    port" or "Connected to console window" if the console is not using a serial
4853    port.
4854 
4855    The plain SHOW command reports the port status of the connection in use.
4856 
4857 
4858    Implementation notes:
4859 
4860     1. We must duplicate the code from the "sim_show_telnet" routine in
4861        "sim_console.c" here because that routine reports a console window
4862        connection if Telnet is not used, and there is no way to suppress that
4863        report when a serial port is used.
4864 */
4865 
ex_show_serial(FILE * stream,DEVICE * dptr,UNIT * uptr,int32 flag,char * cptr)4866 static t_stat ex_show_serial (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
4867 {
4868 if (*cptr != '\0')                                      /* if something follows */
4869     return SCPE_2MARG;                                  /*   then report extraneous characters */
4870 
4871 else {                                                      /* otherwise */
4872     if (serial_line (sim_con_tmxr.ldsc) != NULL)            /*   if a serial connection exists */
4873         if (flag == SH_TEL)                                 /*     but we've asked for Telnet status explicitly */
4874             fputs ("Connected to serial port\n", stream);   /*       then report the other connection type */
4875 
4876         else {                                              /*     otherwise */
4877             fprintf (stream, "Connected to ");              /*       report */
4878             tmxr_fconns (stream, sim_con_tmxr.ldsc, -1);    /*         the connection port and time */
4879             tmxr_fstats (stream, sim_con_tmxr.ldsc, -1);    /*           and the transmit and receive counts */
4880             }
4881 
4882     else if (sim_con_tmxr.master != 0)                      /*   otherwise if a Telnet connection exists */
4883         if (flag == SH_SER)                                 /*     but we've asked for serial status explicitly */
4884             fputs ("Connected to Telnet port\n", stream);   /*       then report the other connection type */
4885 
4886         else if (sim_con_tmxr.ldsc [0].conn == 0)           /*     otherwise if the client hasn't connected yet */
4887             fprintf (stream, "Listening on port %d\n",      /*       then report that we're still listening */
4888                      sim_con_tmxr.port);
4889 
4890         else {                                              /*     otherwise report the socket connection */
4891             fprintf (stream, "Listening on port %d, connected to socket %d\n",
4892                      sim_con_tmxr.port, sim_con_tmxr.ldsc [0].conn);
4893 
4894             tmxr_fconns (stream, sim_con_tmxr.ldsc, -1);    /* report the connection address and time */
4895             tmxr_fstats (stream, sim_con_tmxr.ldsc, -1);    /*   and the transmit and receive counts */
4896             }
4897 
4898     else                                                    /*     otherwise no port connection exists */
4899         fprintf (stream, "Connected to console window\n");  /*       so report the default association */
4900 
4901     return SCPE_OK;                                     /* return command success */
4902     }
4903 }
4904 
4905 
4906 /* Hooked command extension replacement routines */
4907 
4908 
4909 /* Substitute arguments into a command line.
4910 
4911    This hook routine extends the standard "sub_args" routine to perform token
4912    substitution as well as parameter substitution.  The standard behavior
4913    substitutes parameter placeholders of the form "%n", where "n" is a digit
4914    from 0-9, with the strings pointed to by the corresponding elements in the
4915    supplied "args" array.
4916 
4917    In addition to the parameter substitution, built-in and environment tokens
4918    surrounded by percent signs are replaced.  These substitutions provide the
4919    value of environment variables, as well as the current date and time in
4920    various formats that may be used with the REPLY facility to set the target OS
4921    time on bootup.  They also provide some internal simulator values, such as
4922    the path to the binary and the current code version, that may be used in
4923    command files.
4924 
4925    The routine interprets these character sequences as follows:
4926 
4927      %0 - %9   =  parameter substitution from the supplied "args" array
4928 
4929      %token%   =  substitute a value corresponding to the predefined token
4930 
4931      %envvar%  =  substitute the value of an environment variable
4932 
4933      %%        =  substitute a literal percent sign
4934 
4935    Tokens consist of characters without intervening blanks.  For example,
4936    "%TOKEN%" is a token, but "100% PURE" is not (however, this should be
4937    represented as "100%% PURE" to designate the literal percent sign
4938    explicitly).  If a token or environment variable is not defined, it will be
4939    replaced with a null value.
4940 
4941    On entry, "iptr" points at the buffer containing the command line to scan for
4942    substitutions, "optr" points at a temporary output buffer of the same size
4943    as the input buffer, "bufsize" is the size of the buffers, and "args" points
4944    at an array of parameter substitution values.  On return, the "iptr" buffer
4945    contains the substituted line.  If substitution overflows the output buffer,
4946    the result will be truncated at "bufsize" characters with no error
4947    indication.
4948 
4949    This routine is also called for commands entered at the simulation console in
4950    addition to those appearing in command files.  The numeric arguments in the
4951    former case represent the parameters provided at SIMH invocation.
4952 
4953 
4954    Implementation notes:
4955 
4956     1. A literal percent sign is designated by a pair of percent signs (i.e.,
4957        "%%").  This change from the earlier practice of using a backslash as the
4958        escape character is for compatibility with version 4.x.
4959 
4960     2. The C "str..." functions offer no performance advantage over inline code
4961        and are used for clarity only.
4962 
4963     3. If supplied, the "args" array must contain 10 elements.  If "args" is
4964        NULL, then parameter substitution will not occur.
4965 */
4966 
ex_substitute_args(char * iptr,char * optr,int32 bufsize,char * args[])4967 static void ex_substitute_args (char *iptr, char *optr, int32 bufsize, char *args [])
4968 {
4969 char  *start, *end, *in, *out, *env;
4970 int32 i;
4971 
4972 start = strchr (iptr, '%');                             /* see if any % characters are present */
4973 
4974 if (start == NULL)                                      /* if not (the usual case) */
4975     return;                                             /*   then we are done */
4976 
4977 in = iptr;                                              /* at least one % is present */
4978 out = optr;                                             /*   so set up the input and output buffer pointers */
4979 
4980 bufsize = bufsize - 1;                                  /* ensure there is space for the trailing NUL */
4981 
4982 do {                                                    /* copy string segments to a temporary buffer */
4983     if (start - in != 0)                                /* if the leading substring is not empty */
4984         copy_string (&out, &bufsize, in, start - in);   /*   then copy up to the next % */
4985 
4986     if (isdigit (start [1])) {                          /* if the next character is a digit */
4987         i = (int32) (start [1] - '0');                  /*   then convert to an index */
4988 
4989         if (i < 10 && args != NULL && args [i] != NULL) /* if the corresponding argument is present */
4990             copy_string (&out, &bufsize, args [i], 0);  /*   then copy the value */
4991 
4992         in = start + 2;                                 /* skip over the argument substitution */
4993         }
4994 
4995     else {                                              /* otherwise it might be a keyword substitution */
4996         end = strpbrk (start + 1, "% ");                /* search for an embedded blank before the next % */
4997 
4998         if (end == NULL) {                              /* if there is no ending % */
4999             in = start;                                 /*   then copy the remainder of the line */
5000             break;                                      /*     to the output buffer */
5001             }
5002 
5003         else if (bufsize > 0)                           /* otherwise we'll need at least one more character */
5004             if (*end != '%') {                          /* if there is an intervening blank */
5005                 *out++ = *start;                        /*   then the initial % does not start a token */
5006                 *out = '\0';                            /*     so copy the character */
5007 
5008                 in = start + 1;                         /* advance over it */
5009                 bufsize = bufsize - 1;                  /*   and count it */
5010                 }
5011 
5012             else if (end == start + 1) {                /* otherwise if the % signs are adjacent */
5013                 *out++ = '%';                           /*   then output a literal % */
5014                 *out = '\0';                            /*     and terminate the string */
5015 
5016                 in = start + 2;                         /* advance over it */
5017                 bufsize = bufsize - 1;                  /*   and count it */
5018                 }
5019 
5020             else {                                      /* otherwise a substitution token may be present */
5021                 in = end + 1;                           /*   so skip over it */
5022 
5023                 *end = '\0';                            /* temporarily terminate the string at the second % */
5024 
5025                 env = getenv (start + 1);               /* search the environment for a match */
5026 
5027                 *end = '%';                             /* restore the % before testing */
5028 
5029                 if (env)                                    /* if the keyword is an environment variable */
5030                     copy_string (&out, &bufsize, env, 0);   /*   then copy the value */
5031                 else                                        /* otherwise */
5032                     replace_token (&out, &bufsize, start);  /*   replace the token if it's defined */
5033                 }
5034         }
5035 
5036     start = strchr (in, '%');                           /* search for the next initial % */
5037     }                                                   /*   and continue to copy and substitute */
5038 while (start != NULL && bufsize > 0);                   /*     until there are no more tokens */
5039 
5040 if (bufsize > 0)                                        /* if any space remains */
5041     copy_string (&out, &bufsize, in, 0);                /*   then copy any remaining input to the output buffer */
5042 
5043 strcpy (iptr, optr);                                    /* replace the original buffer */
5044 return;                                                 /*   and return */
5045 }
5046 
5047 
5048 /* Get a specified radix from the command-line switches or keyword.
5049 
5050    This hook routine extends the standard "sim_get_radix" routine to permit a
5051    request for binary interpretation of numeric data.  The standard behavior
5052    permits commands such as EXAMINE to override the default radix of the data
5053    item by specifying one of the following switches on the command line:
5054 
5055      Switch  Interpretation
5056      ------  -------------------
5057        -O    An octal value
5058        -D    A decimal value
5059        -H    A hexadecimal value
5060 
5061     ...and to set the default radix for a device with these commands:
5062 
5063       Command            Interpretation
5064       -----------------  -------------------
5065       SET <dev> OCTAL    An octal value
5066       SET <dev> DECIMAL  A decimal value
5067       SET <dev> HEX      A hexadecimal value
5068 
5069     This routine extends the interpretation to add -B and SET <dev> BINARY for
5070     binary value interpretations.
5071 
5072     On entry, if the "cptr" parameter is non-null, then it points at the keyword
5073     for a SET <dev> command.  If the keyword is "BINARY", the returned radix is
5074     2.  Otherwise, it is 0 to indicate that the keyword was not understood by
5075     this routine.
5076 
5077     Otherwise, the "cptr" parameter is NULL, and the "switches" parameter is
5078     checked for the presence of the O, D, H, or B flags.  If one is present, the
5079     corresponding radix is returned.  Otherwise, the "default_radix" parameter
5080     value is returned.
5081 
5082 
5083     Implementation notes:
5084 
5085      1. The order of switch interpretation matches that of the standard SCP
5086         routine, so that the same precedence is used when multiple conflicting
5087         switches are present.
5088 
5089      2. This must be implemented as an extension, as several simulators already
5090         use -B for their own purposes (e.g., for the PDP11, it indicates a byte
5091         access).
5092 */
5093 
ex_get_radix(const char * cptr,int32 switches,int32 default_radix)5094 static int32 ex_get_radix (const char *cptr, int32 switches, int32 default_radix)
5095 {
5096 if (cptr != NULL)
5097     if (strncmp (cptr, "BINARY", strlen (cptr)) == 0)   /* if the keyword is "BINARY" */
5098         return 2;                                       /*   then return radix 2 */
5099     else                                                /* otherwise */
5100         return 0;                                       /*   indicate that the keyword is not recognized */
5101 
5102 else if (switches & SWMASK ('O'))                       /* otherwise if the -O switch was specified */
5103     return 8;                                           /*   then interpret data as octal */
5104 
5105 else if (switches & SWMASK ('D'))                       /* otherwise if the -D switch was specified */
5106     return 10;                                          /*   then interpret data as decimal */
5107 
5108 else if (switches & SWMASK ('H'))                       /* otherwise if the -H switch was specified */
5109     return 16;                                          /*   then interpret data as hex */
5110 
5111 else if (switches & SWMASK ('B'))                       /* otherwise if the -B switch was specified */
5112     return 2;                                           /*   then interpret data as binary */
5113 
5114 else                                                    /* otherwise */
5115     return default_radix;                               /*   use the default radix for the data item */
5116 }
5117 
5118 
5119 /* Local command extension SCP support routines */
5120 
5121 
5122 /* Service a timed execution breakpoint.
5123 
5124    When a timed breakpoint triggers, the BP_TIMED type is set in the
5125    "sim_brk_summ" variable to tell the "sim_brk_test" routine to pass a
5126    breakpoint indication back to the VM for action.  Then we set the breakpoint
5127    action pointer to the buffer containing any actions specified in the original
5128    command.
5129 
5130    The next time the VM instruction loop calls "sim_brk_test", the breakpoint
5131    type will be returned, the instruction loop will drop out, and any stored
5132    actions will be executed.
5133 
5134 
5135    Implementation notes:
5136 
5137     1. It is safe to use a single static breakpoint action buffer for timed
5138        breakpoints, because a triggered breakpoint causes the VM to exit the
5139        instruction loop, and reentry (a necessary condition for a second
5140        breakpoint to trigger) clears any pending actions.
5141 */
5142 
timed_bp_service(UNIT * uptr)5143 static t_stat timed_bp_service (UNIT *uptr)
5144 {
5145 sim_brk_summ |= BP_TIMED;                               /* indicate that a breakpoint stop is required */
5146 sim_brk_act = timed_bp_actions;                         /*   and set up the actions, if any */
5147 
5148 return SCPE_OK;                                         /* return with success */
5149 }
5150 
5151 
5152 /* Local SCP command extension routines */
5153 
5154 
5155 /* Execute commands in a text file.
5156 
5157    This routine is called to execute the SCP commands present in a text file.
5158    It is called by the CALL and DO command executors, where it processes
5159    commands of the form:
5160 
5161      DO { <switches> } <filename> { <param 1> { <param 2> { ... <param 9> } } }
5162 
5163    The <filename> must be present.  Up to nine optional parameters may be
5164    specified, which may be referenced within the command file with the
5165    substitutions "%1" through "%9", respectively.  If a parameter is omitted,
5166    the corresponding substitution will be the empty string.  Parameters are
5167    normally delimited by spaces.  However, spaces may be embedded in a parameter
5168    if it is quoted, either with single (') or double (") quote marks.  The
5169    starting and ending quotes must match, or an "Invalid argument" error is
5170    indicated.
5171 
5172    Three optional switches are recognized:
5173 
5174      -E = continue execution in the presence of errors
5175      -V = echo each command line as it is executed
5176      -A = echo all simulation stop and ATTACH/DETACH messages
5177 
5178    The -E switch causes command file execution to continue regardless of any
5179    error conditions.  If -E is present, only EXIT or an ASSERT failure will stop
5180    execution.  Without -E, execution stops if a command returns an SCP error.
5181 
5182    Specifying the -V switch will print each command line before it is executed.
5183    The filename containing the command is printed as a prompt, e.g.:
5184 
5185      cmdfile> echo Hello!
5186 
5187    If -V is absent, then the command line is printed only if the command fails.
5188    Simulation stops, including the expiration of a STEP command, are not
5189    considered errors and so do not cause the associated commands to be printed.
5190 
5191    The -A switch causes all simulation stops and messages from the ATTACH and
5192    DETACH commands to be reported.  In its absence, the "Breakpoint" and "Step
5193    expired" messages that would normally be printed in response to the
5194    associated stop condition are suppressed, as are the informational messages
5195    from ATTACH and DETACH, such as "Creating new file."  This provides a cleaner
5196    console display log when automated prompts and responses are used.  As an
5197    example, if "cmdfile" contains:
5198 
5199      GO UNTIL "Memory size? " ; ATTACH -N MS0 new.tape ; REPLY "1024\r" ; GO
5200 
5201    ...then "DO cmdfile" would display:
5202 
5203      Memory size? 1024
5204 
5205    ...whereas "DO -A cmdfile" would display:
5206 
5207      Memory size?
5208      Breakpoint, P: 37305 (CLF 10)
5209      MS: creating new file
5210      1024
5211 
5212    All three switches propagate from top-level command files to nested files.
5213    For example, invoking a top-level command file with "DO -V" will verbosely
5214    list not only that file's commands but also the commands within any DO files
5215    invoked therein.
5216 
5217    On entry, the "cptr" parameter points at the invocation string after the DO
5218    or CALL keyword (i.e., points at the optional switches or command filename).
5219    The "file" parameter is NULL if the routine is to open the filename present
5220    at the start of the "cptr" string or is a file stream pointer to an open
5221    file.  The "flag" parameter indicates the source of the call and nesting
5222    level and contains one of these values:
5223 
5224      < 0 = global or local initialization file (no error if not found)
5225        0 = startup command line file
5226        1 = "DO" command
5227      > 1 = nested DO or CALL command
5228 
5229    For a nested command call, the parameter contains the nesting level in bits
5230    0-3 and the value of the invoking switches in bits 4-29.
5231 
5232    The routine begins by separating the nesting level from the propagated
5233    switches.  If the nesting level is too deep, the routine returns with an
5234    error.
5235 
5236    It then obtains any switches specified on the command line and merges them
5237    into the propagated switches.  Then it separates the command line parameters,
5238    removing any leading or trailing spaces (unless the parameter is quoted).  If
5239    the "file" parameter is NULL, it then attempts to open the specified command
5240    file.  If the open fails, and this is not an initialization file request,
5241    then the attempt is repeated after appending the ".sim" extension to the
5242    specified name.  If this second attempt also fails, the command fails with a
5243    "File open error" message.
5244 
5245    The routine then enters the command loop.  The next command line is obtained,
5246    either from a pending breakpoint action or from the command file, and
5247    argument substitutions are made.  If the end of the file was reached, the
5248    loop exits with success status.  Otherwise, if the resulting line is empty
5249    (e.g., is a blank or comment line), the loop continues with the next command
5250    line.
5251 
5252    If the line contains a command, it is echoed if the -V switch was present.
5253    If it's a label line, it is ignored, and the loop continues.  Otherwise, the
5254    global switches are cleared, and the "sim_quiet" value is set if neither the
5255    -A (audible) nor -V (verbose) switch was specified.  This suppresses certain
5256    noise messages unless they were specifically requested by the invocation.
5257    Then the command keyword is parsed, and the action routine is obtained.
5258 
5259    If it is a DO command, the DO executor is called with the nesting level
5260    increased by one.  On reentry here, the nesting level is checked, and an
5261    error occurs if the limit is exceeded.
5262 
5263    If the command is RUN (or any of the execution commands), and neither the
5264    -A nor -V switches was specified, the SIM_SW_HIDE switch will be passed to
5265    the RUN executor to suppress BREAK and STEP messages.  Then the associated
5266    command handler is invoked, and its status result is obtained.
5267 
5268    If the command is one of those restricted to use within a command file (CALL,
5269    RETURN, GOTO, or ABORT), the appropriate action is taken.  Otherwise, if the
5270    command succeeds, the command loop continues.  If the command fails, actions
5271    then depend on the specific error code and switch settings.  Three
5272    determinations are made: whether command file execution continues, whether
5273    the command line is printed, and whether the error message text is printed.
5274 
5275    Execution stays in the command loop if the error is not due to an EXIT or
5276    ABORT command or an ASSERT failure -- these always terminate DO execution --
5277    and the code is for a simulation stop (all VM-defined status codes and the
5278    STEP expired code) or the -E switch was specified.  That is, the loop
5279    terminates for an EXIT, ABORT, ASSERT that fails, or an SCP error and the -E
5280    switch was not given.
5281 
5282    The failing command line is printed if an SCP error occurred, the -V switch
5283    was not given, and the command was not DO unless it failed because the file
5284    could not be opened.  This excludes simulator stops, which are not considered
5285    errors in a command file.  it also avoids printing the line a second time if
5286    it was previously printed by the -V switch.  Finally, because the status of a
5287    failing command is passed back as the status of the enclosing DO or CALL
5288    command to ensure that execution stops for a failure in a nested invocation,
5289    the command line is not printed unless the DO command itself failed.  This
5290    determination is made independently of the determination to stay, because we
5291    want command lines causing errors to be printed, regardless of whether or not
5292    we will ignore the error.
5293 
5294    The error text is printed if it is an SCP error and either the command file
5295    is nested or the error will be ignored.  Simulation stops have already been
5296    printed by the "fprint_stopped" call in the RUN handler, and if the DO
5297    command was initiated from the command line and not by a nested DO or CALL
5298    invocation, and the execution loop will be exited, then the main command loop
5299    will print the error text when this routine returns.
5300 
5301    Finally, we call the VM's post-command handler if it is defined and check for
5302    a CTRL+C stop.  If it is detected, a message is printed, any pending
5303    breakpoint actions are cleared, and the status return is set to propagate the
5304    abort back through all nesting levels.
5305 
5306    Once the command loop exits, either for an error, or because the command file
5307    is exhausted, the file is closed if we had opened it, and the last command
5308    status is returned as the status of the invoking DO or CALL.  If we will be
5309    exiting the simulator, the end-of-session detach for all units is performed
5310    before returning, so that the same quietness as was used for the attaches is
5311    used.
5312 
5313 
5314    Implementation notes:
5315 
5316     1. The nesting limit is arbitrary and is intended mainly to prevent a stack
5317        overflow abort if a command file executes a recursive DO in an infinite
5318        loop.  Note that CALL invocations count toward the nesting limit.  Note
5319        also that four bits of the "flag" parameter are allocated to the nesting
5320        level, so the limit must be less than 15.
5321 
5322     2. The command line switches are not extracted here when processing the
5323        initialization or command-line files.  This is because the switches were
5324        already processed and stripped in the "main" routine before we were
5325        called.
5326 
5327     3. The standard ATTACH and DETACH commands are too noisy for command-file
5328        execution.  Specifically, ATTACH reports "unit is read only" for files
5329        without write permission, "creating new file" for newly created
5330        files, and "buffering file in memory" when the unit has the UNIT_BUFABLE
5331        flag.  DETACH reports "writing buffer to file" for a unit with
5332        UNIT_BUFABLE.  All of the messages report conditions over which the user
5333        of an executing command file has no control.
5334 
5335     4. The "do_arg" array is an array of string pointers, rather than an array
5336        of strings, that point into the command line buffer that is passed to
5337        this routine.  That buffer must not be constant, as we separate the
5338        parameters by writing a NUL after each parsed parameter.
5339 
5340     5. The command file is opened as a binary, rather than text, file on Windows
5341        systems.  This is because "fgetpos" and "fsetpos" (used in the CALL
5342        executor) fail to work properly on text files.  Opening as binary means
5343        that lines obtained with "fgets" have trailing CRLFs instead of LFs, but
5344        the "read_line" routine strips both line termination characters, so the
5345        result is as expected.
5346 
5347     6. The HP simulators look for SIM_SW_HIDE in "sim_switches" and return
5348        SCPE_OK status in lieu of the SCPE_STEP or STOP_BRKPNT codes.  In turn,
5349        this suppresses the "Step expired" and "Breakpoint" simulation stop
5350        messages that otherwise would be interspersed with prompts and responses
5351        supplied by the GO UNTIL and REPLY commands.
5352 
5353     7. Execution commands are identified by the "help_base" field of their
5354        corresponding CTAB entries being non-null.  This is a hack; see the
5355        comments for the "ex_cmds" structure declaration for details.
5356 
5357     8. A failing command within a command file returns an error status that
5358        causes the bad command to be echoed to identify the cause of the error.
5359        That status is also returned as the command file execution status, which
5360        would normally cause the invoking DO or CALL command to be echoed as
5361        well.  This is undesirable, as the command file name has already been
5362        printed as part of the echo, so this is suppressed.
5363 
5364        An exception is made for the SCPE_OPENERR status returned when the DO
5365        command file cannot be opened; otherwise, there would be no indication
5366        what caused the error message.  However, when this status is passed back
5367        to nested invocations, the exception would cause each invoking DO command
5368        to be listed, which would be wrong (the outer DOs did not fail with file
5369        open errors).
5370 
5371        To fix this, we tag the innermost nested return only with SCPE_DOFAILED,
5372        and use this to implement the exception.  This suppresses the propagating
5373        errors, so that only the DO command that actually encountered the file
5374        open error is echoed.
5375 
5376     9. The CALL command currently does not take switches (they aren't parsed
5377        from the command line string), so the called context uses the same switch
5378        environment as the caller.
5379 */
5380 
5381 #define ARG_COUNT           10                          /* number of DO command arguments */
5382 #define NEST_LIMIT          10                          /* DO command nesting limit (must be <= 15) */
5383 
5384 #define LEVEL_SHIFT         4                           /* bits allocated to the level value */
5385 #define LEVEL_MASK          ((1u << LEVEL_SHIFT) - 1)   /* mask for the level value */
5386 
execute_file(FILE * file,int32 flag,char * cptr)5387 static t_stat execute_file (FILE *file, int32 flag, char *cptr)
5388 {
5389 const  t_bool interactive = (flag > 0);         /* TRUE if DO was issued interactively */
5390 static t_bool must_detach = TRUE;               /* TRUE until "detach_all" has been called during exit */
5391 FILE   *do_file;
5392 CTAB   *cmdp;
5393 char   term, *kptr, *do_arg [ARG_COUNT], cbuf [CBUFSIZE], kbuf [CBUFSIZE];
5394 int32  level, switches, audible, errignore, verbose, count;
5395 t_bool is_do;
5396 t_bool staying = TRUE;
5397 t_stat status = SCPE_OK;
5398 
5399 if (interactive) {                                      /* if we were originally called from the SCP prompt */
5400     level    = flag & LEVEL_MASK;                       /*   then isolate the nesting level */
5401     switches = flag >> LEVEL_SHIFT;                     /*     and propagated switches from the parameter */
5402 
5403     if (level >= NEST_LIMIT)                            /* if the call nesting level is too deep */
5404         return SCPE_NEST;                               /*   then report the error */
5405 
5406     cptr = get_sim_sw (cptr);                           /* get any command line switches */
5407 
5408     if (cptr == NULL)                                   /* if an invalid switch was present */
5409         return SCPE_INVSW;                              /*   then report it */
5410     }
5411 
5412 else {                                                  /* otherwise this is an initialization file call */
5413     level    = 1;                                       /*   so start out at level 1 */
5414     switches = 0;                                       /*     with no propagated switches */
5415     }
5416 
5417 switches |= sim_switches;                               /* merge specified and propagated switches */
5418 
5419 audible   = switches & SWMASK ('A');                    /* set the audible flag if -A is present */
5420 errignore = switches & SWMASK ('E');                    /*   and the error ignore flag if -E is present */
5421 verbose   = switches & SWMASK ('V');                    /*     and the verbose flag if -V is present */
5422 
5423 for (count = 0; count < ARG_COUNT; count++) {           /* parse the arguments */
5424     if (cptr == NULL || *cptr == '\0')                  /* if the argument list is exhausted */
5425         do_arg [count] = NULL;                          /*   then invalidate the remaining pointers */
5426 
5427     else {                                              /* otherwise */
5428         if (*cptr == '\'' || *cptr == '"')              /*   if a quoted string is present */
5429             term = *cptr++;                             /*     then save the terminator */
5430         else                                            /*   otherwise */
5431             term = ' ';                                 /*     use a space as the terminator */
5432 
5433         do_arg [count] = cptr;                          /* point at the start of the parameter */
5434 
5435         cptr = strchr (cptr, term);                     /* find the terminator */
5436 
5437         if (cptr != NULL) {                             /* if the parameter was terminated */
5438             *cptr++ = '\0';                             /*   then separate the parameters */
5439 
5440             while (isspace (*cptr))                     /* discard any trailing spaces */
5441                 cptr++;                                 /*   that follow the parameter */
5442             }
5443 
5444         else if (term != ' ')                           /* otherwise if a quoted string is not terminated */
5445             return SCPE_ARG;                            /*   then report a bad argument */
5446         }
5447     }
5448 
5449 if (do_arg [0] == NULL)                                 /* if the command filename is missing */
5450     return SCPE_2FARG;                                  /*   then report it */
5451 
5452 else if (file != NULL)                                  /* otherwise if a stream was specified */
5453     do_file = file;                                     /*   then use it */
5454 
5455 else {                                                  /* otherwise */
5456     do_file = fopen (do_arg [0], "rb");                 /*   open the specified command file */
5457 
5458     if (do_file == NULL) {                              /* if the file failed to open as is */
5459         if (flag < 0)                                   /*   then if this is an initialization file  */
5460             return SCPE_OPENERR;                        /*     then do not try an alternate filename */
5461 
5462         strcat (strcpy (cbuf, do_arg [0]), ".sim");     /* append a ".sim" extension to the filename */
5463         do_file = fopen (cbuf, "rb");                   /*   and try again */
5464 
5465         if (do_file == NULL) {                              /* if the open failed a second time */
5466             if (flag == 0)                                  /*   then if we're executing the startup file */
5467                 fprintf (stderr, "Can't open file %s\n",    /*     then report the failure */
5468                          do_arg [0]);
5469 
5470             if (level > 1)                              /* if this is a nested DO call */
5471                 return SCPE_OPENERR | SCPE_DOFAILED;    /*   then return failure with the internal flag */
5472 
5473             else                                        /* otherwise this is a top-level call */
5474                 return SCPE_OPENERR;                    /*   so simply return failure */
5475             }
5476         }
5477     }
5478 
5479 stop_requested = FALSE;                                 /* clear any pending WRU stop */
5480 
5481 do {
5482     cptr = sim_brk_getact (cbuf, CBUFSIZE);             /* get any pending breakpoint actions */
5483 
5484     if (cptr == NULL)                                   /* if there are no pending actions */
5485          cptr = read_line (cbuf, CBUFSIZE, do_file);    /*   then get a line from the command file */
5486 
5487     ex_substitute_args (cbuf, kbuf, CBUFSIZE, do_arg);  /* substitute arguments in the command */
5488 
5489     if (cptr == NULL) {                                 /* if the end of the command file was reached */
5490         status = SCPE_OK;                               /*   then set normal status */
5491         break;                                          /*     and exit the command loop */
5492         }
5493 
5494     else if (*cptr == '\0')                             /* otherwise if the line is blank */
5495         continue;                                       /*   then ignore it */
5496 
5497     if (verbose) {                                      /* if command echo is specified */
5498         printf ("%s> %s\n", do_arg [0], cptr);          /*   then output the command filename and line */
5499 
5500         if (sim_log)                                    /* if the console is logging */
5501             fprintf (sim_log, "%s> %s\n",               /*   then write it to the log file as well */
5502                               do_arg [0], cptr);
5503         }
5504 
5505     if (*cptr == ':')                                   /* if this is a label line */
5506         continue;                                       /*   then ignore it */
5507 
5508     sim_switches = 0;                                   /* initialize the switches for the new command */
5509     sim_quiet = not (audible | verbose);                /*   and quiet the noise if not specifically requested */
5510 
5511     kptr = get_glyph (cptr, kbuf, 0);                   /* parse the command keyword */
5512     status = get_command (kbuf, &cmdp);                 /*   and get the associated descriptor */
5513 
5514     if (status == SCPE_OK) {                            /* if the command is valid */
5515         is_do = (cmdp->action == ex_do_handler);        /*   then set a flag if this is a DO command  */
5516 
5517         if (is_do)                                          /* if this is a DO command */
5518             status = cmdp->action (switches << LEVEL_SHIFT  /*   then execute the DO */
5519                                      | level + 1, kptr);    /*     and propagate the switches */
5520 
5521         else {                                          /* otherwise */
5522             if (cmdp->help_base != NULL && sim_quiet)   /*   if this is a quiet RUN (GO, etc.) command */
5523                 sim_switches = SIM_SW_HIDE;             /*     then suppress BREAK and STEP stop messages */
5524 
5525             status = cmdp->action (cmdp->arg, kptr);    /* execute the command */
5526 
5527             if (cmdp->action == ex_restricted_cmd)          /* if this is a restricted command */
5528                 if (cmdp->arg == EX_GOTO)                   /*   then if this is a GOTO command */
5529                     status = goto_label (do_file, kptr);    /*     then transfer control to the label */
5530 
5531                 else if (cmdp->arg == EX_CALL)                  /* otherwise if this is a CALL command */
5532                     status = gosub_label (do_file, do_arg [0],  /*   then transfer control */
5533                                           switches << LEVEL_SHIFT
5534                                             | level + 1,
5535                                           kptr);
5536 
5537                 else if (cmdp->arg == EX_RETURN) {      /* otherwise if this is a RETURN command */
5538                     status = SCPE_OK;                   /*   then clear the error status */
5539                     break;                              /*     and drop out of the loop */
5540                     }
5541 
5542                 else if (cmdp->arg == EX_ABORT) {       /* otherwise if this is an ABORT command */
5543                     stop_requested = TRUE;              /*   then set the flag */
5544                     status = SCPE_ABORT;                /*     and abort status */
5545                     }
5546             }
5547         }
5548 
5549     else                                                /* otherwise the command is invalid */
5550         is_do = FALSE;                                  /*   so it can't be a DO command */
5551 
5552     staying = (status != SCPE_ABORT                     /* stay if not aborting */
5553                 && status != SCPE_EXIT                  /*   and not exiting */
5554                 && status != SCPE_AFAIL                 /*   and no assertion failed */
5555                 && (errignore                           /*   and errors are ignored */
5556                   || status < SCPE_BASE                 /*     or a simulator stop occurred */
5557                   || status == SCPE_STEP));             /*       or a step expired */
5558 
5559     if (not staying)                                    /* if leaving due to an error */
5560         sim_brk_clract ();                              /*   then cancel any pending actions */
5561 
5562     if (status >= SCPE_BASE                             /* if an SCP error occurred */
5563       && status != SCPE_EXIT && status != SCPE_STEP) {  /*   other then EXIT or STEP */
5564         if (not verbose                                 /*     then if the line has not already been printed */
5565           && not is_do || status & SCPE_DOFAILED) {     /*       and it's not a command status returned by DO */
5566             printf("%s> %s\n", do_arg [0], cptr);       /*         then print the offending command line */
5567 
5568             if (sim_log)                                /* if the console is logging */
5569                 fprintf (sim_log, "%s> %s\n",           /*   then write it to the log file as well */
5570                                   do_arg [0], cptr);
5571             }
5572 
5573         if (is_do)                                      /* if it's a DO command that has failed */
5574             status &= ~SCPE_DOFAILED;                   /*   then remove the failure location flag */
5575         }
5576 
5577     if (status >= SCPE_BASE && status <= SCPE_LAST      /* if an SCP error occurred */
5578       && (staying || not interactive)) {                /*   and we're staying or were not invoked from the SCP prompt */
5579         printf ("%s\n", sim_error_text (status));       /*     then print the error message */
5580 
5581         if (sim_log)                                    /* if the console is logging */
5582             fprintf (sim_log, "%s\n",                   /*   then write it to the log file as well */
5583                               sim_error_text (status));
5584         }
5585 
5586     if (sim_vm_post != NULL)                            /* if the VM defined a post-processor */
5587         sim_vm_post (TRUE);                             /*   then call it, specifying SCP origin */
5588 
5589     if (stop_requested) {                               /* if a stop was detected via a signal */
5590         stop_requested = FALSE;                         /*   then clear the request */
5591 
5592         printf ("Command file execution aborted\n");
5593 
5594         if (sim_log)
5595             fprintf (sim_log, "Command file execution aborted\n");
5596 
5597         sim_brk_clract ();                              /* cancel any pending actions */
5598 
5599         staying = FALSE;                                /* end command file execution */
5600         status = SCPE_ABORT;                            /*   and set status to clear nested invocations */
5601         }
5602     }
5603 
5604 while (staying);                                        /* continue execution until a terminating condition */
5605 
5606 if (status == SCPE_EXIT && must_detach) {               /* if we are exiting the simulator and haven't detached */
5607     detach_all (0, TRUE);                               /*   then detach all units while still quiet */
5608     must_detach = FALSE;                                /*     and clear flag to avoid repeating if nested */
5609     }
5610 
5611 sim_quiet = ex_quiet;                                   /* restore the original quiet setting */
5612 
5613 if (file == NULL)                                       /* if we opened the command file */
5614     fclose (do_file);                                   /*   then close it before leaving */
5615 
5616 return status;                                          /* return the termination status */
5617 }
5618 
5619 
5620 /* Execute the GOTO command.
5621 
5622    This routine is called to process a GOTO command within a command file.  The
5623    command form is:
5624 
5625      GOTO <label>
5626 
5627    Where:
5628 
5629      <label> is an identifier that appears in a label statement somewhere within
5630              the current command file.
5631 
5632    This routine transfers command file execution to the labeled statement
5633    specified by the "cptr" parameter by positioning the file specified by the
5634    "stream" parameter to the line following the label.  On entry, "cptr" points
5635    at the remainder of the GOTO command line, which should contain a transfer
5636    label.  If it is missing or is followed by additional characters, "Too few
5637    arguments" or "Too many arguments" errors are returned.  Otherwise, a search
5638    for the label is performed by rewinding the file stream and reading lines
5639    until either the label is found or the EOF is reached.  In the latter case,
5640    an "Invalid argument" error is returned.
5641 
5642 
5643    Implementation notes:
5644 
5645     1. A colon preceding the specified label is ignored.  Either "GOTO :label"
5646        or "GOTO label" is accepted.
5647 
5648     2. A simple linear search from the start of the file is performed.  Most
5649        labels will follow their GOTOs in the file, but optimizing the search
5650        into two parts (i.e., from the current point to the end, and then from
5651        the start to the current point) isn't worth the complexity.
5652 
5653     3. The search is case-sensitive.
5654 */
5655 
goto_label(FILE * stream,char * cptr)5656 static t_stat goto_label (FILE *stream, char *cptr)
5657 {
5658 char cbuf [CBUFSIZE], label [CBUFSIZE], *lptr;
5659 
5660 lptr = label;                                           /* save a pointer to the target label buffer */
5661 
5662 cptr = get_glyph_nc (cptr, lptr, 0);                    /* parse the label from the remaining command line */
5663 
5664 if (*cptr != '\0')                                      /* if there are extraneous characters following */
5665     return SCPE_2MARG;                                  /*   then return the "Too many parameters" error */
5666 
5667 else if (*lptr == '\0')                                 /* otherwise if the label is missing */
5668     return SCPE_2FARG;                                  /*   then return the "Too few parameters" error */
5669 
5670 else if (*lptr == ':')                                  /* otherwise if the label starts with a colon */
5671     lptr++;                                             /*   then skip over it to point at the identifier */
5672 
5673 rewind (stream);                                        /* reposition the file to the start */
5674 
5675 do {                                                    /* loop until the label or the EOF is found */
5676     cptr = read_line (cbuf, CBUFSIZE, stream);          /* read the next line from the file */
5677 
5678     if (cptr == NULL)                                   /* if the end of file was seen */
5679         return SCPE_ARG;                                /*   then report that the label was not found */
5680 
5681     else if (*cptr == ':') {                            /* otherwise if this is a label line */
5682         cptr++;                                         /*   then skip the leading colon */
5683 
5684         if (strcmp (cptr, lptr) == 0)                   /* if this is the target label */
5685             break;                                      /*   then terminate the search here */
5686         }
5687     }
5688 while (TRUE);                                           /* otherwise continue to search */
5689 
5690 return SCPE_OK;                                         /* return success as the label was found */
5691 }
5692 
5693 
5694 /* Execute the CALL command.
5695 
5696    This routine is called to process a CALL command within a command file.  The
5697    command form is:
5698 
5699      CALL <label> { <param 1> { <param 2> { ... <param 9> } } }
5700 
5701    Where:
5702 
5703      <label> is an identifier that appears in a label statement somewhere within
5704              the current command file.
5705 
5706    This routine saves the current position in the file specified by the "stream"
5707    parameter and then transfers command file execution to the labeled statement
5708    specified by the "cptr" parameter by positioning the file to the line
5709    following the label.  If positioning succeeds, the commands there are
5710    executed until a RETURN or ABORT command is executed, or the end of the
5711    command file is reached.  At that point, the original position is restored,
5712    and the command file continues to execute at the line after the original CALL
5713    command.
5714 
5715    On entry, "cptr" points at the remainder of the CALL command line, which
5716    should contain a transfer label.  The "filename" parameter points to the the
5717    name of the current command file, "level" is set to the current nesting
5718    level, and "switches" contain the set of switches that were used to invoke
5719    the current command file.  These three parameters are used to set up the same
5720    command file environment for subroutine execution.
5721 
5722    If the transfer label is missing, "Too few arguments" status is returned.
5723    Otherwise, the current stream position is saved, and the "goto_label" routine
5724    is called to position the stream to the start of the subroutine.  If that
5725    routine succeeds, the current filename is copied into a parameter buffer,
5726    followed by the remainder of the CALL command line parameters.  The switches
5727    are reset, and the "execute_file" routine is called to execute the
5728    subroutine.  On return, the file position is restored, and the execution
5729    status is returned.
5730 
5731 
5732    Implementation notes:
5733 
5734     1.  The "execute_file" routine parses the command line for switches and the
5735         command filename, which must be the "zeroth" parameter.  We must supply
5736         these so that the subroutine executes in the same environment as the
5737         containing command file, except that the parameters to the subroutine
5738         are set to those passed in the CALL command rather than from the
5739         original parameters to the command file invocation.
5740 
5741     2. The saved file position is the line after the CALL statement, which is
5742        where the called subroutine will return.
5743 */
5744 
gosub_label(FILE * stream,char * filename,int32 flag,char * cptr)5745 static t_stat gosub_label (FILE *stream, char *filename, int32 flag, char *cptr)
5746 {
5747 char   label [CBUFSIZE];
5748 t_stat status;
5749 fpos_t current;
5750 
5751 cptr = get_glyph_nc (cptr, label, 0);                   /* parse the label from the remaining command line */
5752 
5753 if (label [0] == '\0')                                  /* if the label is missing */
5754     return SCPE_2FARG;                                  /*   then return the "Too few parameters" error */
5755 
5756 else {                                                  /* otherwise */
5757     if (fgetpos (stream, &current) != 0) {              /*   save the current position; if that fails */
5758         perror ("Saving the file position failed");     /*     then report the error to the console */
5759         return SCPE_ABORT;                              /*       and abort command file execution */
5760         }
5761 
5762     status = goto_label (stream, label);                /* position the file to the subroutine label */
5763 
5764     if (status == SCPE_OK) {                            /* if positioning succeeded */
5765         strcpy (label, filename);                       /*   then form a parameter line */
5766         strcat (label, " ");                            /*     with the command filename */
5767         strcat (label, cptr);                           /*       preceding the CALL parameters */
5768 
5769         status = execute_file (stream, flag, label);    /* execute the subroutine */
5770 
5771         if (fsetpos (stream, &current) != 0) {              /* restore the file position; if that fails */
5772             perror ("Restoring the file position failed");  /*     then report the error to the console */
5773             return SCPE_ABORT;                              /*       and abort command file execution */
5774             }
5775         }
5776 
5777     return status;                                      /* return the command status */
5778     }
5779 }
5780 
5781 
5782 /* Replace a predefined token.
5783 
5784    This routine replaces a predefined token with its associated value.  Token
5785    references are character strings surrounded by percent signs ("%") and not
5786    containing blanks.  For example, "%token%" is a token reference, but "%not a
5787    token%" is not.
5788 
5789    On entry, "token_ptr" points at a valid token reference, "out_ptr" points at
5790    a pointer to the buffer where the associated value is to be placed, and
5791    "out_size" points at the size of the output buffer.  The token is obtained by
5792    stripping the percent signs and converting to upper case.  The keyword table
5793    is then searched for a match for the token.  If it is found, the associated
5794    substitution action directs the generation of the returned value.
5795 
5796    For date and time values, the current time (or a time rescaled to the 20th
5797    century) is formatted as directed by the keyword entry.  Other values are
5798    generally static and are copied to the output buffer.  After copying, the
5799    output pointer is advanced over the value, and the buffer size is decreased
5800    appropriately.
5801 
5802 
5803    Implementation notes:
5804 
5805     1. We get the current local time unconditionally, as most of the token
5806        substitutions requested will be for date or time values.
5807 
5808     2. The "token_ptr" cannot be "const" because the "get_glyph" routine takes a
5809        variable input parameter, even though it is not modified.
5810 
5811     3. If insufficient space remains in the output buffer, the "strftime"
5812        routine return 0 and leaves the buffer in an indeterminate state.  In
5813        this case, we store a NUL at the start of the buffer to ensure that the
5814        buffer is properly terminated.
5815 
5816     4. The SIM_MAJOR value is a constant and so cannot be the target of the
5817        "ptr" field.  Therefore, we must declare a variable that is set to this
5818        value and point at that.
5819 
5820     5. "sim_prog_name" is a pointer to the program name, rather than the program
5821        name string itself.  So the associated "ptr" field is a pointer to a
5822        pointer to the name.
5823 
5824     6. The "copy_string" routine updates its output pointer and size
5825        parameters, so we avoid updating them again when that routine is called.
5826 */
5827 
5828 typedef enum {                                  /* substitution actions */
5829     Format_Value,                               /*   format an integer value */
5830     Format_Date,                                /*   format a date or time */
5831     Rescale_Date,                               /*   format a date rescaled to the 20th century */
5832     Copy_String,                                /*   copy a string value directly */
5833     } ACTION;
5834 
5835 typedef struct {                                /* the keyword descriptor */
5836     const char  *token;                         /*   the token name */
5837     void        *ptr;                           /*   a pointer to the substitution value */
5838     const char  *format;                        /*   the substitution format */
5839     ACTION      substitution;                   /*   the substitution action */
5840     } KEYWORD;
5841 
5842 static uint32 sim_major = SIM_MAJOR;                    /* the simulator major version number */
5843 static char **sim_name_ptr = (char **) &sim_name;       /* a pointer to the simulator name */
5844 
5845 static const KEYWORD keys [] = {
5846 /*    Token           Pointer           Format  Substitution */
5847 /*    --------------  ----------------  ------  ------------ */
5848     { "DATE_YYYY",    NULL,             "%Y",   Format_Date  }, /* four-digit year */
5849     { "DATE_YY",      NULL,             "%y",   Format_Date  }, /* two-digit year 00-99 */
5850     { "DATE_MM",      NULL,             "%m",   Format_Date  }, /* two-digit month 01-12 */
5851     { "DATE_MMM",     NULL,             "%b",   Format_Date  }, /* three-character month JAN-DEC */
5852     { "DATE_DD",      NULL,             "%d",   Format_Date  }, /* two-digit day of the month 01-31 */
5853     { "DATE_JJJ",     NULL,             "%j",   Format_Date  }, /* three-digit Julian day of the year 001-366 */
5854     { "DATE_RRRR",    NULL,             "%Y",   Rescale_Date }, /* four-digit year rescaled to the 20th century 1972-1999 */
5855     { "DATE_RR",      NULL,             "%y",   Rescale_Date }, /* two-digit year rescaled to the 20th century 72-99 */
5856     { "TIME_HH",      NULL,             "%H",   Format_Date  }, /* two-digit hour of the day 01-23 */
5857     { "TIME_MM",      NULL,             "%M",   Format_Date  }, /* two-digit minute of the hour 00-59 */
5858     { "TIME_SS",      NULL,             "%S",   Format_Date  }, /* two-digit second of the minute 00-59 */
5859     { "SIM_MAJOR",    &sim_major,       "%d",   Format_Value }, /* the major version number of the simulator */
5860     { "SIM_NAME",     &sim_name_ptr,    NULL,   Copy_String  }, /* the name of the simulator */
5861     { "SIM_EXEC",     &sim_prog_name,   NULL,   Copy_String  }, /* the simulator executable path and filename */
5862     { "SIM_RUNNING",  &concurrent_run,  "%d",   Format_Value }, /* non-zero if the simulator is running */
5863     { NULL,           0,                NULL,   0            }
5864     };
5865 
replace_token(char ** out_ptr,int32 * out_size,char * token_ptr)5866 static void replace_token (char **out_ptr, int32 *out_size, char *token_ptr)
5867 {
5868 const KEYWORD *kptr;
5869 char          tbuf [CBUFSIZE];
5870 size_t        space;
5871 time_t        time_value;
5872 struct tm     *now;
5873 
5874 get_glyph (token_ptr + 1, tbuf, '%');                   /* copy the token and convert it to upper case */
5875 
5876 for (kptr = keys; kptr->token != NULL; kptr++)          /* search the keyword table for the token */
5877     if (strcmp (tbuf, kptr->token) == 0) {              /* if the current keyword matches */
5878         time_value = time (NULL);                       /*   then get the current UTC time */
5879         now = localtime (&time_value);                  /*     and convert to local time */
5880 
5881         switch (kptr->substitution) {                   /* dispatch for the substitution */
5882 
5883             case Rescale_Date:                          /* current date with year rescaled to 1972-1999 */
5884                 while (now->tm_year >= 100)             /* reduce the current year */
5885                     now->tm_year = now->tm_year - 28;   /*   until it aligns with one in the 20th century */
5886 
5887             /* fall through into Format_Date */
5888 
5889             case Format_Date:                           /* current time/date */
5890                 space = strftime (*out_ptr, *out_size,  /* format and store a time or date value */
5891                                   kptr->format, now);
5892 
5893                 if (space == 0)                         /* if the value wasn't stored */
5894                     **out_ptr = '\0';                   /*   then be sure the output string is terminated */
5895                 break;
5896 
5897             case Format_Value:                          /* integer value */
5898                 space = snprintf (*out_ptr, *out_size,  /* format and store an integer value */
5899                                   kptr->format,
5900                                   *(int *) kptr->ptr);
5901                 break;
5902 
5903             default:                                    /* needed to quiet warning about "space" being undefined */
5904             case Copy_String:                           /* string value */
5905                 copy_string (out_ptr, out_size,         /* copy a string value */
5906                              *(char **) kptr->ptr, 0);
5907                 space = 0;                              /* output pointer and size have already been adjusted */
5908                 break;
5909             }                                           /* all cases are covered */
5910 
5911         *out_ptr = *out_ptr + space;                    /* adjust the output pointer */
5912         *out_size = *out_size - space;                  /*   and the size remaining for the copy */
5913         }
5914 
5915 return;
5916 }
5917 
5918 
5919 /* Copy a string without overrun.
5920 
5921    This routine copies a string to a buffer while ensuring that the buffer does
5922    not overflow.  On entry, "source" points to the string to copy, "source_size"
5923    is zero if the entire string is to be copied or a positive value if only a
5924    leading substring is to be copied, "target" points at a pointer to the target
5925    buffer, and "target_size" points at the size of the buffer.
5926 
5927    If "source_size" is zero, the string length is obtained.  If the source
5928    length is greater than the space available in the target buffer, the length
5929    is reduced to match the space available.  Then the string is copied.  The
5930    target buffer pointer is updated, and a NUL is appended to terminate the
5931    string.  Finally, the buffer size is decreased by the size of the copied
5932    string.
5933 
5934 
5935    Implementation notes:
5936 
5937     1. This routine is needed because the standard "strncpy" function does not
5938        guarantee that a NUL is appended.  Also, if space is available, the
5939        remainder of the output buffer is filled with NULs, which is unnecessary
5940        for our use.
5941 */
5942 
copy_string(char ** target,int32 * target_size,const char * source,int32 source_size)5943 static void copy_string (char **target, int32 *target_size, const char *source, int32 source_size)
5944 {
5945 int32 copy_size;
5946 
5947 if (source_size == 0)                                   /* if we are asked to calculate the source length */
5948     copy_size = strlen (source);                        /*   then do it now */
5949 else                                                    /* otherwise */
5950     copy_size = source_size;                            /*   use the supplied size */
5951 
5952 if (copy_size > *target_size)                           /* if there is not enough space remaining */
5953     copy_size = *target_size;                           /*   then copy only enough to fill the buffer */
5954 
5955 memcpy (*target, source, copy_size);                    /* copy the string */
5956 
5957 *target = *target + copy_size;                          /* advance the output buffer pointer */
5958 **target = '\0';                                        /*   and terminate the copied string */
5959 
5960 *target_size = *target_size - copy_size;                /* drop the remaining space count */
5961 return;                                                 /*   and return */
5962 }
5963 
5964 
5965 /* Parse a quoted string.
5966 
5967    A string delimited by single or double quotation marks is parsed from the
5968    buffer pointed to by the "sptr" parameter and copied into the buffer pointed
5969    to by the "dptr" parameter.  If the "decode" parameter is TRUE, then the
5970    following specialized escapes are decoded and replaced with the indicated
5971    substitutions:
5972 
5973       Escape  Substitution
5974       ------  ------------
5975         \"         "
5976         \'         '
5977         \\         \
5978         \r         CR
5979         \n         LF
5980 
5981    In addition, a backslash followed by exactly three octal digits is replaced
5982    with the corresponding ASCII code.  The opening and closing quotes are not
5983    copied, but any escaped embedded quotes are.  All other characters are copied
5984    verbatim, except that lowercase alphabetic characters are replaced with
5985    uppercase characters if the "upshift" parameter is TRUE.
5986 
5987    The function returns a pointer to the next character in the source buffer
5988    after the closing quotation mark.  If the opening and closing quotation marks
5989    are not the same or the closing quotation mark is missing, the function
5990    returns NULL.
5991 
5992 
5993    Implementation notes:
5994 
5995     1. The routine assumes that the first "sptr" character is the opening quote,
5996        and it is this character that will be sought as the closing quote.  It is
5997        not necessary to escape the alternate quote character if it appears in
5998        the string.  For example, "It's great!" and 'Say "Hi"' are accepted as
5999        legal.
6000 */
6001 
parse_quoted_string(char * sptr,char * dptr,t_bool upshift,t_bool decode)6002 static char *parse_quoted_string (char *sptr, char *dptr, t_bool upshift, t_bool decode)
6003 {
6004 char   quote;
6005 uint32 i, octal;
6006 
6007 quote = *sptr++;                                        /* save the opening quotation mark */
6008 
6009 while (sptr [0] != '\0' && sptr [0] != quote)           /* while characters remain */
6010     if (decode && sptr [0] == '\\')                     /* if decoding and an escape sequence follows */
6011         if (sptr [1] == quote || sptr [1] == '\\') {    /*   then if it is a quote or backslash escape */
6012             sptr++;                                     /*     then skip over the sequence identifier */
6013             *dptr++ = *sptr++;                          /*       and copy the escaped character verbatim */
6014             }
6015 
6016         else if (sptr [1] == 'r' || sptr [1] == 'R') {  /* otherwise if it is a carriage return escape */
6017             sptr = sptr + 2;                            /*   then skip the escape pair */
6018             *dptr++ = CR;                               /*     and substitute a CR */
6019             }
6020 
6021         else if (sptr [1] == 'n' || sptr [1] == 'N') {  /* otherwise if it is a line feed escape */
6022             sptr = sptr + 2;                            /*   then skip the escape pair */
6023             *dptr++ = LF;                               /*     and substitute a LF */
6024             }
6025 
6026         else if (isdigit (sptr [1])) {                  /* otherwise if it's a numeric escape */
6027             sptr++;                                     /*   then skip over the sequence identifier */
6028 
6029             for (i = octal = 0; i < 3; i++)             /* look for three octal digits */
6030                 if (*sptr >= '0' && *sptr <= '7')       /* if it's an octal digit */
6031                     octal = octal * 8 + *sptr++ - '0';  /*   then accumulate the value */
6032                 else                                    /* otherwise */
6033                     break;                              /*   the escape is invalid */
6034 
6035             if (i == 3 && octal <= DEL)                 /* if the result is valid */
6036                 *dptr++ = (char) octal;                 /*   then copy the escaped value */
6037             else                                        /* otherwise */
6038                 return NULL;                            /*   the numeric escape is invalid */
6039             }
6040 
6041         else                                            /* otherwise the escape is unrecognized */
6042             *dptr++ = *sptr++;                          /*   so copy the character verbatim */
6043 
6044     else if (upshift)                                   /* otherwise if case conversion is requested */
6045         *dptr++ = (char) toupper (*sptr++);             /*   then copy the character and upshift */
6046     else                                                /* otherwise */
6047         *dptr++ = *sptr++;                              /*   copy the character verbatim */
6048 
6049 *dptr = '\0';                                           /* terminate the destination buffer */
6050 
6051 if (sptr [0] == '\0')                                   /* if we did not see a closing quotation mark */
6052     return NULL;                                        /*   then the string is invalid */
6053 
6054 else {                                                  /* otherwise */
6055     while (isspace (*++sptr));                          /*   discard any trailing spaces */
6056 
6057     return sptr;                                        /* return a pointer to the remainder of the source string */
6058     }
6059 }
6060 
6061 
6062 /* Parse a timing clause.
6063 
6064    This routine parses clauses of the form:
6065 
6066      <keyword> <count>
6067      <keyword> <time> <units>
6068      <time> <units>
6069 
6070    ...where <keyword> is an optional keyword prefixing the clause, and <count>
6071    is an unsigned decimal count of event ticks if <units> is omitted, or an
6072    unsigned time interval if <units> is present, which may be MICROSECOND[S],
6073    MILLISECOND[S], or SECOND[S].  The abbreviations USEC[S] and MSEC[S] may be
6074    used interchangeably for the first two units, respectively.
6075 
6076    The routine is called to parse two types of timing clauses.
6077 
6078    If a keyword such as "DELAY" is specified, the clause is parsed and converted
6079    to a count of event ticks.  The delay can be either an explicit count of
6080    event ticks or a real-time interval that is converted into the equivalent
6081    event tick count. For the latter, the count is scaled by the time units and
6082    the number of event ticks per second defined by the VM.
6083 
6084    If a keyword is not specified, a time interval is parsed and converted to a
6085    count of milliseconds.
6086 
6087    On entry, "keyword" points at a string containing the leading keyword
6088    expected, or NULL if no keyword precedes the timing clause, "cptr" points at
6089    a pointer to the input string, and "ticks" points at an integer variable that
6090    will receive the time value.  If the keyword is specified, but the input
6091    string does not begin with it, SCPE_FMT is returned.  If the keyword is
6092    specified and matches, or if the keyword is not specified, the following
6093    count is converted to a number.  A parsing error returns SCPE_ARG.
6094 
6095    If the value is acceptable, then if a delay is requested but no time units
6096    are present, then "ticks" is set to the value and SCPE_OK is returned (no
6097    time units implies that the value represents the number of event ticks).  If
6098    a time is requested, then a time unit is required.  A missing unit sets
6099    SCPE_2FARG, while specifying an invalid unit returns SCPE_ARG.
6100 
6101    With a valid unit, the <count> value is scaled by the time unit and, for a
6102    delay, by the number of instructions executed per second.  If the VM never
6103    set the execution rate, SCPE_NOFNC is returned, while if the tick count is
6104    too large, SCPE_ARG is returned.  A completed parse sets "ticks" to the
6105    scaled event tick count and returns SCPE_OK.
6106 */
6107 
parse_time(char * keyword,char ** cptr,int32 * ticks)6108 static t_stat parse_time (char *keyword, char **cptr, int32 *ticks)
6109 {
6110 typedef struct {                                /* the time factor descriptor */
6111     const char  *unit;                          /*   the time units name */
6112     uint32      scale;                          /*   the time scale factor */
6113     } FACTOR;
6114 
6115 static const FACTOR units [] = {                /* the list of acceptable time units */
6116 /*         Unit        Scale Factor */
6117 /*    ---------------  ------------ */
6118     { "MICROSECONDS",  1000000,     },
6119     { "USECS",         1000000,     },
6120     { "MILLISECONDS",  1000,        },
6121     { "MSECS",         1000,        },
6122     { "SECONDS",       1,           },
6123     { NULL,            0,           }
6124     };
6125 
6126 const FACTOR *fptr;
6127 char     vbuf [CBUFSIZE];
6128 t_uint64 time;
6129 t_stat   status;
6130 
6131 if (keyword != NULL)                                    /* if the keyword is specified */
6132     if (*keyword != toupper (**cptr))                   /*   then if the expected keyword is not present */
6133         return SCPE_FMT;                                /*     then report a formatting error */
6134 
6135     else {                                              /* otherwise */
6136         *cptr = get_glyph (*cptr, vbuf, 0);             /*   parse out the next glyph */
6137 
6138         if (strcmp (vbuf, keyword) != 0)                /* if this is not the expected keyword */
6139             return SCPE_FMT;                            /*   then report a formatting error */
6140         }
6141 
6142 if (**cptr == '\0')                                     /* if nothing follows */
6143     return SCPE_2FARG;                                  /*   then report a missing argument */
6144 
6145 else {                                                  /* otherwise */
6146     *cptr = get_glyph (*cptr, vbuf, 0);                 /*   get the count */
6147 
6148     time = get_uint (vbuf, 10, INT_MAX, &status);       /* parse the number */
6149 
6150     if (status != SCPE_OK)                              /* if the parse failed */
6151         return status;                                  /*   then return the error status */
6152 
6153     else {                                              /* otherwise */
6154         if (**cptr != '\0') {                           /*   if something follows */
6155             *cptr = get_glyph (*cptr, vbuf, 0);         /*     then get the next glyph */
6156 
6157             for (fptr = units; fptr->unit != NULL; fptr++)  /* search the units table */
6158                 if (MATCH_CMD (vbuf, fptr->unit) == 0)      /*   for a match */
6159                     break;                                  /*     and quit if one is found */
6160 
6161             if (fptr->scale == 0)                       /* if the unit is not valid */
6162                 return SCPE_ARG;                        /*   then report a bad argument */
6163 
6164             else if (keyword == NULL)                           /* otherwise if this is a time request */
6165                 time = (t_uint64) time * 1000 / fptr->scale;    /*   then scale the result to milliseconds */
6166 
6167             else if (ex_sim_execution_rate == 0)        /* otherwise if the VM did not set the execution rate */
6168                 return SCPE_NOFNC;                      /*   then the command is not allowed */
6169 
6170             else                                        /* otherwise calculate the delay in event ticks */
6171                 time = (t_uint64) time * ex_sim_execution_rate / fptr->scale;
6172             }
6173 
6174         else if (keyword == NULL)                       /* otherwise if this is a time request */
6175             return SCPE_2FARG;                          /*   then a time unit is required */
6176 
6177         if (time > INT_MAX)                             /* if the time value is too large */
6178             return SCPE_ARG;                            /*   then report a bad argument */
6179 
6180         else {                                          /* otherwise */
6181             *ticks = (int32) time;                      /*   return the tick count */
6182             return SCPE_OK;                             /*     and indicate success */
6183             }
6184         }
6185     }
6186 }
6187 
6188 
6189 /* Encode a string for printing.
6190 
6191    This routine encodes a string containing control characters into the
6192    equivalent escaped form, surrounded by quote marks.  On entry, "source"
6193    points at the string to encode.  Embedded quotes and control characters are
6194    replaced with their escaped counterparts.  The return value points at an
6195    internal static buffer containing the encoded string, as the routine is
6196    intended to be called from a print function and so used immediately.
6197 
6198    The supported escapes are the same as those parsed for quoted strings.
6199 
6200 
6201    Implementation notes:
6202 
6203     1. To avoid dealing with buffer overflows, we declare an output buffer large
6204        enough to accommodate the largest decoded input string.  This would be a
6205        string where each character is a control character requiring four
6206        encoding characters in the output buffer (plus three more for the
6207        surrounding quotes and the trailing NUL).  Consequently, we need not
6208        check for the end of the buffer while encoding.
6209 */
6210 
encode(const char * source)6211 static char *encode (const char *source)
6212 {
6213 static char encoding [CBUFSIZE * 4 + 3];                /* ensure there is always enough space */
6214 char   *eptr;
6215 
6216 encoding [0] = '"';                                     /* start with a leading quote */
6217 eptr = encoding + 1;                                    /*   and point at the next character */
6218 
6219 while (*source != '\0') {                               /* while source characters remain */
6220     if (iscntrl (*source)                               /* if the next character is a control character */
6221       || *source == '"' || *source == '\\') {           /*   or is a quote or backslash */
6222         *eptr++ = '\\';                                 /*     then escape it */
6223 
6224         if (*source == '\r')                            /* if it's a CR character */
6225             *eptr++ = 'r';                              /*   then replace it with the \r sequence */
6226 
6227         else if (*source == '\n')                       /* otherwise if it's an LF character */
6228             *eptr++ = 'n';                              /*   then replace it with the \n sequence */
6229 
6230         else if (*source == '"' || *source == '\\')     /* otherwise if it's a quote or backslash character */
6231             *eptr++ = *source;                          /*   then replace it with the \" or \\ sequence */
6232 
6233         else {                                          /* otherwise */
6234             sprintf (eptr, "%03o", (int) *source);      /*   replace it */
6235             eptr = eptr + 3;                            /*      with the \ooo sequence */
6236             }
6237         }
6238 
6239     else                                                /* otherwise it's a normal character */
6240         *eptr++ = *source;                              /*   so copy it verbatim */
6241 
6242     source++;                                           /* bump the source pointer */
6243     }                                                   /*   and continue until all characters are encoded */
6244 
6245 *eptr++ = '"';                                          /* add a closing quote */
6246 *eptr   = '\0';                                         /*   and terminate the string */
6247 
6248 return encoding;                                        /* return a pointer to the encoded string */
6249 }
6250