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