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