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