mirror of
https://github.com/open-simh/simh.git
synced 2026-01-11 23:53:30 +00:00
5727 lines
282 KiB
C
5727 lines
282 KiB
C
/* sim_extension.c: SCP extension routines
|
|
|
|
Copyright (c) 2019-2020, J. David Bryan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of the author shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from the author.
|
|
|
|
21-Oct-20 JDB A NULL poll pointer is now valid for MUX attach and detach
|
|
20-Oct-20 JDB Added FLUSH command to post file contents to disc
|
|
25-Aug-20 JDB Call of "vm_sim_vm_init" is now conditional on USE_VM_INIT
|
|
03-Aug-20 JDB IF now retains remainder of breakpoint command list
|
|
06-Jul-20 JDB Silence spurious compiler warning in "ex_if_cmd"
|
|
14-Feb-20 JDB First release version
|
|
18-Mar-19 JDB Created
|
|
|
|
|
|
This module implements extensions to the base Simulation Control Program
|
|
(SCP) front end for SIMH version 3.x. The current extensions are:
|
|
|
|
- host serial port support for the console and terminal multiplexers
|
|
|
|
- automated prompt/response processing, initially for the system console,
|
|
but extendable to other keyboard/display units
|
|
|
|
- concurrent console mode to enter SCP commands without stopping simulation
|
|
|
|
- work-alikes for a subset of the SCP 4.x commands
|
|
|
|
- execution of a global initialization file at simulator startup
|
|
|
|
This module, and its associated declarations file (sim_extension.h) act as
|
|
shims between the front end and a simulator-specific back end, such as the HP
|
|
2100 or HP 3000 simulator. Each simulator back end must have this inclusion:
|
|
|
|
#include "sim_extension.h"
|
|
|
|
...placed in the "<sim>_defs.h" file that is included by all back-end
|
|
modules, and all back-end modules must be compiled with the inclusion. Also,
|
|
this module (sim_extension.c) must be compiled and linked with the other SCP
|
|
and back-end modules.
|
|
|
|
Extending SCP is possible by the use of hooks and shims. SCP 3.x provides a
|
|
number of replaceable function and variable pointers (the "hooks") that may
|
|
be altered to implement custom behavior. Hooks are necessary where the
|
|
internal behavior of the SCP must change -- for example, when adding new
|
|
command executors to the original table of commands. A one-time initializer
|
|
within this module is called by SCP at simulator startup. This initializer
|
|
points the desired hooks at functions within this module to implement the
|
|
extended actions.
|
|
|
|
To extend the capability at the interface between the SCP front end and the
|
|
simulator-specific back end, shims are used to intercept the calls between
|
|
them. One call from the front end to the back end -- "sim_instr" -- is
|
|
intercepted. All of the other shims are for calls from the back end to
|
|
front-end services or global variables set by the back end. The general
|
|
mechanism is to use a macro to rename a given function identifier within the
|
|
context of the back end. The new name refers to the extension shim, which,
|
|
internally, may call the original function. For example, "sim_extension.h"
|
|
includes this macro:
|
|
|
|
#define sim_putchar ex_sim_putchar
|
|
|
|
...which is included during the back-end compilation. Therefore, a back-end
|
|
module that called "sim_putchar" would actually be compiled to call
|
|
"ex_sim_putchar", a function within this module, instead. Within the shim,
|
|
the macro substitution is not done, so a call to "sim_putchar" calls the
|
|
front-end function.
|
|
|
|
|
|
The following shims are provided:
|
|
|
|
Shimmed Routine Source Module Shimming Routine
|
|
================ ============= =================
|
|
tmxr_poll_conn sim_tmxr.c ex_tmxr_poll_conn
|
|
|
|
sim_putchar sim_console.c ex_sim_putchar
|
|
sim_putchar_s sim_console.c ex_sim_putchar_s
|
|
sim_poll_kbd sim_console.c ex_sim_poll_kbd
|
|
|
|
sim_brk_test scp.c ex_sim_brk_test
|
|
|
|
sim_instr hp----_cpu.c vm_sim_instr
|
|
sim_vm_init hp----_sys.c vm_sim_vm_init
|
|
sim_vm_cmd hp----_sys.c vm_sim_vm_cmd
|
|
|
|
|
|
The following extensions are provided:
|
|
|
|
Extension Routine Module Extended Extended Action
|
|
================== =============== ===================================
|
|
tmxr_attach_unit sim_tmxr.c Attach a network or serial port
|
|
tmxr_detach_unit sim_tmxr.c Detach a network or serial port
|
|
tmxr_detach_line sim_tmxr.c Detach a serial port
|
|
tmxr_control_line sim_tmxr.c Control a line
|
|
tmxr_line_status sim_tmxr.c Get a line's status
|
|
tmxr_line_free sim_tmxr.c Check if a line is disconnected
|
|
tmxr_mux_free sim_tmxr.c Check if all lines are disconnected
|
|
|
|
|
|
The following extension hooks are provided:
|
|
|
|
Hook Name Hook Description
|
|
====================== ===========================
|
|
vm_console_input_unit Console input unit pointer
|
|
vm_console_output_unit Console output unit pointer
|
|
|
|
|
|
The following SCP hooks are used:
|
|
|
|
Hook Name Source Module
|
|
================ =============
|
|
sim_vm_init scp.c
|
|
sim_vm_cmd scp.c
|
|
sim_vm_unit_name scp.c
|
|
sub_args scp.c
|
|
sim_get_radix scp.c
|
|
|
|
tmxr_read sim_tmxr.c
|
|
tmxr_write sim_tmxr.c
|
|
tmxr_show sim_tmxr.c
|
|
tmxr_close sim_tmxr.c
|
|
tmxr_is_extended sim_tmxr.c
|
|
|
|
The extension hooks should be set by the back end to point at the console
|
|
units. They are used to identify the console units for the REPLY and BREAK
|
|
commands.
|
|
|
|
The VM-specific SCP hooks may be set as usual by the back end. The extension
|
|
module intercepts these but ensures that they are called as part of extension
|
|
processing, so their effects are maintained.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the base set of options to the SHOW CONSOLE command is changed in
|
|
"scp.c", the "ex_show_console" routine below must be changed as well.
|
|
See the routine's implementation note for details.
|
|
|
|
2. The existing SCP command table model presents some difficulties when
|
|
extending the command set. When adding new commands that begin with the
|
|
same letter(s) as existing commands, the existing commands must be
|
|
duplicated in the extension table before the new ones. This is required
|
|
to preserve the standard command abbreviation behavior when the
|
|
extension table is searched first. For example, when adding a new CLEAR
|
|
command, the existing CONTINUE command must be duplicated and placed
|
|
ahead of the CLEAR entry, so that entering "C" invokes CONTINUE and not
|
|
CLEAR. But duplicating the CONTINUE entry introduces the potential for
|
|
error if the standard CONTINUE entry is changed. This means that
|
|
duplicated entries must be copied dynamically, which then means that the
|
|
new table cannot be a constant.
|
|
|
|
Also, in some standard and extension cases, additional actions may need
|
|
to be taken for specific commands. The method of identifying the
|
|
command by testing the "action" field of the resulting CTAB entry does
|
|
not work if the command has been replaced. For instance, testing
|
|
"cmdp->action == &do_cmd" doesn't work if the DO command was replaced.
|
|
The only reliable way is to test the "name" field for the expected
|
|
command name string, i.e., testing "strcmp (cmdp->name, "DO") == 0",
|
|
which is both slow and awkward.
|
|
|
|
Further, new commands may be valid only in certain contexts, e.g., in a
|
|
command file or only during some specific mode of execution. There is
|
|
no easy way of passing context to the command executor, except by global
|
|
variables, as all executors receive the same parameters (an integer
|
|
argument and a character pointer).
|
|
|
|
In short, we have to jump through some hoops in implementing the command
|
|
extensions. These are described in the comments associated with their
|
|
appearances.
|
|
*/
|
|
|
|
|
|
|
|
#define COMPILING_EXTENSIONS /* defined to prevent shimming in sim_extension.h */
|
|
|
|
#include <ctype.h>
|
|
#include <float.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "sim_defs.h"
|
|
#include "sim_serial.h"
|
|
#include "sim_extension.h"
|
|
|
|
|
|
|
|
/* The following pragmas quell clang and Microsoft Visual C++ warnings that are
|
|
on by default but should not be, in my opinion. They warn about the use of
|
|
perfectly valid code and require the addition of redundant parentheses and
|
|
braces to silence them. Rather than clutter up the code with scores of extra
|
|
symbols that, in my view, make the code harder to read and maintain, I elect
|
|
to suppress these warnings.
|
|
|
|
VC++ 2008 warning descriptions:
|
|
|
|
- 4114: "same type qualifier used more than once" [legal per C99]
|
|
|
|
- 4554: "check operator precedence for possible error; use parentheses to
|
|
clarify precedence"
|
|
|
|
- 4996: "function was declared deprecated"
|
|
*/
|
|
|
|
#if defined (__clang__)
|
|
|
|
#pragma clang diagnostic ignored "-Wlogical-not-parentheses"
|
|
#pragma clang diagnostic ignored "-Wlogical-op-parentheses"
|
|
#pragma clang diagnostic ignored "-Wbitwise-op-parentheses"
|
|
#pragma clang diagnostic ignored "-Wshift-op-parentheses"
|
|
#pragma clang diagnostic ignored "-Wdangling-else"
|
|
|
|
#elif defined (_MSC_VER)
|
|
|
|
#pragma warning (disable: 4114 4554 4996)
|
|
|
|
#endif
|
|
|
|
|
|
/* MSVC does not have the C-standard "snprintf" function */
|
|
|
|
#if defined (_MSC_VER)
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
|
|
/* Character constants (as integers) */
|
|
|
|
#define BS 0010
|
|
#define CR 0015
|
|
#define LF 0012
|
|
#define ESC 0033
|
|
#define DEL 0177
|
|
|
|
|
|
/* Flags for restricted-use commands */
|
|
|
|
#define EX_GOTO 0
|
|
#define EX_CALL 1
|
|
#define EX_RETURN 2
|
|
#define EX_ABORT 3
|
|
|
|
|
|
/* External variable declarations (actually should be in sim_console.h) */
|
|
|
|
extern TMXR sim_con_tmxr;
|
|
|
|
|
|
/* External routine declarations (actually should be in scp.h) */
|
|
|
|
extern t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
|
|
|
|
/* One-time extension initializer */
|
|
|
|
static void ex_initialize (void);
|
|
|
|
|
|
/* Hooks provided by SCP */
|
|
|
|
void (*sim_vm_init) (void) = ex_initialize; /* use our one-time initializer */
|
|
|
|
|
|
/* Hooks provided by us for use by the back end virtual machine */
|
|
|
|
CTAB *vm_sim_vm_cmd = NULL; /* VM command extension table */
|
|
UNIT *vm_console_input_unit = NULL; /* console input unit pointer */
|
|
UNIT *vm_console_output_unit = NULL; /* console output unit pointer */
|
|
|
|
|
|
/* Pointer to the VM handler for unit names */
|
|
|
|
static char * (*vm_unit_name_handler) (const UNIT *uptr) = NULL;
|
|
|
|
|
|
|
|
/* Extended terminal multiplexer line descriptor.
|
|
|
|
This structure extends the TMLN structure defined by the multiplexer library
|
|
to enable serial port support. The TMLN structure contains a void extension
|
|
pointer, "exptr", which will be initialized to NULL by the line descriptor
|
|
declarations in the various multiplexer simulators. For lines controlled by
|
|
extension routines, this pointer is changed to point at an EX_TMLN extension
|
|
structure, which is defined below.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The name of the serial port is kept in an allocated buffer and referenced
|
|
by the UNIT's "filename" pointer. The "sername" pointer points at the
|
|
same buffer; it is needed only to permit the "ex_tmxr_show" routine to
|
|
print the name when given the TMLN structure. This pointer must NOT be
|
|
freed; the buffer is deallocated by freeing the "filename" pointer.
|
|
*/
|
|
|
|
typedef struct { /* extended line descriptor */
|
|
SERHANDLE serport; /* serial port handle */
|
|
char *sername; /* copy of the serial port name pointer */
|
|
t_bool controlled; /* TRUE if the modem lines are controlled */
|
|
TMCKT signals; /* modem control signals */
|
|
} EX_TMLN;
|
|
|
|
|
|
/* Pointers to the standard routines provided by the TMXR library */
|
|
|
|
static int32 (*tmxr_base_read) (TMLN *lp, int32 length);
|
|
static int32 (*tmxr_base_write) (TMLN *lp, int32 length);
|
|
static void (*tmxr_base_show) (TMLN *lp, FILE *stream);
|
|
static void (*tmxr_base_close) (TMLN *lp);
|
|
|
|
|
|
/* Hooked terminal multiplexer replacement routine declarations */
|
|
|
|
static int32 ex_tmxr_read (TMLN *lp, int32 length);
|
|
static int32 ex_tmxr_write (TMLN *lp, int32 length);
|
|
static void ex_tmxr_show (TMLN *lp, FILE *stream);
|
|
static void ex_tmxr_close (TMLN *lp);
|
|
static t_bool ex_tmxr_extended (TMLN *lp);
|
|
|
|
|
|
/* Local terminal multiplexer extension routine declarations */
|
|
|
|
static t_stat ex_tmxr_attach_line (TMXR *mp, UNIT *uptr, char *cptr);
|
|
static EX_TMLN *serial_line (TMLN *lp);
|
|
|
|
|
|
|
|
/* String breakpoint structure.
|
|
|
|
String breakpoints are implemented by shimming the terminal output routines
|
|
and matching each output character to a breakpoint string. A string
|
|
breakpoint structure holds the character string to be matched and some
|
|
additional data that defines how the breakpoint is handled. The structure is
|
|
populated by a BREAK command having a quoted string parameter. The structure
|
|
may exist only until the breakpoint occurs (a "temporary" breakpoint) or
|
|
until a NOBREAK command is issued to cancel it (a "permanent" breakpoint).
|
|
Consequently, a breakpoint may be reused and so much contain enough
|
|
information to allow the breakpoint to be reset after it triggers.
|
|
|
|
The set of active breakpoint structures are maintained in a linked list
|
|
headed by the "sb_list" global variable.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The structure contains some fields (e.g., "count") that are not used in
|
|
the initial implementation. They are present for future expansion.
|
|
|
|
2. String breakpoints are initially implemented for the system console only.
|
|
Future expansion to allow breakpoints on terminal multiplexer lines is
|
|
envisioned.
|
|
|
|
3. The trigger field contains the simulation global time at which a matched
|
|
breakpoint should trigger. It is set to -1 if the breakpoint has not yet
|
|
matched (i.e., is still pending).
|
|
*/
|
|
|
|
typedef struct sbnode { /* string breakpoint descriptor */
|
|
UNIT *uptr; /* output unit pointer */
|
|
char match [CBUFSIZE]; /* match string */
|
|
char *mptr; /* match pointer */
|
|
int32 type; /* mask of breakpoint types */
|
|
int32 count; /* proceed count */
|
|
int32 delay; /* trigger enable delay */
|
|
double trigger; /* trigger time */
|
|
char action [CBUFSIZE]; /* action string */
|
|
struct sbnode *next; /* pointer to the next entry in the list */
|
|
} STRING_BREAKPOINT;
|
|
|
|
typedef STRING_BREAKPOINT *SBPTR; /* the string breakpoint node pointer type */
|
|
|
|
static SBPTR sb_list = NULL; /* the pointer to the head of the breakpoint list */
|
|
|
|
#define BP_STRING (SWMASK ('_')) /* the default string breakpoint type */
|
|
|
|
|
|
/* String breakpoint local SCP support routines */
|
|
|
|
static char *breakpoint_name (const UNIT *uptr);
|
|
static t_stat breakpoint_service (UNIT *uptr);
|
|
|
|
|
|
/* String breakpoint SCP data structures */
|
|
|
|
/* Unit list */
|
|
|
|
static UNIT breakpoint_unit [] = {
|
|
/* Event Routine Unit Flags Capacity Delay */
|
|
/* ------------------- ---------- -------- ----- */
|
|
{ UDATA (&breakpoint_service, UNIT_IDLE, 0), 0 }
|
|
};
|
|
|
|
|
|
/* Reply structure.
|
|
|
|
Replies are implemented by shimming the terminal input routines and supplying
|
|
characters one-at-a-time from a response string. A reply structure holds the
|
|
character string to be supplied and some additional data that defines how the
|
|
reply is handled. The structure is populated by a REPLY command having a
|
|
quoted string parameter. The structure exists only until the reply is
|
|
completed.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Replies are initially implemented for the system console only. Future
|
|
expansion to allow replies on terminal multiplexer lines is envisioned.
|
|
|
|
2. Only a single reply may exist in the initial implementation. Multiple
|
|
replies for different devices would be implemented by a linked-list of
|
|
structures that are allocated dynamically. Initially, though, the
|
|
"reply" global points at a single static structure instead of the head of
|
|
a list of structures.
|
|
|
|
3. A reply is pending if "rptr" points at "reply [0]" and the current
|
|
simulation time is earlier than the "trigger" time.
|
|
*/
|
|
|
|
typedef struct rpnode { /* reply descriptor */
|
|
UNIT *uptr; /* input unit pointer */
|
|
char reply [CBUFSIZE]; /* reply string */
|
|
char *rptr; /* reply pointer */
|
|
double trigger; /* trigger time */
|
|
struct rpnode *next; /* pointer to the next entry in the list */
|
|
} REPLY;
|
|
|
|
typedef REPLY *RPPTR; /* the reply node pointer type */
|
|
|
|
static RPPTR rp_list = NULL; /* the pointer to the head of the reply list */
|
|
|
|
static REPLY rpx; /* the initial reply descriptor */
|
|
|
|
|
|
/* Local default break and reply delay declarations */
|
|
|
|
static int32 break_delay = 0;
|
|
static int32 reply_delay = 0;
|
|
|
|
|
|
/* Local string breakpoint extension routine declarations */
|
|
|
|
static void free_breakpoints (void);
|
|
static SBPTR find_breakpoint (char *match, SBPTR *prev);
|
|
static void test_breakpoint (int32 test_char);
|
|
|
|
|
|
/* Concurrent console mode status returns */
|
|
|
|
#define SCPE_EXEC (SCPE_LAST + 1) /* a command is ready to execute */
|
|
#define SCPE_ABORT (SCPE_LAST + 2) /* an ABORT command was entered */
|
|
|
|
|
|
/* Concurrent console mode enumerator */
|
|
|
|
typedef enum { /* keyboard mode enumerator */
|
|
Console, /* keystrokes are sent to the console */
|
|
Command /* keystrokes are sent to the command buffer */
|
|
} KEY_MODE;
|
|
|
|
|
|
/* Signal handler function type */
|
|
|
|
typedef void (*SIG_HANDLER) (int);
|
|
|
|
|
|
/* Concurrent console mode local variables */
|
|
|
|
static t_bool concurrent_mode = TRUE; /* the console mode, initially in Concurrent mode */
|
|
|
|
static KEY_MODE keyboard_mode = Console; /* the keyboard mode, initially delivered to the console */
|
|
|
|
static char cmd_buf [CBUFSIZE]; /* the concurrent console command buffer */
|
|
static char *cmd_ptr; /* the command buffer pointer */
|
|
static char *concurrent_do_ptr = NULL; /* the pointer to a concurrent DO command line */
|
|
|
|
static t_bool concurrent_run = FALSE; /* TRUE if the VM is executing in concurrent mode */
|
|
static t_bool stop_requested = FALSE; /* TRUE if a CTRL+E was entered by the user */
|
|
|
|
|
|
/* Concurrent console mode local routine declarations */
|
|
|
|
static void wru_handler (int sig);
|
|
static void put_string (const char *cptr);
|
|
static t_stat get_command (char *cptr, CTAB **cmdp);
|
|
|
|
|
|
|
|
/* Local command extension declarations */
|
|
|
|
static int32 ex_quiet = 0; /* a copy of the global "sim_quiet" setting */
|
|
|
|
|
|
/* Command handler function type */
|
|
|
|
typedef t_stat CMD_HANDLER (int32 flag, char *cptr);
|
|
|
|
|
|
/* Command extension handler function declarations */
|
|
|
|
static t_stat ex_break_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_reply_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_run_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_do_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_if_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_delete_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_flush_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_restricted_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_set_cmd (int32 flag, char *cptr);
|
|
static t_stat ex_show_cmd (int32 flag, char *cptr);
|
|
|
|
|
|
/* Command extension table.
|
|
|
|
This table defines commands and command behaviors that are specific to this
|
|
extension. Specifically:
|
|
|
|
* RUN and GO accept an UNTIL clause, and all forms provide a quiet DO mode
|
|
|
|
* BREAK and NOBREAK provide temporary and string breakpoints
|
|
|
|
* REPLY and NOREPLY provide console responses
|
|
|
|
* DO provides transfers to labels and quiet stops
|
|
|
|
* IF and GOTO add conditional command file execution
|
|
|
|
* DELETE adds platform-independent file purges
|
|
|
|
* CALL and RETURN add labeled subroutine invocations
|
|
|
|
* ABORT stops execution of the current and any nested DO command files
|
|
|
|
* SET adds ENVIRONMENT to define environment variables
|
|
and CONSOLE CONCURRENT/NOCONCURRENT to set the console concurrency mode
|
|
|
|
* SHOW adds string breakpoints to BREAK and adds REPLY for replies
|
|
|
|
The table is initialized with only those fields that differ from the standard
|
|
command table. During one-time initialization, empty or zero fields are
|
|
filled in from the corresponding standard command table entries. This
|
|
ensures that the extension table automatically picks up any changes to the
|
|
standard commands that it modifies.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The RESET and DEPOSIT commands are duplicated from the standard SCP
|
|
command table so that entering "R" doesn't invoke the RUN command and
|
|
entering "D" doesn't invoke the DO command. This would otherwise occur
|
|
because the extension command table is searched before the standard
|
|
command table. Similarly, the ATTACH, ASSIGN, and ASSERT commands are
|
|
duplicated so that entering "A" doesn't invoke the ABORT command.
|
|
|
|
2. The "execute_file" routine needs to do special processing for the RUN,
|
|
GO, STEP, CONTINUE, and BOOT commands. Unfortunately, there is no easy
|
|
way to determine these commands from their CTAB entries. We cannot
|
|
simply check the action routine pointers, because the VM may override
|
|
them, potentially with five different routines. We can't use special
|
|
argument values, because they are used by the standard RUN executor to
|
|
differentiate between the various commands (and they are not unique to
|
|
these commands). What we need is a VM extension field in the CTAB
|
|
structure, but there isn't one. So we rely on the hack that nothing in
|
|
3.x uses the "help_base" field, which was added for 4.x compatibility. We
|
|
therefore indicate a RUN, etc. command by setting the "help_base" field
|
|
non-null (the actual value is, so far, immaterial).
|
|
*/
|
|
|
|
static CTAB ex_cmds [] = {
|
|
/* Name Action Routine Argument Help String */
|
|
/* ---------- ----------------- ----------- ----------------------------------------------------------------- */
|
|
{ "RESET", NULL, 0, NULL },
|
|
{ "DEPOSIT", NULL, 0, NULL },
|
|
{ "ATTACH", NULL, 0, NULL },
|
|
{ "ASSIGN", NULL, 0, NULL },
|
|
{ "ASSERT", NULL, 0, NULL },
|
|
|
|
{ "RUN", ex_run_cmd, 0, NULL, "RUN" },
|
|
{ "GO", ex_run_cmd, 0, NULL, "RUN" },
|
|
{ "STEP", ex_run_cmd, 0, NULL, "RUN" },
|
|
{ "CONTINUE", ex_run_cmd, 0, NULL, "RUN" },
|
|
{ "BOOT", ex_run_cmd, 0, NULL, "RUN" },
|
|
|
|
{ "BREAK", ex_break_cmd, 0, NULL },
|
|
{ "NOBREAK", ex_break_cmd, 0, NULL },
|
|
|
|
{ "REPLY", ex_reply_cmd, 0, "reply <string> {<delay>} send characters to the console\n" },
|
|
{ "NOREPLY", ex_reply_cmd, 1, "noreply cancel a pending reply\n" },
|
|
|
|
{ "DO", ex_do_cmd, 1, NULL },
|
|
|
|
{ "IF", ex_if_cmd, 0, "if <cond> <cmd>;... execute commands if condition TRUE\n" },
|
|
{ "DELETE", ex_delete_cmd, 0, "del{ete} <file> delete a file\n" },
|
|
{ "FLUSH", ex_flush_cmd, 0, "f{lush} flush all open files to disc\n" },
|
|
|
|
{ "GOTO", ex_restricted_cmd, EX_GOTO, "goto <label> transfer control to the labeled line\n" },
|
|
{ "CALL", ex_restricted_cmd, EX_CALL, "call <label> {<par>...} call the labeled subroutine\n" },
|
|
{ "RETURN", ex_restricted_cmd, EX_RETURN, "return return control from a subroutine\n" },
|
|
{ "ABORT", ex_restricted_cmd, EX_ABORT, "abort abort nested command files\n" },
|
|
|
|
{ "SET", ex_set_cmd, 0, NULL },
|
|
{ "SHOW", ex_show_cmd, 0, NULL },
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
static const uint32 ex_cmd_count = sizeof ex_cmds / sizeof ex_cmds [0]; /* the count of commands in the table */
|
|
|
|
|
|
/* Standard front-end command handler pointer declarations */
|
|
|
|
static CMD_HANDLER *break_handler = NULL;
|
|
static CMD_HANDLER *run_handler = NULL;
|
|
static CMD_HANDLER *set_handler = NULL;
|
|
static CMD_HANDLER *show_handler = NULL;
|
|
|
|
|
|
/* Extended command handler pointer declarations */
|
|
|
|
static CMD_HANDLER *ex_do_handler = NULL;
|
|
|
|
|
|
/* SET/SHOW command extension handler routine declarations */
|
|
|
|
static t_stat ex_set_console (int32 flag, char *cptr);
|
|
static t_stat ex_set_environment (int32 flag, char *cptr);
|
|
static t_stat ex_set_concurrent (int32 flag, char *cptr);
|
|
static t_stat ex_set_serial (int32 flag, char *cptr);
|
|
|
|
static t_stat ex_show_console (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
static t_stat ex_show_break (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
static t_stat ex_show_reply (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
static t_stat ex_show_delays (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
static t_stat ex_show_concurrent (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
static t_stat ex_show_serial (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
|
|
|
|
|
|
/* Hooked command extension replacement routine declarations */
|
|
|
|
static void ex_substitute_args (char *iptr, char *optr, int32 bufsize, char *args []);
|
|
static int32 ex_get_radix (const char *cptr, int32 switches, int32 default_radix);
|
|
|
|
|
|
/* Local command extension routine declarations */
|
|
|
|
static t_stat execute_file (FILE *file, int32 flag, char *cptr);
|
|
static t_stat goto_label (FILE *stream, char *cptr);
|
|
static t_stat gosub_label (FILE *stream, char *filename, int32 flag, char *cptr);
|
|
static void replace_token (char **out_ptr, int32 *out_size, char *token_ptr);
|
|
static void copy_string (char **target, int32 *target_size, const char *source, int32 source_size);
|
|
static char *parse_quoted_string (char *sptr, char *dptr, t_bool upshift);
|
|
static t_stat parse_delay (char **cptr, int32 *delay);
|
|
static char *encode (const char *source);
|
|
|
|
|
|
|
|
/* ********************* Extension Module Initializer ************************
|
|
|
|
This routine is called once by the SCP startup code. It fills in the
|
|
extension command table from the corresponding system command table entries,
|
|
saves pointers to the original system command handlers where needed, and
|
|
installs the extension command table and argument substituter.
|
|
|
|
If the VM defines an initializer, it is called. Then if the VM set up its
|
|
own command table, this routine merges the two auxiliary tables, ensuring
|
|
that any VM-defined commands override the corresponding extension commands.
|
|
This is required because SCP only allows a single user-specified command
|
|
table.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "ex_cmd_count" includes the NULL entry at the end of the extension
|
|
command table, so adding in any VM command table entries gives the total
|
|
command count plus one for the NULL entry, as is needed for the correct
|
|
memory allocation size.
|
|
|
|
2. Because the initializer is a void function, it cannot return an
|
|
indication that the memory allocation failed. If it did, the VM-defined
|
|
command table will be ignored.
|
|
|
|
3. For those overriding extension commands that will call the system command
|
|
handlers (e.g., BREAK), we save the action routine pointers before
|
|
installing the extension command table. Similarly, those overriding VM
|
|
commands that intend to call the system command handlers will save the
|
|
corresponding action pointers. However, because the extension table is
|
|
installed before calling the VM initializer, the VM's action pointers
|
|
will point at the extension command handlers. So, for example, a BREAK
|
|
command calls the VM BREAK handler, which in turn calls the extension
|
|
BREAK handler, which in turn calls the system BREAK handler.
|
|
*/
|
|
|
|
static void ex_initialize (void)
|
|
{
|
|
uint32 cmd_count;
|
|
CTAB *systab, *vmtab, *extab = ex_cmds;
|
|
|
|
while (extab->name != NULL) { /* loop through the extension command table */
|
|
systab = find_cmd (extab->name); /* find the corresponding system command table entry */
|
|
|
|
if (systab != NULL) { /* if it is present */
|
|
if (extab->action == NULL) /* then if the action routine field is empty */
|
|
extab->action = systab->action; /* then fill it in */
|
|
|
|
if (extab->arg == 0) /* if the command argument field is empty */
|
|
extab->arg = systab->arg; /* then fill it in */
|
|
|
|
if (extab->help == NULL) /* if the help string field is empty */
|
|
extab->help = systab->help; /* then fill it in */
|
|
|
|
if (extab->help_base == NULL) /* if the help base string field is empty */
|
|
extab->help_base = systab->help_base; /* then fill it in */
|
|
|
|
extab->message = systab->message; /* fill in the message field as we never override it */
|
|
}
|
|
|
|
extab++; /* point at the next table entry */
|
|
}
|
|
|
|
break_handler = find_cmd ("BREAK")->action; /* set up the BREAK/NOBREAK command handler */
|
|
run_handler = find_cmd ("RUN")->action; /* and the RUN/GO command handler */
|
|
set_handler = find_cmd ("SET")->action; /* and the SET command handler */
|
|
show_handler = find_cmd ("SHOW")->action; /* and the SHOW command handler */
|
|
|
|
sim_vm_cmd = ex_cmds; /* set up the extension command table */
|
|
sub_args = ex_substitute_args; /* and argument substituter */
|
|
sim_get_radix = ex_get_radix; /* and EX/DEP/SET radix configuration */
|
|
|
|
#if defined (USE_VM_INIT)
|
|
if (vm_sim_vm_init != NULL) /* if the VM has a one-time initializer */
|
|
vm_sim_vm_init (); /* then call it now */
|
|
#endif
|
|
|
|
vm_unit_name_handler = sim_vm_unit_name; /* save the unit name hook in case the VM set it */
|
|
sim_vm_unit_name = breakpoint_name; /* and substitute our own */
|
|
|
|
if (vm_sim_vm_cmd != NULL) { /* if the VM defines its own command table */
|
|
cmd_count = ex_cmd_count; /* then extension table entry count */
|
|
|
|
for (vmtab = vm_sim_vm_cmd; vmtab->name != NULL; vmtab++) /* add the number of VM command entries */
|
|
cmd_count = cmd_count + 1; /* to the number of extension entries */
|
|
|
|
systab = (CTAB *) calloc (cmd_count, sizeof (CTAB)); /* allocate a table large enough to hold all entries */
|
|
|
|
if (systab != NULL) { /* if the allocation succeeded */
|
|
memcpy (systab, ex_cmds, sizeof ex_cmds); /* then populate the extension commands first */
|
|
|
|
for (vmtab = vm_sim_vm_cmd; vmtab->name != NULL; vmtab++) { /* for each VM command */
|
|
for (extab = systab; extab->name != NULL; extab++) /* if it overrides */
|
|
if (strcmp (extab->name, vmtab->name) == 0) { /* an extension command */
|
|
memcpy (extab, vmtab, sizeof (CTAB)); /* then replace the extension entry */
|
|
break; /* with the VM entry */
|
|
}
|
|
|
|
if (extab->name == NULL) /* if the VM command does not match an extension command */
|
|
memcpy (extab, vmtab, sizeof (CTAB)); /* then add it to the end of the table */
|
|
}
|
|
|
|
sim_vm_cmd = systab; /* install the combined VM and extension table */
|
|
}
|
|
}
|
|
|
|
ex_do_handler = find_cmd ("DO")->action; /* get the address of the extended DO command handler */
|
|
|
|
ex_quiet = sim_quiet; /* save the global quietness setting */
|
|
|
|
tmxr_base_read = tmxr_read; /* get the dedicated socket reader */
|
|
tmxr_read = ex_tmxr_read; /* and replace it with the generic reader */
|
|
|
|
tmxr_base_write = tmxr_write; /* do the same */
|
|
tmxr_write = ex_tmxr_write; /* for the generic writer */
|
|
|
|
tmxr_base_show = tmxr_show; /* and the same */
|
|
tmxr_show = ex_tmxr_show; /* for the generic show */
|
|
|
|
tmxr_base_close = tmxr_close; /* and the same */
|
|
tmxr_close = ex_tmxr_close; /* for the generic closer */
|
|
|
|
tmxr_is_extended = ex_tmxr_extended; /* install the extension detection hook */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* ******************** Terminal Multiplexer Extensions **********************
|
|
|
|
This module extends the following existing routines in "sim_tmxr.c":
|
|
|
|
tmxr_poll_conn -- poll for new network or serial connections
|
|
|
|
...and adds the following new routines:
|
|
|
|
tmxr_attach_unit -- attach a network or serial port
|
|
tmxr_detach_unit -- detach a network or serial port
|
|
tmxr_detach_line -- detach a serial port
|
|
tmxr_control_line -- control a line
|
|
tmxr_line_status -- get a line' status
|
|
tmxr_line_free -- return TRUE if a specified line is disconnected
|
|
tmxr_mux_free -- return TRUE if all lines and network port are disconnected
|
|
|
|
The module implementation requires that multiline multiplexers define a unit
|
|
per line and that unit numbers correspond to line numbers.
|
|
|
|
Multiplexer lines may be connected to terminal emulators supporting the
|
|
Telnet protocol via sockets, or to hardware terminals via host serial ports.
|
|
Concurrent Telnet and serial connections may be mixed on multiline
|
|
multiplexers.
|
|
|
|
When connecting via serial ports, individual multiplexer lines are attached
|
|
to specific host ports using port names appropriate for the host system:
|
|
|
|
sim> attach MUX2 com1 (or /dev/ttyS0)
|
|
|
|
Serial port parameters may be optionally specified:
|
|
|
|
sim> attach MUX2 com1;9600-8n1
|
|
|
|
If the port parameters are omitted, then the host system defaults for the
|
|
specified port are used. The port is allocated during the attach call, but
|
|
the actual connection is deferred until the multiplexer is polled for
|
|
connections, as with Telnet connections.
|
|
|
|
Individual lines may be disconnected from serial ports with:
|
|
|
|
sim> detach MUX2
|
|
|
|
Telnet and serial port connections may be dropped with:
|
|
|
|
sim> set MUX2 DISCONNECT
|
|
|
|
This will disconnect a Telnet client and will drop the Data Terminal Ready
|
|
(DTR) signal on a serial port for 500 milliseconds. The serial port remains
|
|
attached to the line.
|
|
|
|
|
|
Single-line devices may be attached either to a Telnet listening port or to a
|
|
serial port. The device attach routine may be passed either a port number or
|
|
a serial port name. This routine calls "tmxr_attach_unit". If the return
|
|
value is SCPE_OK, then a Telnet port number or serial port name was passed
|
|
and was opened. Otherwise, the attachment failed, and the returned status
|
|
code value should be reported.
|
|
|
|
The device detach routine calls "tmxr_detach_unit" to close either the Telnet
|
|
listening port or the serial port.
|
|
|
|
Multi-line devices with a unit per line and a separate poll unit attach
|
|
serial ports to the former and a Telnet listening port to the latter. Both
|
|
types of attachments may be made concurrently. The system ATTACH and DETACH
|
|
commands are used, for example:
|
|
|
|
sim> attach MUX 1050 -- attach the listening port
|
|
sim> attach MUX0 com1 -- attach a serial port
|
|
|
|
SCP passes the same pointer to unit 0 in both cases. However, the SCP global
|
|
variable "sim_ref_type" will indicate whether the device (REF_DEVICE) or a
|
|
unit (REF_UNIT) was specified. In the cases of a RESTORE or a DETACH ALL,
|
|
where the user does not specify a device or unit, "sim_ref_type" will be set
|
|
to REF_NONE.
|
|
|
|
After a line is detached, the device simulator should clear the "rcve" field
|
|
of the associated line descriptor. However, detaching a listening port will
|
|
disconnect all active Telnet lines but will not disturb any serial lines.
|
|
Consequently, the device simulator cannot determine whether or not a
|
|
multiplexer is active solely by examining the UNIT_ATT flag on the poll unit.
|
|
Instead, the "tmxr_line_free" routine should be called for each line, and
|
|
reception on the line should be inhibited if the routine returns TRUE.
|
|
|
|
Finally, the "tmxr_mux_free" routine should be called to determine if the
|
|
multiplexer is now free (i.e., the listening port is detached and no other
|
|
serial connections exist). If the routine returns TRUE, the poll service may
|
|
be stopped.
|
|
|
|
The "tmxr_attach_unit" and "tmxr_detach_unit" decide which type of port to
|
|
use by examining the UNIT_ATTABLE flag on the supplied unit. If the flag is
|
|
present, then a Telnet port attach/detach is attempted. If the flag is
|
|
missing, then a serial port is assumed. Multiline multiplexers therefore
|
|
will specify UNIT_ATTABLE on the poll unit and not on the line units.
|
|
|
|
Serial port connections are only supported on multiplexer lines that appear
|
|
in the line connection order array. If the line is not present, or the
|
|
default value (i.e., -1 indicating all lines are to be connected) is not set,
|
|
the attachment is rejected with a "Unit not attachable" error.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The system RESTORE command does not restore devices having the DEV_NET
|
|
flag. This flag indicates that the device employs host-specific port
|
|
names that are non-transportable across RESTOREs.
|
|
|
|
If a multiplexer specifies DEV_NET, the device connection state will not
|
|
be altered when a RESTORE is done. That is, all current connections,
|
|
including Telnet sessions, will remain untouched, and connections
|
|
specified at the time of the SAVE will not be reestablished during the
|
|
RESTORE. If DEV_NET is not specified, then the system will attempt to
|
|
restore the attachment state present at the time of the SAVE, including
|
|
Telnet listening and serial ports. Telnet client sessions on individual
|
|
multiplexer lines cannot be reestablished by RESTORE and must be
|
|
reestablished manually.
|
|
|
|
2. Single-line multiplexers must set or clear UNIT_ATTABLE on the unit
|
|
representing the line dynamically, depending on whether a numeric
|
|
(listening port) or non-numeric (serial port) value is specified.
|
|
Multiline unit-per-line multiplexers should not have UNIT_ATTABLE on the
|
|
units representing the lines. UNIT_ATTABLE does not affect the
|
|
attachability when VM-specific attach routines are employed, although it
|
|
does determine which type of port is to be attached. UNIT_ATTABLE also
|
|
controls the reporting of attached units for the SHOW <dev> command.
|
|
|
|
A single-line device will be either detached, attached to a listening
|
|
port, or attached to a serial port. With UNIT_ATTABLE, the device will
|
|
be reported as "not attached," "attached to 1050" (e.g.), or "attached to
|
|
COM1" (e.g.), which is desirable.
|
|
|
|
A unit-per-line device will report the listening port as attached to the
|
|
device (or to a separate device). The units representing lines either
|
|
will be connected to a Telnet session or attached to a serial port.
|
|
Telnet sessions are not reported by SHOW <dev>, so having UNIT_ATTABLE
|
|
present will cause each non-serial line to be reported as "not attached,"
|
|
even if there may be a current Telnet connection. This will be confusing
|
|
to users. Without UNIT_ATTABLE, attachment status will be reported only
|
|
if the line is attached to a serial port, which is preferable.
|
|
*/
|
|
|
|
|
|
/* Global terminal multiplexer extension routines */
|
|
|
|
|
|
/* Attach a network or serial port.
|
|
|
|
This extension for "tmxr_attach" attempts to attach the network or serial
|
|
port name specified by "cptr" to the multiplexer line associated with mux
|
|
descriptor pointer "mp" and unit pointer "uptr". The unit is implicitly
|
|
associated with the line number corresponding to the position of the unit in
|
|
the zero-based array of units belonging to the associated device.
|
|
|
|
The validity of the attachment is determined by the presence or absence of
|
|
the UNIT_ATTABLE ("unit is attachable") flag on the unit indicated by "uptr".
|
|
The Telnet poll unit will have this flag; the individual line units will not.
|
|
The presence or absence of the flag determines the type of attachment to
|
|
attempt.
|
|
|
|
If a device is referenced, the poll unit specified by the "pptr" parameter is
|
|
attached instead of the referenced unit. This is because a device reference
|
|
passes a pointer to unit 0 (i.e., ATTACH MUX and ATTACH MUX0 both set "uptr"
|
|
to point at unit 0). If the poll unit is not defined, then the device attach
|
|
is rejected.
|
|
|
|
An attempt to attach the poll unit directly via a unit reference will be
|
|
rejected by the "ex_tmxr_attach_line" routine because the unit does not
|
|
correspond to a multiplexer port.
|
|
*/
|
|
|
|
t_stat ex_tmxr_attach_unit (TMXR *mp, UNIT *pptr, UNIT *uptr, char *cptr)
|
|
{
|
|
t_stat status;
|
|
|
|
if (sim_ref_type == REF_DEVICE) /* if this is a device reference */
|
|
if (pptr == NULL) /* then if the poll unit is not defined */
|
|
return SCPE_NOATT; /* then report that the attach failed */
|
|
else /* otherwise */
|
|
uptr = pptr; /* substitute the poll unit */
|
|
|
|
if (mp == NULL || uptr == NULL) /* if the descriptor or unit pointer is null */
|
|
status = SCPE_IERR; /* then report an internal error */
|
|
|
|
else if (sim_ref_type != REF_UNIT /* otherwise if this is a device or null reference */
|
|
&& uptr->flags & UNIT_ATTABLE) /* and the poll unit is attachable */
|
|
status = tmxr_attach (mp, uptr, cptr); /* then try to attach a listening port */
|
|
|
|
else /* otherwise it's a unit reference */
|
|
status = ex_tmxr_attach_line (mp, uptr, cptr); /* so try to attach a serial port */
|
|
|
|
return status; /* return the status of the attachment */
|
|
}
|
|
|
|
|
|
/* Detach a network or serial port.
|
|
|
|
This extension for "tmxr_detach" attempts to detach the network or serial
|
|
port from the multiplexer line associated with mux descriptor pointer "mp"
|
|
and unit pointer "uptr". The unit is implicitly associated with the line
|
|
number corresponding to the position of the unit in the zero-based array of
|
|
units belonging to the associated device.
|
|
|
|
The validity of the detachment is determined by the presence or absence of
|
|
the UNIT_ATTABLE ("unit is attachable") flag on the unit indicated by "uptr".
|
|
The Telnet poll unit will have this flag; the individual line units will not.
|
|
The presence or absence of the flag determines the type of detachment to
|
|
attempt.
|
|
|
|
If a device is referenced, the poll unit specified by the "pptr" parameter is
|
|
detached instead of the referenced unit. This is because a device reference
|
|
passes a pointer to unit 0 (i.e., DETACH MUX and DETACH MUX0 both set "uptr"
|
|
to point at unit 0). If the poll unit is not defined, then the device detach
|
|
is rejected.
|
|
|
|
An attempt to detach the poll unit directly via a unit reference will be
|
|
rejected by the "ex_tmxr_detach_line" routine because the unit does not
|
|
correspond to a multiplexer port.
|
|
*/
|
|
|
|
t_stat ex_tmxr_detach_unit (TMXR *mp, UNIT *pptr, UNIT *uptr)
|
|
{
|
|
t_stat status;
|
|
|
|
if (sim_ref_type == REF_DEVICE) /* if this is a device reference */
|
|
if (pptr == NULL) /* then if the poll unit is not defined */
|
|
return SCPE_NOATT; /* then report that the attach failed */
|
|
else /* otherwise */
|
|
uptr = pptr; /* substitute the poll unit */
|
|
|
|
if (mp == NULL || uptr == NULL) /* if the descriptor or unit pointer is null */
|
|
status = SCPE_IERR; /* then report an internal error */
|
|
|
|
else if (sim_ref_type != REF_UNIT /* otherwise if this is a device or null reference */
|
|
&& uptr->flags & UNIT_ATTABLE) /* and the poll unit is attachable */
|
|
status = tmxr_detach (mp, uptr); /* then try to detach a listening port */
|
|
|
|
else /* otherwise it's a line unit */
|
|
status = ex_tmxr_detach_line (mp, uptr); /* so try to detach a serial port */
|
|
|
|
return status; /* return the status of the detachment */
|
|
}
|
|
|
|
|
|
/* Detach a line from serial port.
|
|
|
|
This extension routine disconnects and detaches a line of the multiplexer
|
|
associated with mux descriptor pointer "mp" and unit pointer "uptr" from its
|
|
serial port. The line number is given by the position of the unit in the
|
|
zero-based array of units belonging to the associated device. For example,
|
|
if "uptr" points to unit 3 in a given device, then line 3 will be detached.
|
|
|
|
If the specified unit does not correspond with a multiplexer line, then
|
|
SCPE_NOATT is returned. If the line is not connected to a serial port, then
|
|
SCPE_UNATT is returned. Otherwise, the port is disconnected, and SCPE_OK is
|
|
returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the serial connection had been completed, we disconnect the line,
|
|
which drops DTR to ensure that a modem will disconnect.
|
|
*/
|
|
|
|
t_stat ex_tmxr_detach_line (TMXR *mp, UNIT *uptr)
|
|
{
|
|
TMLN *lp;
|
|
EX_TMLN *exlp;
|
|
|
|
if (uptr == NULL) /* if this is a console reference */
|
|
lp = mp->ldsc; /* point at the (only) line */
|
|
else /* otherwise */
|
|
lp = tmxr_find_ldsc (uptr, mp->lines, mp); /* determine the line from the unit */
|
|
|
|
if (lp == NULL) /* if the unit does not correspond to a line */
|
|
return SCPE_NOATT; /* then report that the unit is not attachable */
|
|
else /* otherwise */
|
|
exlp = serial_line (lp); /* get the serial line extension */
|
|
|
|
if (exlp == NULL) /* if the line is not a serial line */
|
|
return SCPE_UNATT; /* then report that the unit is unattached */
|
|
|
|
if (lp->conn) /* if the connection has been completed */
|
|
tmxr_disconnect_line (lp); /* then disconnect the line */
|
|
|
|
sim_close_serial (exlp->serport); /* close the serial port */
|
|
free (exlp->sername); /* and free the port name */
|
|
|
|
exlp->serport = INVALID_HANDLE; /* reinitialize the structure */
|
|
exlp->sername = NULL; /* to show it is not connected */
|
|
|
|
if (uptr != NULL) { /* if this is not a console detach */
|
|
uptr->filename = NULL; /* then clear the port name pointer */
|
|
|
|
uptr->flags &= ~UNIT_ATT; /* mark the unit as unattached */
|
|
}
|
|
|
|
return SCPE_OK; /* return success */
|
|
}
|
|
|
|
|
|
/* Control a terminal line.
|
|
|
|
This extension routine controls a multiplexer line, specified by the "lp"
|
|
parameter, as though it were connected to a modem. The caller designates
|
|
that the line's Data Terminal Ready (DTR) and Request To Send (RTS) signals
|
|
should be asserted or denied as specified by the "control" parameter. If the
|
|
line is connected to a Telnet session, dropping DTR will disconnect the
|
|
session. If the line is connected to a serial port, the signals are sent to
|
|
the device connected to the hardware port, which reacts in a device-dependent
|
|
manner.
|
|
|
|
Calling this routine establishes VM control over the multiplexer line.
|
|
Control is only relevant when a line is attached to a serial port.
|
|
|
|
Initially, a line is uncontrolled. In this state, attaching a line to a
|
|
serial port automatically asserts DTR and RTS, and detaching the line drops
|
|
both signals. After this routine has been called, this default action no
|
|
longer occurs, and it is the responsibility of the VM to raise and lower DTR
|
|
and RTS explicitly.
|
|
|
|
The caller may reset a line to the uncontrolled state by calling the routine
|
|
with the "control" parameter set to the "Reset_Control" value. Typically,
|
|
this is only necessary if a RESET -P (i.e., power-on reset) is performed.
|
|
|
|
If a null pointer is passed for the "lp" parameter, the routine returns
|
|
SCPE_IERR. If the line extension structure has not been allocated when the
|
|
routine is called, it is allocated here. If the allocation fails, SCPE_MEM
|
|
is returned. If the line is attached to a serial port, the serial control
|
|
routine status (SCPE_OK or SCPE_IOERR) is returned. Otherwise, the routine
|
|
returns SCPE_OK.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The TMCKT and SERCIRCUIT types are renamings of the underlying
|
|
RS232_SIGNAL type, so a type cast is valid.
|
|
*/
|
|
|
|
t_stat ex_tmxr_control_line (TMLN *lp, TMCKT control)
|
|
{
|
|
EX_TMLN *exlp;
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (lp == NULL) /* if the line pointer is invalid */
|
|
return SCPE_IERR; /* then report an internal error */
|
|
else /* otherwise */
|
|
exlp = (EX_TMLN *) lp->exptr; /* point to the descriptor extension */
|
|
|
|
if (exlp == NULL) { /* if the extension has not been allocated */
|
|
lp->exptr = malloc (sizeof (EX_TMLN)); /* then allocate it now */
|
|
|
|
if (lp->exptr == NULL) /* if the memory allocation failed */
|
|
return SCPE_MEM; /* then report the failure */
|
|
|
|
else { /* otherwise */
|
|
exlp = (EX_TMLN *) lp->exptr; /* point to the new descriptor extension */
|
|
|
|
exlp->serport = INVALID_HANDLE; /* clear the serial port handle */
|
|
exlp->sername = NULL; /* and the port name */
|
|
}
|
|
}
|
|
|
|
if (control == Reset_Control) { /* if a reset is requested */
|
|
exlp->controlled = FALSE; /* then mark the line as uncontrolled */
|
|
|
|
if (lp->conn == 0) /* if the line is currently disconnected */
|
|
exlp->signals = No_Signals; /* then default to no control signals */
|
|
else /* otherwise */
|
|
exlp->signals = DTR_Control | RTS_Control; /* default to the connected control signals */
|
|
}
|
|
|
|
else { /* otherwise signal control is requested */
|
|
exlp->controlled = TRUE; /* so mark the line as controlled */
|
|
exlp->signals = control; /* and record the requested signal states */
|
|
|
|
if (exlp->serport != INVALID_HANDLE) /* if the line is connected to a serial port */
|
|
status = sim_control_serial (exlp->serport, /* then let the hardware handle it */
|
|
(SERCIRCUIT) control);
|
|
|
|
else if (lp->conn != 0 /* otherwise if the Telnet line is currently connected */
|
|
&& (control & DTR_Control) == 0) /* and DTR is being dropped */
|
|
tmxr_disconnect_line (lp); /* then disconnect the line */
|
|
}
|
|
|
|
return status; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Get a terminal line's status.
|
|
|
|
This extension routine returns the status of a multiplexer line, specified by
|
|
the "lp" parameter. If the line is connected to a serial port, the hardware
|
|
port status is returned. If the line is connected to a Telnet port,
|
|
simulated modem status (Data Set Ready, Clear To Send, and Data Carrier
|
|
Detect) is returned. If the line is not connected, no signals are returned.
|
|
|
|
If a null pointer is passed for the "lp" parameter, the routine returns the
|
|
Error_Status value.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The TMCKT and SERCIRCUIT types are renamings of the underlying
|
|
RS232_SIGNAL type, so a type cast is valid.
|
|
*/
|
|
|
|
TMCKT ex_tmxr_line_status (TMLN *lp)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the line pointer is invalid */
|
|
return Error_Status; /* then report an internal error */
|
|
else /* otherwise */
|
|
exlp = (EX_TMLN *) lp->exptr; /* point to the descriptor extension */
|
|
|
|
if (exlp != NULL && exlp->serport != INVALID_HANDLE) /* if the line is connected to a serial port */
|
|
return (TMCKT) sim_status_serial (exlp->serport); /* then return the hardware port status */
|
|
|
|
else if (lp->conn != 0) /* otherwise if the line is connected to a Telnet port */
|
|
return DSR_Status | CTS_Status | DCD_Status; /* then simulate a connected modem */
|
|
|
|
else /* otherwise */
|
|
return No_Signals; /* simulate a disconnected modem */
|
|
}
|
|
|
|
|
|
/* Poll for a new network or serial connection.
|
|
|
|
This shim for "tmxr_poll_conn" polls for new Telnet or serial connections for
|
|
the multiplexer descriptor indicated by "mp". If a Telnet or serial
|
|
connection is made, the routine returns the line number of the new
|
|
connection. Otherwise, the routine returns 0. If a serial connection and a
|
|
Telnet connection are both pending, the serial connection takes precedence.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. When a serial port is attached to a line, the connection is made pending
|
|
until we are called to poll for new connections. This is because VM
|
|
multiplexer service routines recognize new connections only as a result
|
|
of calls to this routine.
|
|
|
|
2. A pending serial (re)connection may be deferred. This is needed when a
|
|
line clear drops DTR, as DTR must remain low for a period of time in
|
|
order to be recognized by the serial device. If the "cnms" value
|
|
specifies a time in the future, the connection is deferred until that
|
|
time is reached. This leaves DTR low for the necessary time.
|
|
|
|
3. If the serial line is uncontrolled, the default control signals (i.e.,
|
|
DTR and RTS) will be asserted. Otherwise, the last signals set by the VM
|
|
will be used.
|
|
*/
|
|
|
|
int32 ex_tmxr_poll_conn (TMXR *mp)
|
|
{
|
|
TMLN *lp;
|
|
EX_TMLN *exlp;
|
|
int32 line;
|
|
uint32 current_time;
|
|
|
|
if (mp == NULL) /* if the mux descriptor is invalid */
|
|
return 0; /* then return "no connection" status */
|
|
|
|
current_time = sim_os_msec (); /* get the current time */
|
|
|
|
for (line = 0; line < mp->lines; line++) { /* check each line in sequence for connections */
|
|
lp = mp->ldsc + line; /* get a pointer to the line descriptor */
|
|
exlp = serial_line (lp); /* and to the serial line extension */
|
|
|
|
if (exlp != NULL && lp->conn == 0 /* if the line is a serial line but not yet connected */
|
|
&& current_time >= lp->cnms) { /* and the connection time has been reached */
|
|
tmxr_init_line (lp); /* then initialize the line state */
|
|
|
|
if (exlp->controlled == FALSE) /* if the line as uncontrolled */
|
|
exlp->signals = DTR_Control | RTS_Control; /* then default to the connected control signals */
|
|
|
|
sim_control_serial (exlp->serport, exlp->signals); /* connect the line as directed */
|
|
|
|
lp->conn = 1; /* mark the line as now connected */
|
|
lp->cnms = current_time; /* record the time of connection */
|
|
|
|
tmxr_report_connection (mp, lp, line); /* report the connection to the connected device */
|
|
return line; /* and return the line number */
|
|
}
|
|
}
|
|
|
|
return tmxr_poll_conn (mp); /* there are no serial connections, so check for Telnet */
|
|
}
|
|
|
|
|
|
/* Determine if a line is free.
|
|
|
|
If the line described by the line descriptor pointer "lp" is not connected to
|
|
either a Telnet session or a serial port, this routine returns TRUE.
|
|
Otherwise, it returns FALSE. A TRUE return, therefore, indicates that the
|
|
line is not in use.
|
|
*/
|
|
|
|
t_bool ex_tmxr_line_free (TMLN *lp)
|
|
{
|
|
if (lp == NULL || lp->conn != 0) /* if the line is invalid or is connected */
|
|
return FALSE; /* then mark the line as busy */
|
|
else /* otherwise */
|
|
return serial_line (lp) == NULL; /* the line is free if it's not a serial line */
|
|
}
|
|
|
|
|
|
/* Determine if a multiplexer is free.
|
|
|
|
If the multiplexer described by the mux descriptor pointer "mp" is not
|
|
listening for new Telnet connections and has no lines that are connected to
|
|
serial ports, then this routine returns TRUE. Otherwise, it returns FALSE.
|
|
A TRUE return, therefore, indicates that the multiplexer is not in use.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the listening network socket is detached, then no Telnet sessions can
|
|
exist, so we only need to check for serial connections on the lines.
|
|
*/
|
|
|
|
t_bool ex_tmxr_mux_free (TMXR *mp)
|
|
{
|
|
int32 line;
|
|
TMLN *lp;
|
|
|
|
if (mp == NULL || mp->master != 0) /* if the descriptor is invalid or the socket is open */
|
|
return FALSE; /* then the multiplexer is not free */
|
|
|
|
lp = mp->ldsc; /* point at the first line descriptor */
|
|
|
|
for (line = 0; line < mp->lines; line++, lp++) /* check each line for a serial connection */
|
|
if (ex_tmxr_line_free (lp) == FALSE) /* if a serial port is open */
|
|
return FALSE; /* then the multiplexer is not free */
|
|
|
|
return TRUE; /* the mux is free, as there are no connections */
|
|
}
|
|
|
|
|
|
|
|
/* Hooked terminal multiplexer replacement extension routines */
|
|
|
|
|
|
/* Read from a multiplexer line.
|
|
|
|
This hook routine reads up to "length" characters into the character buffer
|
|
associated with line descriptor pointer "lp". The actual number of
|
|
characters read is returned. If no characters are available, 0 is returned.
|
|
If an error occurred while reading, -1 is returned.
|
|
|
|
If the line is connected to a serial port, a serial read is issued.
|
|
Otherwise, the read routine in the TMXR library is called to read from a
|
|
network port.
|
|
|
|
If a line break was detected on serial input, the associated receive break
|
|
status flag in the line descriptor will be set. Line break indication for
|
|
Telnet connections is embedded in the Telnet protocol and must be determined
|
|
externally.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. It is up to the caller to ensure that the line is connected and a read of
|
|
the specified length fits in the buffer with the current buffer index.
|
|
*/
|
|
|
|
static int32 ex_tmxr_read (TMLN *lp, int32 length)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the descriptor pointer is not set */
|
|
return -1; /* then return failure */
|
|
else /* otherwise */
|
|
exlp = serial_line (lp); /* get the serial line extension */
|
|
|
|
if (exlp == NULL) /* if the line is not a serial line */
|
|
return tmxr_base_read (lp, length); /* then call the standard library routine */
|
|
|
|
else /* otherwise */
|
|
return sim_read_serial (exlp->serport, /* call the serial read routine */
|
|
lp->rxb + lp->rxbpi, /* with the buffer pointer */
|
|
length, /* and maximum read length */
|
|
lp->rbr + lp->rxbpi); /* and the break array pointer */
|
|
}
|
|
|
|
|
|
/* Write to a multiplexer line.
|
|
|
|
This hook routine writes up to "length" characters from the character buffer
|
|
associated with line descriptor pointer "lp". The actual number of
|
|
characters written is returned. If an error occurred while writing, -1 is
|
|
returned.
|
|
|
|
If the line is connected to a serial port, a serial write is issued.
|
|
Otherwise, the write routine in the TMXR library is called to write to a
|
|
network port.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. It is up to the caller to ensure that the line is connected and a write
|
|
of the specified length is contained in the buffer with the current
|
|
buffer index.
|
|
*/
|
|
|
|
static int32 ex_tmxr_write (TMLN *lp, int32 length)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the descriptor pointer is not set */
|
|
return -1; /* then return failure */
|
|
else /* otherwise */
|
|
exlp = serial_line (lp); /* get the serial line extension */
|
|
|
|
if (exlp == NULL) /* if the line is not a serial line */
|
|
return tmxr_base_write (lp, length); /* then call the standard library routine */
|
|
|
|
else /* otherwise */
|
|
return sim_write_serial (exlp->serport, /* call the serial write routine */
|
|
lp->txb + lp->txbpr, /* with the buffer pointer */
|
|
length); /* and write length */
|
|
}
|
|
|
|
|
|
/* Show a multiplexer line connection.
|
|
|
|
This hook routine is called from the "tmxr_fconns" to display the line
|
|
connection status, typically in response to a SHOW <mux> CONNECTIONS command.
|
|
Depending on the line connection type, the Telnet IP address or serial port
|
|
name is displayed.
|
|
*/
|
|
|
|
static void ex_tmxr_show (TMLN *lp, FILE *stream)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the descriptor pointer is not set */
|
|
return; /* then a programming error has occurred */
|
|
else /* otherwise */
|
|
exlp = serial_line (lp); /* get the serial line extension */
|
|
|
|
if (exlp == NULL) /* if the line is not a serial line */
|
|
tmxr_base_show (lp, stream); /* then call the standard library routine */
|
|
else /* otherwise */
|
|
fprintf (stream, "Serial port %s", exlp->sername); /* print the serial port name */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Close a multiplexer line connection.
|
|
|
|
This hook routine disconnects the Telnet or serial session associated with
|
|
line descriptor "lp". If the line is connected to a Telnet port, the close
|
|
routine in the TMXR library is called to close and deallocate the port.
|
|
Otherwise, if the line is connected to an uncontrolled serial port, DTR and
|
|
RTS are dropped to disconnect the attached serial device; the port remains
|
|
connected to the line, which is scheduled for reconnection after a short
|
|
delay for DTR recognition. If the line is controlled, this routine takes no
|
|
action; it is up to the VM to decide how to proceed.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The base close routine does not return a value, so we cannot report an
|
|
error when a null line descriptor pointer was passed.
|
|
*/
|
|
|
|
static void ex_tmxr_close (TMLN *lp)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the descriptor pointer is not set */
|
|
return; /* then a programming error has occurred */
|
|
else /* otherwise */
|
|
exlp = serial_line (lp); /* get the serial line extension */
|
|
|
|
if (exlp == NULL) /* if the line is not a serial line */
|
|
tmxr_base_close (lp); /* then call the standard library routine */
|
|
|
|
else if (exlp->controlled == FALSE) { /* otherwise if the line is uncontrolled */
|
|
sim_control_serial (exlp->serport, No_Signals); /* then disconnect the line by dropping DTR */
|
|
lp->cnms = sim_os_msec () + 500; /* and schedule reconnection 500 msec from now */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Determine if a line is extended.
|
|
|
|
This hook routine returns TRUE if the line described by the line descriptor
|
|
pointer "lp" is controlled by this extension module and FALSE if it is not.
|
|
A line is extended only if it is connected to a serial port; the presence of
|
|
a non-null TMLN extension structure pointer is not sufficient, as that
|
|
pointer may be set if a line control call is made for a Telnet port.
|
|
|
|
Returning FALSE indicates to the caller that the line should receive the
|
|
standard operations. Returning TRUE indicates that this extension module
|
|
will operate the line.
|
|
*/
|
|
|
|
static t_bool ex_tmxr_extended (TMLN *lp)
|
|
{
|
|
return serial_line (lp) != NULL; /* return TRUE if it's a serial line */
|
|
}
|
|
|
|
|
|
|
|
/* Local terminal multiplexer extension routines */
|
|
|
|
|
|
/* Attach a line to a serial port.
|
|
|
|
Attach a line of the multiplexer associated with mux descriptor pointer "mp"
|
|
and unit pointer "uptr" to the serial port name indicated by "cptr". The
|
|
unit is implicitly associated with the line number corresponding to the
|
|
position of the unit in the zero-based array of units belonging to the
|
|
associated device. For example, if "uptr" points to unit 3 in a given
|
|
device, and "cptr" points to the string "COM1", then line 3 will be attached
|
|
to serial port "COM1".
|
|
|
|
An optional configuration string may be present after the port name. If
|
|
present, it must be separated from the port name with a semicolon and has
|
|
this form:
|
|
|
|
<rate>-<charsize><parity><stopbits>
|
|
|
|
where:
|
|
|
|
rate = communication rate in bits per second
|
|
charsize = character size in bits (5-8, including optional parity)
|
|
parity = parity designator (N/E/O/M/S for no/even/odd/mark/space parity)
|
|
stopbits = number of stop bits (1, 1.5, or 2)
|
|
|
|
As an example:
|
|
|
|
9600-8n1
|
|
|
|
The supported rates, sizes, and parity options are host-specific. If a
|
|
configuration string is not supplied, then host system defaults for the
|
|
specified port are used.
|
|
|
|
If the serial port allocation is successful, then the port name is stored in
|
|
the UNIT structure, the UNIT_ATT flag is set, and the routine returns
|
|
SCPE_OK. If it fails, the error code is returned.
|
|
|
|
Implementation notes:
|
|
|
|
1. If the device associated with the unit referenced by "uptr" does not have
|
|
the DEV_NET flag set, then the optional configuration string is saved
|
|
with the port name in the UNIT structure. This allows a RESTORE to
|
|
reconfigure the attached serial port during reattachment. The combined
|
|
string will be displayed when the unit is SHOWed.
|
|
|
|
If the unit has the DEV_NET flag, the optional configuration string is
|
|
removed before the attached port name is saved in the UNIT structure, as
|
|
RESTORE will not reattach the port, and so reconfiguration is not needed.
|
|
|
|
2. The "exptr" field of the line descriptor will be set on entry if a call
|
|
to the "ex_tmxr_control_line" routine has preceded this call. If the
|
|
structure has not been allocated, it is allocated here and is set to the
|
|
uncontrolled state; a subsequent call to "ex_tmxr_control_line" will
|
|
establish VM control if desired.
|
|
|
|
3. Attempting to attach line that does not appear in the connection order
|
|
array will be rejected. This ensures that an omitted line will receive
|
|
neither a Telnet connection nor a serial connection.
|
|
*/
|
|
|
|
static t_stat ex_tmxr_attach_line (TMXR *mp, UNIT *uptr, char *cptr)
|
|
{
|
|
TMLN *lp;
|
|
EX_TMLN *exlp;
|
|
DEVICE *dptr;
|
|
char *pptr, *sptr, *tptr;
|
|
SERHANDLE serport;
|
|
t_stat status;
|
|
int32 cntr, line;
|
|
char portname [1024];
|
|
t_bool arg_error = FALSE;
|
|
SERCONFIG config = { 0 };
|
|
|
|
if (uptr == NULL) /* if this is a console reference */
|
|
lp = mp->ldsc; /* point at the (only) line */
|
|
else /* otherwise */
|
|
lp = tmxr_find_ldsc (uptr, mp->lines, mp); /* determine the line from the unit */
|
|
|
|
if (lp == NULL) /* if the unit does not correspond to a line */
|
|
return SCPE_NXUN; /* then report that the unit does not exist */
|
|
|
|
else if (lp->conn) /* otherwise if the line is connected via Telnet */
|
|
return SCPE_NOFNC; /* then the command is not allowed */
|
|
|
|
else if (cptr == NULL) /* otherwise if the port name is missing */
|
|
return SCPE_2FARG; /* then report a missing argument */
|
|
|
|
else { /* otherwise get the multiplexer line number */
|
|
line = (int32) (lp - mp->ldsc); /* implied by the line descriptor */
|
|
|
|
if (mp->lnorder != NULL && mp->lnorder [0] >= 0) { /* if the line order exists and is not defaulted */
|
|
for (cntr = 0; cntr < mp->lines; cntr++) /* then see if the line to attach */
|
|
if (line == mp->lnorder [cntr]) /* is present in the */
|
|
break; /* connection order array */
|
|
|
|
if (cntr == mp->lines) /* if the line was not found */
|
|
return SCPE_NOATT; /* then report that the line is not attachable */
|
|
}
|
|
}
|
|
|
|
pptr = get_glyph_nc (cptr, portname, ';'); /* separate the port name from the optional configuration */
|
|
|
|
if (*pptr != '\0') { /* if a parameter string is present */
|
|
config.baudrate = (uint32) strtotv (pptr, &sptr, 10); /* then parse the baud rate */
|
|
arg_error = (pptr == sptr); /* and check for a bad argument */
|
|
|
|
if (*sptr != '\0') /* if a separator is present */
|
|
sptr++; /* then skip it */
|
|
|
|
config.charsize = (uint32) strtotv (sptr, &tptr, 10); /* parse the character size */
|
|
arg_error = arg_error || (sptr == tptr); /* and check for a bad argument */
|
|
|
|
if (*tptr != '\0') /* if the parity character is present */
|
|
config.parity = toupper (*tptr++); /* then save it */
|
|
|
|
config.stopbits = (uint32) strtotv (tptr, &sptr, 10); /* parse the number of stop bits */
|
|
arg_error = arg_error || (tptr == sptr); /* and check for a bad argument */
|
|
|
|
if (arg_error) /* if any parse failure occurred */
|
|
return SCPE_ARG; /* then report an invalid argument error */
|
|
|
|
else if (strcmp (sptr, ".5") == 0) /* otherwise if 1.5 stop bits are requested */
|
|
config.stopbits = 0; /* then recode the request */
|
|
}
|
|
|
|
serport = sim_open_serial (portname); /* open the named serial port */
|
|
|
|
if (serport == INVALID_HANDLE) /* if the port name is invalid or in use */
|
|
return SCPE_OPENERR; /* then report the attach failure */
|
|
|
|
else { /* otherwise we have a good serial port */
|
|
if (*pptr) { /* if the parameter string was specified */
|
|
status = sim_config_serial (serport, config); /* then set the serial configuration */
|
|
|
|
if (status != SCPE_OK) { /* if configuration failed */
|
|
sim_close_serial (serport); /* then close the port */
|
|
return status; /* and report the error */
|
|
}
|
|
}
|
|
|
|
dptr = find_dev_from_unit (uptr); /* find the device that owns the unit */
|
|
|
|
if (dptr != NULL && dptr->flags & DEV_NET) /* if RESTORE will be inhibited */
|
|
cptr = portname; /* then save just the port name */
|
|
|
|
if (mp->dptr == NULL) /* if the device has not been set in the descriptor */
|
|
mp->dptr = dptr; /* then set it now */
|
|
|
|
tptr = (char *) malloc (strlen (cptr) + 1); /* get a buffer for the port name and configuration */
|
|
|
|
if (tptr == NULL) { /* if the memory allocation failed */
|
|
sim_close_serial (serport); /* then close the port */
|
|
return SCPE_MEM; /* and report the failure */
|
|
}
|
|
|
|
else /* otherwise */
|
|
strcpy (tptr, cptr); /* copy the port name into the buffer */
|
|
|
|
exlp = (EX_TMLN *) lp->exptr; /* point to the descriptor extension */
|
|
|
|
if (exlp == NULL) { /* if the extension has not been allocated */
|
|
lp->exptr = malloc (sizeof (EX_TMLN)); /* then allocate it now */
|
|
|
|
if (lp->exptr == NULL) { /* if the memory allocation failed */
|
|
free (tptr); /* then free the port name buffer */
|
|
sim_close_serial (serport); /* and close the port */
|
|
|
|
return SCPE_MEM; /* report the failure */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
exlp = (EX_TMLN *) lp->exptr; /* point to the new descriptor extension */
|
|
|
|
exlp->controlled = FALSE; /* mark the line as uncontrolled */
|
|
exlp->signals = No_Signals; /* and set the unconnected control signals */
|
|
}
|
|
}
|
|
|
|
exlp->serport = serport; /* save the serial port handle */
|
|
exlp->sername = tptr; /* and the port name */
|
|
|
|
if (uptr != NULL) { /* if this is not a console attach */
|
|
uptr->filename = tptr; /* then save the port name pointer in the UNIT */
|
|
uptr->flags |= UNIT_ATT; /* and mark the unit as attached */
|
|
}
|
|
|
|
tmxr_init_line (lp); /* initialize the line state */
|
|
|
|
lp->cnms = 0; /* schedule for an immediate connection */
|
|
lp->conn = 0; /* and indicate that there is no connection yet */
|
|
}
|
|
|
|
return SCPE_OK; /* the line has been connected */
|
|
}
|
|
|
|
|
|
/* Get the extension pointer for a serial line.
|
|
|
|
This routine returns a pointer to the TMLN extension structure if it exists
|
|
and is currently in use for a serial line. Otherwise, it returns NULL. A
|
|
non-null return therefore indicates that the line is connected to a serial
|
|
port and a serial operation should be performed instead of a Telnet
|
|
operation.
|
|
*/
|
|
|
|
static EX_TMLN *serial_line (TMLN *lp)
|
|
{
|
|
EX_TMLN *exlp;
|
|
|
|
if (lp == NULL) /* if the line pointer is invalid */
|
|
return NULL; /* then the line cannot be a serial line */
|
|
else /* otherwise */
|
|
exlp = (EX_TMLN *) lp->exptr; /* point at the corresponding line extension */
|
|
|
|
if (exlp != NULL && exlp->serport != INVALID_HANDLE) /* if it's allocated to a serial port */
|
|
return exlp; /* then return the extension pointer */
|
|
else /* otherwise */
|
|
return NULL; /* it's not a serial line */
|
|
}
|
|
|
|
|
|
|
|
/* ********************* String Breakpoint Extensions ************************
|
|
|
|
This module extends the following existing routines in "sim_console.c" and
|
|
"scp.c":
|
|
|
|
sim_putchar -- write a character to the console window
|
|
sim_putchar_s -- write a character to the console window and stall if busy
|
|
|
|
sim_brk_test -- test for a breakpoint at the current program location
|
|
|
|
The console output routines are extended to match output characters to
|
|
pending string breakpoints, and the breakpoint test routine is extended to
|
|
check for triggered string breakpoints.
|
|
|
|
If a string breakpoint for the console is set, each output character is
|
|
matched to the breakpoint string. This matching takes place only if the
|
|
console input has not been redirected to the command buffer, i.e., if it is
|
|
in "Console" mode and not "Command" mode. If the output characters form a
|
|
matching string, the breakpoint is triggered, which will cause the next call
|
|
to "sim_brk_test" to return the string breakpoint type. The VM typically
|
|
makes such a call once per instruction.
|
|
|
|
If a breakpoint has a delay specified, triggering is not enabled until the
|
|
delay time has expired. The state of a breakpoint -- not triggered
|
|
(pending), trigger delayed, or triggered -- is indicated by the "trigger"
|
|
field of the breakpoint structure. If the value is negative, the breakpoint
|
|
is not triggered. Otherwise, the value indicates the simulator global time
|
|
at which the breakpoint transitions from trigger delayed to triggered.
|
|
Comparison to the global time therefore indicates the trigger state.
|
|
*/
|
|
|
|
|
|
/* Global string breakpoint extension routines */
|
|
|
|
|
|
/* Put a character to the console.
|
|
|
|
This shim for "sim_putchar" outputs the character designated by "c" to the
|
|
console window. If the keyboard is in Console mode, and a string breakpoint
|
|
is set, the character is matched to the current breakpoint. The matching is
|
|
not done in Command mode to prevent a breakpoint from triggering due to
|
|
command echoing or output.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the output character cannot be written due to a stall, it is ignored.
|
|
Output is normally stalled when the keyboard is in Command mode, so any
|
|
characters output via calls to this routine while in Command mode will be
|
|
lost.
|
|
*/
|
|
|
|
t_stat ex_sim_putchar (int32 c)
|
|
{
|
|
if (keyboard_mode == Console) { /* if we are in console mode */
|
|
if (sb_list != NULL) /* then if string breakpoints exist */
|
|
test_breakpoint (c); /* then test for a match */
|
|
|
|
return sim_putchar (c); /* output the character */
|
|
}
|
|
|
|
else if (sim_con_tmxr.master != 0) /* otherwise if the consoles are separate */
|
|
return sim_putchar (c); /* then output the character */
|
|
|
|
else /* otherwise we're in unified command mode */
|
|
return SCPE_OK; /* so discard the output */
|
|
}
|
|
|
|
|
|
/* Put a character to the console with stall detection.
|
|
|
|
This shim for "sim_putchar_s" outputs the character designated by "c" to the
|
|
console window. If the keyboard is in Console mode, and a string breakpoint
|
|
is set, the character is matched to the current breakpoint. The matching is
|
|
not done in Command mode to prevent a breakpoint from triggering due to
|
|
command echoing or output.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the output character cannot be written due to a stall, SCPE_STALL is
|
|
returned. The calling routine should detect this condition and
|
|
reschedule the output. Output is normally stalled when the keyboard is
|
|
in Command mode, so any characters output via calls to this routine while
|
|
in Command mode should be rescheduled for output when the keyboard
|
|
returns to Console mode.
|
|
*/
|
|
|
|
t_stat ex_sim_putchar_s (int32 c)
|
|
{
|
|
if (keyboard_mode == Console) { /* if we are in console mode */
|
|
if (sb_list != NULL) /* then if string breakpoints exist */
|
|
test_breakpoint (c); /* then test for a match */
|
|
|
|
return sim_putchar_s (c); /* output the character */
|
|
}
|
|
|
|
else if (sim_con_tmxr.master != 0) /* otherwise if the consoles are separate */
|
|
return sim_putchar_s (c); /* then output the character */
|
|
|
|
else /* otherwise we're in unified command mode */
|
|
return SCPE_STALL; /* so stall the output */
|
|
}
|
|
|
|
|
|
/* Test for a breakpoint at the current location.
|
|
|
|
This shim for "sim_brk_test" checks for a triggered string breakpoint or a
|
|
numeric breakpoint of type "type" at the address designated by "location".
|
|
If a breakpoint is detected, the type of the breakpoint is returned.
|
|
|
|
The "type" parameter is the union of all numeric breakpoint types that are
|
|
valid for this address location. To be triggered, a numeric breakpoint must
|
|
match both the location and one of the specified types.
|
|
|
|
String breakpoints are always tested, as only one type of string breakpoint
|
|
is defined -- the default string breakpoint type. Therefore, it is not
|
|
necessary for the VM to add the string breakpoint type to the "type"
|
|
parameter when calling this routine.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. For numeric breakpoint types, a type's presence in "sim_brk_summ"
|
|
indicates that one or more breakpoints of that type are pending and must
|
|
be checked for triggering during this routine. For string breakpoints,
|
|
presence of the BP_STRING type indicates that a string breakpoint has
|
|
already triggered, and no further check is required.
|
|
|
|
2. String breakpoints are triggered by the "breakpoint_service" routine when
|
|
the breakpoint delay expires. There would be no need to handle
|
|
triggering here if there were a global breakpoint status value, as the
|
|
service routine could simply return that hypothetical SCPE_BREAK code to
|
|
stop instruction execution. Unfortunately, "breakpoint triggered" is a
|
|
VM-specific status code (e.g., STOP_BRKPNT), so we must use this routine
|
|
to return a breakpoint indication to the VM, which then stops execution
|
|
and returns its own VM-specific status.
|
|
*/
|
|
|
|
uint32 ex_sim_brk_test (t_addr location, uint32 type)
|
|
{
|
|
static char tempbuf [CBUFSIZE];
|
|
uint32 result;
|
|
BRKTAB *bp;
|
|
|
|
if (sim_brk_summ & BP_STRING) { /* if a string breakpoint has triggered */
|
|
sim_brk_summ &= ~BP_STRING; /* then clear the code */
|
|
result = BP_STRING; /* and return the match type */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
result = sim_brk_test (location, type); /* test for a numeric breakpoint */
|
|
|
|
if (result != 0) { /* if the breakpoint fired */
|
|
bp = sim_brk_fnd (location); /* then find it */
|
|
|
|
if (bp != NULL && bp->typ & SWMASK ('T')) { /* if the breakpoint is temporary */
|
|
if (bp->act != NULL) /* then if actions are defined for it */
|
|
sim_brk_act = strcpy (tempbuf, bp->act); /* then copy the action string */
|
|
|
|
sim_brk_clr (location, bp->typ); /* clear the breakpoint */
|
|
}
|
|
}
|
|
}
|
|
|
|
return result; /* return the test result */
|
|
}
|
|
|
|
|
|
/* Local string breakpoint SCP support routines */
|
|
|
|
|
|
/* Return the name of the breakpoint delay unit.
|
|
|
|
The unit that implements string breakpoint delays does not have a
|
|
corresponding device, so the SHOW QUEUE command will fail to find the unit in
|
|
the list of devices. To provide the name, we set the "sim_vm_unit_name" hook
|
|
in the one-time initialization to point at this routine, which supplies the
|
|
name when passed the break delay unit as the parameter. If the unit is not
|
|
the break delay unit, then we let the VM-defined name routine handle it, if
|
|
one was defined. Otherwise, we return NULL to indicate that we did not
|
|
recognize it.
|
|
*/
|
|
|
|
static char *breakpoint_name (const UNIT *uptr)
|
|
{
|
|
if (uptr == breakpoint_unit) /* if the request is for the break delay unit */
|
|
return "Break delay timer"; /* then return the descriptive name */
|
|
else if (vm_unit_name_handler) /* otherwise if the VM defined a name handler */
|
|
return vm_unit_name_handler (uptr); /* then call it to process the request */
|
|
else /* otherwise */
|
|
return NULL; /* report that we do not recognize the unit */
|
|
}
|
|
|
|
|
|
/* Service a breakpoint.
|
|
|
|
A matched breakpoint remains in the trigger-delayed state until any specified
|
|
delay elapses. When a pending breakpoint is satisfied, service is scheduled
|
|
on the breakpoint unit to wait until the delay expires. This service routine
|
|
then triggers the breakpoint and handles removal of the allocated structure
|
|
if it is temporary or resetting the breakpoint if it is permanent.
|
|
|
|
On entry, one of the breakpoints in the linked list will be ready to trigger.
|
|
We scan the list looking for the first breakpoint whose trigger time has
|
|
passed. The scan is done only if no prior trigger is waiting to be
|
|
acknowledged. This condition could occur if the VM has not called
|
|
"sim_brk_test" since the earlier breakpoint triggered. In that case, we
|
|
postpone our check, so that we do not have two triggered breakpoints at the
|
|
same time (as only one of the two sets of breakpoint actions can be
|
|
performed).
|
|
|
|
When a breakpoint triggers, the BP_STRING type is set in the "sim_brk_summ"
|
|
variable to tell the "sim_brk_test" routine to pass a breakpoint indication
|
|
back to the VM for action. Then we clean up the breakpoint structure,
|
|
copying the action string into a local static buffer if the breakpoint was
|
|
temporary; it will be executed from there.
|
|
|
|
When we are called, there may be additional breakpoints in the trigger-
|
|
delayed state, i.e., waiting for their respective delays to expire before
|
|
triggering. While we are processing the breakpoint list, we also look for
|
|
the earliest breakpoint in that state and schedule our service to reactivate
|
|
when that delay expires. If no additional breakpoints are delayed, the
|
|
service stops.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If a second breakpoint is waiting, the delay until that breakpoint
|
|
triggers could be zero -- if, for example, a breakpoint with a delay of
|
|
2000 has already waited for a period of 1000 when a second breakpoint
|
|
with a delay of 1000 is satisfied. After servicing the first breakpoint,
|
|
we cannot reactivate the service with a zero time, because we would be
|
|
reentered before the VM had a chance to call "sim_brk_test" and so the
|
|
first would still be pending, which would hold off recognizing the
|
|
second. This would result in another zero service time, and the VM would
|
|
never get an opportunity to recognize any breakpoints. So we arbitrarily
|
|
reschedule with a delay of one, which allows the VM to recognize the
|
|
first triggered breakpoint.
|
|
|
|
2. It is safe to use a single static breakpoint action buffer for temporary
|
|
breakpoints, because a triggered breakpoint causes the VM to exit the
|
|
instruction loop, and reentry (a necessary condition for a second
|
|
breakpoint to trigger) clears any pending actions.
|
|
*/
|
|
|
|
static t_stat breakpoint_service (UNIT *uptr)
|
|
{
|
|
static char tempbuf [CBUFSIZE];
|
|
SBPTR bp, prev;
|
|
int32 delay;
|
|
const double entry_time = sim_gtime (); /* the global simulation time at entry */
|
|
double next_time = DBL_MAX; /* the time at which the next breakpoint triggers */
|
|
|
|
bp = sb_list; /* start searching at the head of the breakpoint list */
|
|
prev = NULL; /* for a triggered breakpoint */
|
|
|
|
while (bp != NULL) { /* loop until the list is exhausted */
|
|
if (bp->trigger >= 0.0) /* looking for a triggered breakpoint */
|
|
if ((sim_brk_summ & BP_STRING) == 0 /* if no outstanding service request exists */
|
|
&& entry_time >= bp->trigger) { /* and this breakpoint is now triggered */
|
|
sim_brk_summ |= BP_STRING; /* then indicate that a breakpoint stop is required */
|
|
|
|
if (bp->type & SWMASK ('T')) { /* if a temporary breakpoint just triggered */
|
|
sim_brk_act = strcpy (tempbuf, bp->action); /* then copy the action string */
|
|
|
|
if (prev != NULL) { /* if there is a previous node */
|
|
prev->next = bp->next; /* then link it to the next one, */
|
|
free (bp); /* free the current node */
|
|
bp = prev->next; /* and make the next node current */
|
|
}
|
|
|
|
else { /* otherwise we're clearing the first node */
|
|
sb_list = bp->next; /* so point the list header at the next one, */
|
|
free (bp); /* free the current node */
|
|
bp = sb_list; /* and make the next node current */
|
|
}
|
|
|
|
continue; /* continue the search with the next node */
|
|
}
|
|
|
|
else { /* otherwise it's a persistent breakpoint */
|
|
sim_brk_act = bp->action; /* so copy the action string pointer */
|
|
bp->mptr = bp->match; /* and reset the match pointer */
|
|
bp->trigger = -1.0; /* and the trigger */
|
|
}
|
|
}
|
|
|
|
else if (bp->trigger < next_time) /* otherwise obtain the earliest trigger time */
|
|
next_time = bp->trigger; /* of all of the trigger-delayed breakpoints */
|
|
|
|
prev = bp; /* the current node becomes the prior one */
|
|
bp = bp->next; /* and the next node becomes current */
|
|
}
|
|
|
|
if (next_time < DBL_MAX) { /* if another triggered breakpoint was seen */
|
|
delay = (int32) (next_time - entry_time); /* then get the relative delay */
|
|
|
|
if (delay < 1 && sim_brk_summ & BP_STRING) /* if a breakpoint was triggered in this pass */
|
|
delay = 1; /* ensure that the VM has time to process it */
|
|
|
|
sim_activate (breakpoint_unit, delay); /* reschedule the service to handle the next breakpoint */
|
|
}
|
|
|
|
return SCPE_OK; /* return with success */
|
|
}
|
|
|
|
|
|
/* Local string breakpoint extension routines */
|
|
|
|
|
|
/* Cancel all string breakpoints.
|
|
|
|
This routine cancels all of the string breakpoints. It is called in response
|
|
to the NOBREAK "" and NOBREAK ALL commands. It walks the linked list headed
|
|
by "sb_list" and releases each string breakpoint structure encountered.
|
|
Before returning, it clears the list head pointer and cancels the breakpoint
|
|
delay time, in case a breakpoint had entered the trigger-delayed state.
|
|
*/
|
|
|
|
static void free_breakpoints (void)
|
|
{
|
|
SBPTR bp, node;
|
|
|
|
bp = sb_list; /* start at the list head */
|
|
|
|
while (bp != NULL) { /* if the current node exists */
|
|
node = bp; /* then save a node pointer */
|
|
bp = bp->next; /* point at the next node */
|
|
free (node); /* and free the current one */
|
|
}
|
|
|
|
sb_list = NULL; /* clear the breakpoint list header */
|
|
sim_cancel (breakpoint_unit); /* and cancel the breakpoint timer */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Find a string breakpoint.
|
|
|
|
This routine looks through the list of string breakpoints for one matching
|
|
the supplied string. If a matching breakpoint is found, a pointer to the
|
|
structure is returned. Otherwise, NULL is returned. In either case, a
|
|
pointer to the prior (or last, if no match) structure is returned via the
|
|
second function parameter.
|
|
|
|
A case-sensitive match is performed.
|
|
*/
|
|
|
|
static SBPTR find_breakpoint (char *match, SBPTR *prev)
|
|
{
|
|
SBPTR bp = sb_list; /* start at the list head */
|
|
|
|
*prev = NULL; /* initialize the previous node pointer */
|
|
|
|
while (bp != NULL) /* if the current node exists */
|
|
if (strcmp (match, bp->match) == 0) /* and it matches the search string */
|
|
break; /* then the search is over */
|
|
|
|
else { /* otherwise */
|
|
*prev = bp; /* save the node pointer */
|
|
bp = bp->next; /* and point at the next one */
|
|
}
|
|
|
|
return bp; /* return the matching node pointer or NULL */
|
|
}
|
|
|
|
|
|
/* Test for a string breakpoint.
|
|
|
|
This routine is called when string breakpoints exist and a character is to be
|
|
output. It checks for a match between that character (the test character)
|
|
and the next character in each match string in the linked list of breakpoint
|
|
structures. If a match string is completed, the breakpoint enters the
|
|
trigger-delayed state, and the break delay timer is scheduled if it is not
|
|
running. Matching a particular breakpoint does not inhibit matching against
|
|
all of the other breakpoints.
|
|
|
|
Within each STRING_BREAKPOINT structure in the linked list of breakpoints,
|
|
the "match" field contains the string to match, and the "mptr" field points
|
|
at the next character to check (initially, the first character of the match
|
|
string).
|
|
|
|
If the test character equals the match character, then the match pointer is
|
|
advanced. If the pointer now points at the end of the match string, then the
|
|
breakpoint enters the trigger delayed state. The "trigger" field is set to
|
|
the trigger activation time. This is normally the current time but will be a
|
|
time in the future if a breakpoint delay was specified. If no other
|
|
breakpoint is in this state, the break delay timer will not be active, and so
|
|
the routine will activate it with the specified delay. If the timer is
|
|
already active, the remaining delay time is compared to the delay time for
|
|
the newly matched breakpoint. If the remaining time is greater, the timer is
|
|
reset to the shorter delay, as the new breakpoint will trigger before the
|
|
existing one. Otherwise, the timer continues with the remaining time.
|
|
|
|
If the test character does not equal the current match character, then a
|
|
check is made to see if it matches any prior characters in the match string.
|
|
If it does not, then the match pointer is reset to the start of the string.
|
|
However, if it does, then it's possible that the characters output so
|
|
far match a prior substring of the match string.
|
|
|
|
Consider a match string of "ABABC" that has been matched through the second
|
|
"B", so that the match pointer points at the "C". We are called with an "A"
|
|
as the test character. It does not match "C", but in looking backward, we
|
|
see that it does match the second "A". So we search backward from there to
|
|
see if the output characters match the earlier part of the match string. In
|
|
this case, the last three characters output match the leading substring "ABA"
|
|
of the match string, so the match pointer is reset to point at the fourth
|
|
character, rather than the second. Then, if "BC" is subsequently output, the
|
|
match will succeed.
|
|
|
|
Conceptually, this search begins with:
|
|
|
|
match pointer
|
|
|
|
|
A B A B C match characters
|
|
A B A B A output characters
|
|
|
|
|
test character
|
|
|
|
Because the characters do not match, we "slide" the output string to the left
|
|
to see if we can find a trailing output substring that matches a leading
|
|
match substring. We start with the right-most match character that equals
|
|
the test character
|
|
|
|
first matching character
|
|
|
|
|
A B A B C match characters
|
|
A B A B A output characters
|
|
| | |
|
|
match
|
|
|
|
Here, the last three output string characters match the first three match
|
|
string characters, so we reset the match pointer to the fourth character:
|
|
|
|
match pointer
|
|
|
|
|
A B A B C match characters
|
|
|
|
Now if an additional "B" and "C" are output (i.e., the entire output is
|
|
"ABABABC"), then the breakpoint will trigger on receipt of the "C".
|
|
|
|
A B A B C match characters
|
|
A B A B A B C output characters
|
|
| | | | |
|
|
match
|
|
|
|
Now consider a match string of "ABAB" that has matched through the second
|
|
"A", and we are called with a test character of "A":
|
|
|
|
match pointer
|
|
|
|
|
A B A B match characters
|
|
A B A A output characters
|
|
|
|
|
test character
|
|
|
|
The first substring test does not match:
|
|
|
|
first matching character
|
|
|
|
|
A B A B match characters
|
|
A B A A output characters
|
|
| |
|
|
no match
|
|
|
|
So we search backward for another test character match and try again, and
|
|
this one succeeds:
|
|
|
|
second matching character
|
|
|
|
|
A B A B match characters
|
|
A B A A output characters
|
|
|
|
|
match
|
|
|
|
The match pointer is reset to point at the following "B", and the breakpoint
|
|
will trigger if the subsequent output produces "ABAABAB".
|
|
|
|
Effectively, the search starts with the longest possible substring and then
|
|
attempts shorter and shorter substrings until either a match occurs or no
|
|
substring matches. In the first case, the match pointer is reset
|
|
appropriately, and partial matching continues. In the second, the match
|
|
pointer is reset to the beginning of the match string, and a new match is
|
|
sought.
|
|
|
|
Searching for the longest substring that matches the output stream would
|
|
appear to require an output history buffer. However, the fact that all of
|
|
the prior output characters until the current one have matched means that the
|
|
match string itself IS the history of the relevant part of the output stream.
|
|
We need only search for substrings that equal the substring of the match
|
|
string that ends with the last-matched character.
|
|
|
|
This matching process is repeated for each node in the list of breakpoints.
|
|
*/
|
|
|
|
static void test_breakpoint (int32 test_char)
|
|
{
|
|
char *history, *hptr, *sptr;
|
|
int32 trigger_time;
|
|
SBPTR bp = sb_list; /* start at the list head */
|
|
|
|
while (bp != NULL) { /* if the current node exists */
|
|
if (*bp->mptr != '\0') /* then if the search string is not exhausted */
|
|
if (*bp->mptr == test_char) { /* then if the search character matches */
|
|
bp->mptr++; /* then point at the next search character */
|
|
|
|
if (*bp->mptr == '\0') { /* if the search string is completely matched */
|
|
bp->trigger = /* then set the trigger time */
|
|
sim_gtime () + bp->delay; /* to the current time plus any delay */
|
|
|
|
trigger_time = sim_is_active (breakpoint_unit); /* get any remaining delay time */
|
|
|
|
if (trigger_time == 0 || trigger_time > bp->delay) /* if it's not running or the delay is too long */
|
|
sim_activate_abs (breakpoint_unit, bp->delay); /* then reschedule the timer to the shorter time */
|
|
}
|
|
}
|
|
|
|
else if (bp->mptr != bp->match) { /* otherwise if we have a partial match */
|
|
history = --bp->mptr; /* then save a pointer to the output history */
|
|
|
|
do { /* search for a substring match */
|
|
while (bp->mptr >= bp->match /* while still within the match string */
|
|
&& *bp->mptr != test_char) /* and the search character doesn't match */
|
|
bp->mptr--; /* back up until a matching character is found */
|
|
|
|
if (bp->mptr < bp->match) { /* if no matching character was found */
|
|
bp->mptr = bp->match; /* then reset the search pointer to the start */
|
|
sptr = NULL; /* and exit the substring search */
|
|
}
|
|
|
|
else { /* otherwise there is a potential substring match */
|
|
hptr = history; /* so set up the output history */
|
|
sptr = bp->mptr - 1; /* and matching substring pointers */
|
|
|
|
while (sptr >= bp->match /* test for a substring match */
|
|
&& *sptr == *hptr) { /* in reverse */
|
|
sptr--; /* until a match fails */
|
|
hptr--; /* or the entire substring matches */
|
|
}
|
|
|
|
if (sptr < bp->match) { /* if a matching substring was found */
|
|
bp->mptr++; /* then point at the next character to match */
|
|
sptr = NULL; /* and exit the substring search */
|
|
}
|
|
|
|
else /* otherwise the substring did not match */
|
|
bp->mptr = sptr; /* so try the next shorter substring */
|
|
}
|
|
}
|
|
while (sptr); /* continue testing until a match or exhaustion */
|
|
}
|
|
|
|
bp = bp->next; /* point at the next breakpoint node */
|
|
} /* and continue until all nodes are checked */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* ************* Concurrent Console Mode and Reply Extensions ****************
|
|
|
|
This module extends the following existing routines in "hp----_cpu.c" and
|
|
"sim_console.c":
|
|
|
|
sim_instr -- execute simulated machine instructions
|
|
|
|
sim_poll_kbd -- poll the console keyboard for input
|
|
|
|
The instruction execution routine is extended to process commands entered in
|
|
concurrent console mode. The keyboard poll routine is extended to allow
|
|
entry of commands concurrently with instruction execution and also to supply
|
|
previously established character string replies automatically.
|
|
|
|
In the normal console mode, entry of SCP commands first requires instruction
|
|
execution to be stopped by entering the WRU character (default is CTRL+E).
|
|
This prints "Simulation stopped" on the console, terminates instruction
|
|
execution, and returns to the "sim>" prompt. At this point, a command such
|
|
as a tape image attachment may be entered, and then execution may be resumed.
|
|
|
|
The problem with this is that while instruction execution is stopped, the
|
|
simulated time-of-day clock is also stopped. It that clock had been set
|
|
accurately at target OS startup, it will lose time each time an SCP command
|
|
must be entered.
|
|
|
|
To alleviate this, a "concurrent" console mode may be established. In this
|
|
mode, entering CTRL+E does not terminate instruction execution but rather
|
|
diverts console keystrokes into a separate command buffer instead of
|
|
returning them to the VM. During command entry, instructions continue to
|
|
execute, so the simulated time-of-day clock remains accurate. When the
|
|
command is terminated with ENTER, the VM's "sim_instr" routine returns to the
|
|
extension shim. The shim executes the command and then automatically resumes
|
|
instruction execution. The simulated clock is stopped for the command
|
|
execution time, but that time is usually shorter than one clock tick and so
|
|
is absorbed by the clock calibration routine.
|
|
|
|
The extended keyboard poll routine switches between "Console" mode, where the
|
|
characters are delivered to the VM, and "Command" mode, where the characters
|
|
are delivered to a command buffer. Entry into command mode is made by
|
|
sensing CTRL+E, and exit from command mode is made by sensing ENTER. Limited
|
|
editing is provided in Command mode because the poll routine obtains
|
|
characters, not keystrokes, and so can't sense the non-character keys such as
|
|
the arrow keys.
|
|
|
|
Concurrent console mode is an option, set by the SET CONSOLE [NO]CONCURRENT
|
|
command.
|
|
*/
|
|
|
|
|
|
/* Global concurrent console and reply extension routines */
|
|
|
|
|
|
/* Execute CPU instructions.
|
|
|
|
This shim for the virtual machine's "sim_instr" routine detects commands
|
|
entered in concurrent console mode, executes them, and then calls "sim_instr"
|
|
again. This loop continues until a simulation stop condition occurs.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. On Unix systems, WRU is registered as the SIGINT character, so CTRL+E
|
|
does not arrive via the keyboard poll. Instead, the SIGINT handler
|
|
installed by the "run_cmd" routine is called, which sets the "stop_cpu"
|
|
flag. This value is tested in "sim_process_event", which returns
|
|
SCPE_STOP in response. This causes the VM's "sim_instr" to stop
|
|
execution and print "Simulation stopped" before returning. We cannot
|
|
test "stop_cpu" in our "ex_sim_poll_kbd" routine to trigger the
|
|
concurrent mode prompt because "sim_process_event" is called after every
|
|
machine instruction, so it will be seen there before we can act on it in
|
|
our keyboard poll. So instead we must replace the installed SIGINT
|
|
handler with one of our own that sets a local "stop_requested" flag that
|
|
is tested in our keyboard poll.
|
|
|
|
2. When the system console is connected to a serial port, we fake a Telnet
|
|
connection by setting "sim_con_tmxr.master" non-zero during instruction
|
|
execution. This tricks the "sim_poll_kbd" and "sim_putchar[_s]" routines
|
|
in "sim_console.c" into calling the terminal multiplexer routines, which
|
|
will read from or write to the serial console.
|
|
|
|
3. Leading spaces must be skipped before calling "get_glyph" to parse the
|
|
command keyword, as that routine uses spaces to mark the end of the
|
|
keyword. If a leading space is present, "get_glyph" will return a null
|
|
string as the keyword, and "find_cmd" (called via "get_command") will
|
|
return a successful match with the first entry in the command table.
|
|
|
|
4. The routine must restore the console to "command mode" to reenable text
|
|
mode on the console log, which is necessary to obtain the correct line
|
|
ends on logged commands. It must also save and restore the command line
|
|
switches in effect at entry, so that any switches present in the entered
|
|
command line aren't incorrectly used by the VM's "sim_instr" routine.
|
|
|
|
5. With one exception, entered commands must be "unrestricted", i.e., must
|
|
not interfere with the partial unwinding of the VM run state. The
|
|
exception is the DO command. To allow command files to contain
|
|
prompt/response actions, we handle DO specially by setting the global
|
|
"concurrent_do_ptr" to point at the DO command line and then stop
|
|
execution to unwind the run state. The DO command is then handled in the
|
|
"ex_run_cmd" routine by executing the command file and then automatically
|
|
reentering this routine to resume instruction execution.
|
|
|
|
7. We can't simply return SCPE_EXIT in response to an EXIT command because
|
|
the standard "run_cmd" routine ignores the return status and always
|
|
returns SCPE_OK to the command loop. To get the loop to exit, we could
|
|
either set a global here and return SCPE_EXIT from "ex_run_cmd" in
|
|
response, or set up EXIT as a "breakpoint" action to be executed upon
|
|
return. The latter option is implemented.
|
|
*/
|
|
|
|
t_stat sim_instr (void)
|
|
{
|
|
SIG_HANDLER prior_handler;
|
|
char *cptr, gbuf [CBUFSIZE], tbuf [CBUFSIZE];
|
|
t_stat status, reason;
|
|
int32 saved_switches;
|
|
CTAB *cmdp;
|
|
|
|
prior_handler = signal (SIGINT, wru_handler); /* install our WRU handler in place of the current one */
|
|
|
|
if (prior_handler == SIG_ERR) /* if installation failed */
|
|
status = SCPE_SIGERR; /* then report an error */
|
|
|
|
else do { /* otherwise */
|
|
stop_requested = FALSE; /* clear any pending WRU stop */
|
|
|
|
if (serial_line (sim_con_tmxr.ldsc) != NULL) /* if the system console is on a serial port */
|
|
sim_con_tmxr.master = 1; /* then fake a Telnet connection */
|
|
|
|
status = vm_sim_instr (); /* call the instruction executor */
|
|
|
|
if (serial_line (sim_con_tmxr.ldsc) != NULL) /* if the system console is on a serial port */
|
|
sim_con_tmxr.master = 0; /* then clear the fake Telnet connection */
|
|
|
|
if (status == SCPE_EXEC) { /* if a concurrent command was entered */
|
|
cptr = cmd_buf; /* then point at the command buffer */
|
|
|
|
ex_substitute_args (cptr, tbuf, sizeof cmd_buf, NULL); /* substitute variables in the command line */
|
|
|
|
while (isspace (*cptr)) /* remove any leading spaces */
|
|
cptr++; /* that would confuse the "get_glyph" routine */
|
|
|
|
if (*cptr == '\0') /* if the command was entirely blank */
|
|
continue; /* then ignore it */
|
|
|
|
sim_ttcmd (); /* return the console to command state */
|
|
|
|
if (sim_log) /* if the console is being logged */
|
|
fprintf (sim_log, "\nscp> %s\n", cptr); /* then echo the command to the log file */
|
|
|
|
if (*cptr == ';') { /* if a comment was entered */
|
|
sim_ttrun (); /* then return the console to run mode */
|
|
continue; /* and ignore the command */
|
|
}
|
|
|
|
saved_switches = sim_switches; /* save the switches currently in effect */
|
|
sim_switches = 0; /* and reset them to avoid interference */
|
|
|
|
cptr = get_glyph (cptr, gbuf, 0); /* parse the command keyword */
|
|
|
|
reason = get_command (gbuf, &cmdp); /* get the command descriptor */
|
|
|
|
if (cmdp != NULL /* if the command is valid */
|
|
&& cmdp->action == ex_do_handler) { /* and is a DO command */
|
|
concurrent_do_ptr = cptr; /* then point at the parameters */
|
|
status = SCPE_OK; /* and stop execution */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
if (reason == SCPE_OK) /* if the command is legal */
|
|
reason = cmdp->action (cmdp->arg, cptr); /* then execute it */
|
|
|
|
if (reason != SCPE_OK) /* if an error is indicated */
|
|
if (reason == SCPE_EXIT) { /* the if the command was EXIT (or QUIT or BYE) */
|
|
sim_brk_act = "exit"; /* then set execute an EXIT command on return */
|
|
status = SCPE_STOP; /* and stop execution */
|
|
}
|
|
|
|
else if (cmdp != NULL /* otherwise if the command is known */
|
|
&& cmdp->action == ex_restricted_cmd /* and is */
|
|
&& cmdp->arg == EX_ABORT) { /* an ABORT command */
|
|
stop_requested = TRUE; /* then set the flag */
|
|
status = SCPE_STOP; /* and handle it as a simulation stop */
|
|
}
|
|
|
|
else { /* otherwise report the error */
|
|
printf ("%s\n", sim_error_text (reason));
|
|
|
|
if (sim_log) /* if the console is being logged */
|
|
fprintf (sim_log, "%s\n", /* then report it to the log as well */
|
|
sim_error_text (reason));
|
|
}
|
|
|
|
if (sim_vm_post != NULL) /* if the VM wants command notification */
|
|
(*sim_vm_post) (TRUE); /* then let it know we executed a command */
|
|
}
|
|
|
|
sim_ttrun (); /* return the console to run mode */
|
|
sim_switches = saved_switches; /* and restore the original switches */
|
|
}
|
|
}
|
|
|
|
while (status == SCPE_EXEC); /* continue execution if stopped for a command */
|
|
|
|
if (status != SCPE_SIGERR) /* if the signal handler was set up properly */
|
|
signal (SIGINT, prior_handler); /* then restore the prior handler */
|
|
|
|
return status; /* return the result of instruction execution */
|
|
}
|
|
|
|
|
|
/* Poll the console keyboard.
|
|
|
|
This shim for "sim_poll_kbd" polls the console keyboard for keystrokes and
|
|
delivers the resulting characters to the caller. The routine extends the
|
|
standard one to supply automatic responses for the REPLY command and to
|
|
enable a "concurrent" command mode that allows SCP commands to be entered
|
|
without stopping simulation execution.
|
|
|
|
During simulator execution, the system console is connected to the simulation
|
|
console by default. While it is so connected, keystrokes entered at the
|
|
simulation console are delivered to the system console device.
|
|
|
|
With a SET CONSOLE TELNET command, the system console may be redirected to a
|
|
Telnet port. After this separation, two console windows exist: the
|
|
simulation console remains attached to the originating command window, while
|
|
the system console is attached to the Telnet client window. When the system
|
|
console window has the input focus, keystrokes are delivered to the system
|
|
console device. When the simulation console window has the focus, keystrokes
|
|
are delivered to SCP.
|
|
|
|
In non-concurrent mode, SCP responds only to CTRL+E and ignores all other
|
|
keystrokes. If concurrent mode is enabled with a SET CONSOLE CONCURRENT
|
|
command, then the simulation console becomes interactive after a CTRL+E while
|
|
simulator execution continues. During this time, system console operation
|
|
depends on whether the simulation and system consoles are joined or
|
|
separated.
|
|
|
|
In concurrent mode, the simulation console is in one of two states: Console
|
|
or Command. It starts in Console state. If the simulation and system
|
|
consoles are joined, then keystrokes are delivered to the system console. If
|
|
they are separated, then keystrokes are ignored.
|
|
|
|
Pressing CTRL+E prints an "scp> " prompt on the simulation console, and the
|
|
console switches to the Command state. While execution continues, keystrokes
|
|
forming an SCP command are placed in a command buffer; the command is
|
|
executed when ENTER is pressed. Limited editing is provided in the Command
|
|
state. Pressing BACKSPACE deletes the last character entered, and pressing
|
|
ESCAPE clears all characters. Pressing ENTER with no characters present
|
|
terminates the Command state and returns to the Console state.
|
|
|
|
In the Command state, if the simulation and system console are joined, then
|
|
the system console device will not receive any keyboard input, and any
|
|
console output call will return with an SCPE_STALL result. To the user,
|
|
pressing CTRL+E pauses any output in progress to the system console, which
|
|
resumes automatically after the entered command is executed. If the
|
|
simulation and system console are separate, then the system console continues
|
|
to function normally while the command is being entered at the simulation
|
|
console.
|
|
|
|
Pressing CTRL+E while in the Command state causes a simulation stop, just as
|
|
it does in non-concurrent mode. The console is returned to Console state,
|
|
and any partially entered command is abandoned.
|
|
|
|
While in Console mode, this routine supplies characters from a prior REPLY
|
|
command as though they were typed by the user. This allows command files to
|
|
contain automated prompt/response pairs for console interaction.
|
|
|
|
If a reply exists, a check is made to ensure that any specified reply delay
|
|
has been met. If it hasn't, then keyboard polling is performed normally. If
|
|
it has, or if we are in the middle of a reply, the next character in the
|
|
reply string is returned to the caller. If the next character is a NUL, then
|
|
the reply is exhausted, and the reply context is cleared.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. It would be nice to have better editing capability in Command mode, e.g.,
|
|
to be able to recall from a command history and edit the resulting line.
|
|
Unfortunately, the standard "sim_poll_kbd" routine returns characters and
|
|
not keystrokes, so it is impossible to detect, e.g., an up-arrow key. It
|
|
might be easy to implement a one-line recall command by restoring the
|
|
command buffer to the state just prior to ENTER, though then only the
|
|
usual editing (BS and typing replacements) would be available.
|
|
|
|
2. Currently, only a reply to a single device (the console) is allowed, so
|
|
the reply list head pointer is used directly. In the future, a linked
|
|
list of reply structures will be used, and the routine will have to
|
|
search the list for the one matching the console unit.
|
|
*/
|
|
|
|
t_stat ex_sim_poll_kbd (void)
|
|
{
|
|
RPPTR rp;
|
|
int32 reply_char;
|
|
t_stat key_char;
|
|
|
|
rp = rp_list; /* start searching at the head of the reply list */
|
|
|
|
if (keyboard_mode == Console) { /* if we are in console mode */
|
|
if (rp != NULL) { /* then if a REPLY is pending */
|
|
if (rp->rptr > rp->reply /* then if we are already replying */
|
|
|| sim_gtime () >= rp->trigger) { /* or the delay time has been met */
|
|
reply_char = (int32) *rp->rptr++; /* then get the reply next character */
|
|
|
|
if (reply_char == 0) /* if it's the end-of-string NUL */
|
|
rp_list = NULL; /* then clear the reply */
|
|
|
|
else if (reply_char == sim_brk_char) /* otherwise if it's the break character */
|
|
return SCPE_BREAK; /* then report the break */
|
|
|
|
else /* otherwise */
|
|
return reply_char | SCPE_KFLAG; /* return the reply character */
|
|
}
|
|
}
|
|
|
|
if (stop_requested) { /* if WRU was detected via a signal */
|
|
key_char = SCPE_STOP; /* then indicate a simulator stop */
|
|
stop_requested = FALSE; /* and clear the request */
|
|
}
|
|
|
|
else /* otherwise */
|
|
key_char = sim_poll_kbd (); /* poll the keyboard for a key */
|
|
|
|
if (key_char == SCPE_STOP && concurrent_mode) { /* if it's the sim stop character and in concurrent mode */
|
|
keyboard_mode = Command; /* then switch to command mode */
|
|
|
|
put_string ("\r\nscp> "); /* print the concurrent command prompt */
|
|
|
|
cmd_ptr = cmd_buf; /* reset the command buffer pointer */
|
|
*cmd_ptr = '\0'; /* and clear any previous command */
|
|
|
|
return SCPE_OK; /* return while absorbing the character */
|
|
}
|
|
|
|
else /* otherwise */
|
|
return key_char; /* return the character */
|
|
}
|
|
|
|
else { /* otherwise we're in command mode */
|
|
if (stop_requested) { /* if WRU was detected via a signal */
|
|
key_char = SCPE_STOP; /* then indicate a simulator stop */
|
|
stop_requested = FALSE; /* and clear the request */
|
|
}
|
|
|
|
else /* otherwise */
|
|
key_char = sim_os_poll_kbd (); /* poll the simulation console keyboard for a key */
|
|
|
|
if (key_char == SCPE_STOP) { /* if it's the sim stop character */
|
|
keyboard_mode = Console; /* then return to console mode */
|
|
|
|
put_string ("\r\n"); /* skip to the next line */
|
|
|
|
cmd_ptr = cmd_buf; /* reset the command buffer pointer */
|
|
*cmd_ptr = '\0'; /* and clear any pending command */
|
|
|
|
return SCPE_STOP; /* stop the simulator */
|
|
}
|
|
|
|
else if (key_char & SCPE_KFLAG) { /* otherwise if a character was obtained */
|
|
key_char = key_char & 0177; /* then mask to just the value */
|
|
|
|
if (key_char == CR || key_char == LF) { /* if the character is carriage return or line feed */
|
|
keyboard_mode = Console; /* then return to console mode */
|
|
|
|
put_string ("\r\n"); /* skip to the next line */
|
|
|
|
if (cmd_ptr != cmd_buf) { /* if the buffer is occupied */
|
|
*cmd_ptr = '\0'; /* then terminate the command buffer */
|
|
return SCPE_EXEC; /* and execute the command */
|
|
}
|
|
}
|
|
|
|
else if (key_char == BS || key_char == DEL) { /* otherwise if the character is backspace or delete */
|
|
if (cmd_ptr > cmd_buf) { /* then if the buffer contains characters */
|
|
cmd_ptr--; /* then drop the last one */
|
|
put_string ("\b \b"); /* and clear it from the screen */
|
|
}
|
|
}
|
|
|
|
else if (key_char == ESC) /* otherwise if the character is escape */
|
|
while (cmd_ptr > cmd_buf) { /* then while characters remain in the buffer */
|
|
cmd_ptr--; /* then drop them one by one */
|
|
put_string ("\b \b"); /* and clear them from the screen */
|
|
}
|
|
|
|
else { /* otherwise it's a normal character */
|
|
*cmd_ptr++ = (char) (key_char); /* so add it to the buffer and advance the pointer */
|
|
sim_os_putchar (key_char); /* and echo it to the screen */
|
|
}
|
|
}
|
|
|
|
if (sim_con_tmxr.master != 0) /* if the consoles are separate */
|
|
return sim_poll_kbd (); /* then poll the system console keyboard */
|
|
else /* otherwise we're in unified command mode */
|
|
return SCPE_OK; /* so return with any obtained character absorbed */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Local concurrent console and reply extension routines */
|
|
|
|
|
|
/* Signal handler for CTRL+E.
|
|
|
|
This routine is a SIGINT handler that is installed to detect CTRL+E on Unix
|
|
systems and CTRL+C on others. It is used in place of the standard
|
|
"int_handler" routine. That routine sets the global "stop_cpu" flag, which
|
|
is tested in "sim_process_event" and causes simulated execution to stop. For
|
|
concurrent console operation, we must detect the condition without setting
|
|
the global flag. So this routine sets a local flag that is tested by our
|
|
"ex_sim_poll_kbd" routine to switch from Console to Command mode.
|
|
|
|
It is also used in our "execute_file" command to abort a DO command file that
|
|
may be stuck in an infinite loop.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. On Unix systems, WRU is registered as the SIGINT character, so CTRL+E
|
|
does not arrive via the keyboard poll. Instead, a SIGINT handler is
|
|
used, which is why we need this handler to detect initiation of Command
|
|
mode on Unix.
|
|
*/
|
|
|
|
static void wru_handler (int sig)
|
|
{
|
|
stop_requested = TRUE; /* indicate that WRU was seen */
|
|
return; /* and continue execution */
|
|
}
|
|
|
|
|
|
/* Write a string of characters to the console */
|
|
|
|
static void put_string (const char *cptr)
|
|
{
|
|
while (*cptr != '\0') /* write characters to the console */
|
|
sim_os_putchar (*cptr++); /* until the end of the string */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Get a command descriptor.
|
|
|
|
This routine searches for the command whose name is indicated by the "cptr"
|
|
parameter and copies the corresponding command descriptor (CTAB) entry
|
|
pointer into the pointer variable designated by the "cmdp" pointer. If the
|
|
command is not found, the routine sets the pointer variable to NULL and
|
|
returns SCPE_UNK (Unknown command). If the command is found but is
|
|
restricted, the routine returns SCPE_NOFNC (Command not allowed). Otherwise,
|
|
the routine returns SCPE_OK.
|
|
|
|
This routine is similar to the standard "find_cmd" routine, except that it
|
|
also checks to see if the simulator is currently running and, if so, that the
|
|
command is in the list of unrestricted commands. A command entered while the
|
|
simulator is running must not interfere with execution; those that do are
|
|
deemed "restricted" commands.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The unrestricted ("allowed") command list is structured as a simple
|
|
string, with the command names preceded and followed by spaces. This
|
|
allows a simple "strstr" search to look for a match.
|
|
|
|
2. We search the list of unrestricted commands for the full command name to
|
|
avoid false matches by, e.g., searching for the entered command "R"
|
|
instead of "RUN". We also add leading and trailing blanks before
|
|
searching to ensure that we haven't matched a substring of an
|
|
unrestricted command (although currently there are no restricted commands
|
|
that are substrings of unrestricted commands).
|
|
|
|
3. The "cptr" parameter cannot be declared "const" because it is passed to
|
|
"find_cmd", which takes a non-const pointer.
|
|
*/
|
|
|
|
static const char allowed_cmds [] = " " /* the list of unrestricted commands */
|
|
"RESET " "EXAMINE " "DEPOSIT " "EVALUATE " "BREAK " /* standard commands */
|
|
"NOBREAK " "ATTACH " "DETACH " "ASSIGN " "DEASSIGN "
|
|
"EXIT " "QUIT " "BYE " "SET " "SHOW "
|
|
"DO " "ECHO " "ASSERT " "HELP "
|
|
|
|
"REPLY " "NOREPLY " "IF " "DELETE " "ABORT " /* extended commands */
|
|
"FLUSH "
|
|
|
|
"POWER "; /* simulator-specific commands */
|
|
|
|
static t_stat get_command (char *cptr, CTAB **cmdp)
|
|
{
|
|
char cmd_name [80];
|
|
t_stat status;
|
|
|
|
*cmdp = find_cmd (cptr); /* search for the command */
|
|
|
|
if (*cmdp == NULL) /* if the command is not valid */
|
|
status = SCPE_UNK; /* then report it as unknown */
|
|
|
|
else if (sim_is_running) { /* otherwise if commands are currently restricted */
|
|
cmd_name [0] = ' '; /* then surround */
|
|
strcpy (cmd_name + 1, (*cmdp)->name); /* the command name */
|
|
strcat (cmd_name, " "); /* with leading and trailing blanks */
|
|
|
|
if (strstr (allowed_cmds, cmd_name) == NULL) /* if the command keyword was not found in the list */
|
|
status = SCPE_NOFNC; /* then the command is restricted */
|
|
else /* otherwise */
|
|
status = SCPE_OK; /* the command is allowed */
|
|
}
|
|
|
|
else /* otherwise commands are not restricted */
|
|
status = SCPE_OK; /* so the command is valid */
|
|
|
|
return status; /* return the search result */
|
|
}
|
|
|
|
|
|
|
|
/* ************************ SCP Command Extensions ***************************
|
|
|
|
This module extends the following existing commands:
|
|
|
|
RUN -- reset and start simulation
|
|
GO -- start simulation
|
|
STEP -- execute <n> instructions
|
|
CONTINUE -- continue simulation
|
|
BOOT -- bootstrap simulation
|
|
BREAK -- set breakpoints
|
|
NOBREAK -- clear breakpoints
|
|
DO -- execute commands in a file
|
|
SET -- set simulator options
|
|
SHOW -- show simulator options
|
|
|
|
...and adds the following new commands:
|
|
|
|
REPLY -- send characters to the console
|
|
NOREPLY -- cancel a pending reply
|
|
IF -- execute commands if condition TRUE
|
|
DELETE -- delete a file
|
|
GOTO -- transfer control to the labeled line
|
|
CALL -- call the labeled subroutine
|
|
RETURN -- return control from a subroutine
|
|
ABORT -- abort nested command files
|
|
FLUSH -- flush all buffered files
|
|
|
|
The RUN and GO commands are enhanced to add an UNTIL option that sets a
|
|
temporary breakpoint, and all of the simulated execution commands are
|
|
enhanced to save and restore an existing SIGINT handler that may be in effect
|
|
within a DO command file. The BREAK and NOBREAK commands are enhanced to
|
|
provide temporary and string breakpoints. DO is enhanced to provide GOTO,
|
|
CALL, RETURN, and ABORT commands that affect the flow of control. SET and
|
|
SHOW are enhanced to provide access to environment variables, to provide
|
|
serial support to the system console, and to provide a concurrent command
|
|
mode during simulated execution.
|
|
|
|
The new REPLY and NOREPLY commands enable and disable automated responses
|
|
from the system console. IF provides conditional command execution. DELETE
|
|
provides a host-independent method of deleting a file, such as a temporary or
|
|
scratch file.
|
|
|
|
Also, an extension is provided to add optional binary data interpretation to
|
|
the existing octal, decimal, and hexadecimal command-line overrides.
|
|
|
|
In addition, command-line parameter substitution is extended to provide a set
|
|
of substitution variables that yield the current date and time in various
|
|
formats, as well as environment variable values. Combined with the new SET
|
|
ENVIRONMENT and IF commands, arbitrary variable values may be set, tested,
|
|
and used to affect command file execution.
|
|
*/
|
|
|
|
|
|
/* Global SCP command extension handler routines */
|
|
|
|
|
|
/* Execute the BREAK and NOBREAK commands.
|
|
|
|
This command processing routine enhances the existing BREAK and NOBREAK
|
|
commands to provide temporary and string breakpoints. The routine processes
|
|
commands of the form:
|
|
|
|
BREAK { -T } <address-list> { ; <action> ... }
|
|
BREAK { -T } <quoted-string> { ; <action> ... }
|
|
BREAK { -T } <quoted-string> DELAY <delay> { ; <action> ... }
|
|
BREAK DELAY <delay>
|
|
NOBREAK <quoted-string>
|
|
NOBREAK ""
|
|
NOBREAK ALL
|
|
|
|
Where:
|
|
|
|
-T = indicates that the breakpoint is temporary
|
|
|
|
delay = the number of event ticks that elapse after the breakpoint is
|
|
satisfied before execution is stopped; default is 0
|
|
|
|
The new "-T" breakpoint type switch indicates that the breakpoint will be set
|
|
temporarily. A temporary breakpoint is removed once it occurs; this is
|
|
equivalent to setting the (first) breakpoint action to NOBREAK. Without
|
|
"-T", the breakpoint is persistent and will cause a simulation stop each time
|
|
it occurs.
|
|
|
|
String breakpoints cause simulator stops when the character sequences are
|
|
encountered in the system console output stream; these are similar to stops
|
|
that occur when CPU execution reaches specified memory addresses.
|
|
|
|
The first string form sets a breakpoint that stops simulator execution when
|
|
the content of the quoted string appears in the system console output. By
|
|
default, the simulator stops immediately after the final character of the
|
|
quoted string is output. The second string form may be used to insert a
|
|
delay of the specified number of event ticks (e.g., machine instructions)
|
|
before execution stops. The delay is set temporarily for that breakpoint; it
|
|
then reverts to a zero delay for subsequent breakpoints.
|
|
|
|
If all of the string breakpoints for a program require a delay, it may be set
|
|
as the new default by using the BREAK DELAY command.
|
|
|
|
The optional action commands are executed when the breakpoint occurs. If the
|
|
breakpoint is temporary, the actions are executed once; otherwise, they
|
|
execute each time the breakpoint occurs.
|
|
|
|
The first NOBREAK command form cancels the string breakpoint specified by the
|
|
quoted string. The second form cancels all string breakpoints; the empty
|
|
quoted string is required to differentiate between canceling string
|
|
breakpoints and canceling the current address breakpoint. The NOBREAK ALL
|
|
command cancels all string breakpoints in addition to canceling all address
|
|
breakpoints.
|
|
|
|
Specifying a quoted string in a BREAK command that matches an existing
|
|
breakpoint replaces the delay and actions of that breakpoint with the values
|
|
specified in the new BREAK command. It does not create a second breakpoint
|
|
with the same string.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Currently, only one type of string breakpoint is defined (the implicit
|
|
"BP_STRING" type), although we include all command-line switches in the
|
|
"type" field of the breakpoint structure for future use.
|
|
|
|
2. A NOBREAK command specifying a breakpoint string that does not match any
|
|
existing breakpoint succeeds with no warning message to be consistent
|
|
with the behavior of NOBREAK <address> that does match an existing
|
|
breakpoint.
|
|
*/
|
|
|
|
#define SIM_BREAK_MASK ((1u << 26) - 1 & ~SWMASK ('T')) /* mask for the alpha switches except "T" */
|
|
|
|
static t_stat ex_break_cmd (int32 flag, char *cptr)
|
|
{
|
|
SBPTR bp, prev;
|
|
char *aptr, *optr, mbuf [CBUFSIZE];
|
|
int32 delay;
|
|
t_stat status;
|
|
|
|
cptr = get_sim_sw (cptr); /* get any command-line switches */
|
|
|
|
if (cptr == NULL) /* if an invalid switch was present */
|
|
return SCPE_INVSW; /* then report it */
|
|
else /* otherwise */
|
|
optr = cptr; /* save the original command-line pointer */
|
|
|
|
if (flag == SSH_ST && (*cptr == 'd' || *cptr == 'D')) { /* if this might be a BREAK DELAY command */
|
|
status = parse_delay (&cptr, &delay); /* then attempt to parse a DELAY clause */
|
|
|
|
if (status != SCPE_OK) /* if the numeric parse failed */
|
|
return status; /* then return the error status */
|
|
|
|
else if (delay >= 0) /* otherwise if the delay was given */
|
|
if (*cptr != '\0') /* then if more characters follow */
|
|
return SCPE_2MARG; /* then too many arguments were given */
|
|
|
|
else { /* otherwise */
|
|
break_delay = delay; /* set the global delay value */
|
|
return SCPE_OK; /* and we're done */
|
|
}
|
|
}
|
|
|
|
if (*cptr == '\'' || *cptr == '"') { /* if a quoted string is present */
|
|
cptr = parse_quoted_string (cptr, mbuf, FALSE); /* then parse it with decoding */
|
|
|
|
if (cptr == NULL) /* if the string is not terminated */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
|
|
else /* otherwise the string is valid */
|
|
if (flag == SSH_CL) /* so if this is a NOBREAK command */
|
|
if (*cptr != '\0') /* then if there are extraneous characters */
|
|
return SCPE_2MARG; /* then report too many arguments */
|
|
|
|
else if (mbuf [0] == '\0') { /* otherwise if the string is empty */
|
|
free_breakpoints (); /* then free all of the string breakpoints */
|
|
return SCPE_OK; /* and we're done */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
bp = find_breakpoint (mbuf, &prev); /* find the specified breakpoint */
|
|
|
|
if (bp != NULL) { /* if it is present */
|
|
if (prev != NULL) /* then if there is a previous node */
|
|
prev->next = bp->next; /* then link it to the next one */
|
|
else /* otherwise we're clearing the first node */
|
|
sb_list = bp->next; /* so point the header at the next one */
|
|
|
|
free (bp); /* free the current node */
|
|
}
|
|
|
|
return SCPE_OK; /* either way, we're done */
|
|
}
|
|
|
|
else { /* otherwise this is a BREAK command */
|
|
aptr = strchr (cptr, ';'); /* so search for actions */
|
|
|
|
if (aptr != NULL) /* if actions are present */
|
|
*aptr++ = '\0'; /* then separate the actions from the breakpoints */
|
|
|
|
if (*cptr == '\0') /* if no DELAY clause follows */
|
|
delay = break_delay; /* then use the global delay value */
|
|
|
|
else { /* otherwise */
|
|
status = parse_delay (&cptr, &delay); /* attempt to parse a DELAY clause */
|
|
|
|
if (status != SCPE_OK) /* if the numeric parse failed */
|
|
return status; /* then return the error status */
|
|
|
|
else if (delay < 0) /* otherwise if the keyword is not DELAY */
|
|
return SCPE_ARG; /* then the syntax is bad */
|
|
|
|
else if (*cptr != '\0') /* otherwise if more characters follow */
|
|
return SCPE_2MARG; /* then too many arguments were given */
|
|
}
|
|
|
|
bp = find_breakpoint (mbuf, &prev); /* see if the string matches an existing breakpoint */
|
|
|
|
if (bp == NULL) { /* if it does not */
|
|
bp = (SBPTR) malloc (sizeof (STRING_BREAKPOINT)); /* then allocate a new breakpoint */
|
|
|
|
if (bp == NULL) /* if the allocation failed */
|
|
return SCPE_MEM; /* then report the error */
|
|
|
|
else if (prev == NULL) /* otherwise if this is the first breakpoint */
|
|
sb_list = bp; /* then set the list header to point at it */
|
|
|
|
else /* otherwise */
|
|
prev->next = bp; /* add it to the end of the existing list */
|
|
}
|
|
|
|
bp->next = NULL; /* set the next node pointer */
|
|
bp->uptr = vm_console_output_unit; /* and the output unit pointer */
|
|
|
|
strcpy (bp->match, mbuf); /* copy the match string */
|
|
bp->mptr = bp->match; /* and set the match pointer to the start */
|
|
|
|
bp->type = sim_switches | BP_STRING; /* add the "string breakpoint" flag */
|
|
bp->count = 0; /* clear the count */
|
|
bp->delay = delay; /* set the delay value */
|
|
bp->trigger = -1.0; /* and clear the trigger time */
|
|
|
|
if (aptr == NULL) /* if no actions were specified */
|
|
bp->action [0] = '\0'; /* then clear the action buffer */
|
|
|
|
else { /* otherwise */
|
|
while (isspace (*aptr)) /* skip any leading blanks */
|
|
aptr++; /* that might precede the first action */
|
|
|
|
strcpy (bp->action, aptr); /* copy the action string */
|
|
}
|
|
|
|
return SCPE_OK; /* return with success */
|
|
}
|
|
}
|
|
|
|
else { /* otherwise */
|
|
if (flag == SSH_ST && (sim_switches & SIM_BREAK_MASK) == 0) /* if no breakpoint type switches are set */
|
|
sim_switches |= sim_brk_dflt; /* then use the specified default types */
|
|
|
|
status = break_handler (flag, optr); /* process numeric breakpoints */
|
|
|
|
if (status == SCPE_OK && flag == SSH_CL) { /* if the NOBREAK succeeded */
|
|
get_glyph (cptr, mbuf, 0); /* then parse out the next glyph */
|
|
|
|
if (strcmp (mbuf, "ALL") == 0) /* if this was a NOBREAK ALL command */
|
|
free_breakpoints (); /* then clear all string breakpoints too */
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute the REPLY and NOREPLY commands.
|
|
|
|
This command processing routine adds new REPLY and NOREPLY commands to
|
|
automate replies through the system console when programmatic input is next
|
|
requested by the target OS. The routine processes commands of the form:
|
|
|
|
REPLY <quoted-string>
|
|
REPLY <quoted-string> DELAY <delay>
|
|
REPLY DELAY <delay>
|
|
NOREPLY
|
|
|
|
Where:
|
|
|
|
delay = the number of event ticks that must elapse before the first
|
|
character of the reply is sent; default is 0
|
|
|
|
The first form supplies the content of the quoted string to the system
|
|
console, character by character, as though entered by pressing keys on the
|
|
keyboard. By default, the first character is supplied to the console device
|
|
immediately after simulation is resumed with a GO or CONTINUE command. The
|
|
second form may be used to insert a delay of the specified number of event
|
|
ticks (e.g., machine instructions) before the first character is supplied.
|
|
|
|
If the second form is used, the delay is set temporarily for that reply; it
|
|
then reverts to a zero delay for subsequent replies. If all of the replies
|
|
to a program require a delay, it may be set as the new default by using the
|
|
third form.
|
|
|
|
The NOREPLY command cancels any pending reply. Replies are also effectively
|
|
canceled when they are consumed.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Currently, only a reply to a single device (the console) is allowed, so
|
|
the reply list head pointer is set to point at a static structure. In
|
|
the future, the structures will be allocated and deallocated dynamically.
|
|
*/
|
|
|
|
static t_stat ex_reply_cmd (int32 flag, char *cptr)
|
|
{
|
|
char rbuf [CBUFSIZE];
|
|
int32 delay;
|
|
t_stat status;
|
|
|
|
if (flag) { /* if this is a NOREPLY command */
|
|
rp_list = NULL; /* then clear any pending reply */
|
|
return SCPE_OK; /* and we're done */
|
|
}
|
|
|
|
else if (*cptr == '\0') /* otherwise if a REPLY has no quoted string */
|
|
return SCPE_MISVAL; /* then report it as missing */
|
|
|
|
if (*cptr == 'd' || *cptr == 'D') { /* if this might be a REPLY DELAY command */
|
|
status = parse_delay (&cptr, &delay); /* then attempt to parse a DELAY clause */
|
|
|
|
if (status != SCPE_OK) /* if the numeric parse failed */
|
|
return status; /* then return the error status */
|
|
|
|
else if (delay >= 0) /* otherwise if the delay was given */
|
|
if (*cptr != '\0') /* then if more characters follow */
|
|
return SCPE_2MARG; /* then too many arguments were given */
|
|
|
|
else { /* otherwise */
|
|
reply_delay = delay; /* set the global delay value */
|
|
return SCPE_OK; /* and we're done */
|
|
}
|
|
}
|
|
|
|
if (*cptr == '\'' || *cptr == '"') { /* if a quoted string is present */
|
|
cptr = parse_quoted_string (cptr, rbuf, FALSE); /* then parse it with decoding */
|
|
|
|
if (cptr == NULL) /* if the string is not terminated */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
|
|
else { /* otherwise the string is valid */
|
|
if (*cptr == '\0') /* if no DELAY clause follows */
|
|
delay = reply_delay; /* then use the global delay value */
|
|
|
|
else { /* otherwise */
|
|
status = parse_delay (&cptr, &delay); /* attempt to parse a DELAY clause */
|
|
|
|
if (status != SCPE_OK) /* if the numeric parse failed */
|
|
return status; /* then return the error status */
|
|
|
|
else if (delay < 0) /* otherwise if the keyword is not DELAY */
|
|
return SCPE_ARG; /* then the syntax is bad */
|
|
|
|
else if (*cptr != '\0') /* otherwise if more characters follow */
|
|
return SCPE_2MARG; /* then too many arguments were given */
|
|
}
|
|
|
|
rp_list = &rpx; /* point at the new reply structure */
|
|
|
|
rp_list->uptr = vm_console_input_unit; /* set the input unit pointer */
|
|
|
|
strcpy (rp_list->reply, rbuf); /* copy the reply string */
|
|
rp_list->rptr = rp_list->reply; /* and point at the starting character */
|
|
|
|
rp_list->trigger = sim_gtime () + delay; /* set the trigger time delay */
|
|
|
|
return SCPE_OK; /* return success */
|
|
}
|
|
}
|
|
|
|
else /* otherwise something other than */
|
|
return SCPE_ARG; /* a quoted string is present */
|
|
}
|
|
|
|
|
|
/* Execute the RUN, GO, STEP, CONTINUE, and BOOT commands.
|
|
|
|
This command processing routine enhances the existing RUN and GO commands to
|
|
provide optional temporary breakpoints. The routine processes RUN and GO
|
|
commands of the form:
|
|
|
|
GO UNTIL <stop-address> { ; <action> ... }
|
|
GO UNTIL <quoted-string> { ; <action> ... }
|
|
GO UNTIL <quoted-string> DELAY <delay> { ; <action> ... }
|
|
GO <start-address> UNTIL <stop-address> { ; <action> ... }
|
|
GO <start-address> UNTIL <quoted-string> { ; <action> ... }
|
|
GO <start-address> UNTIL <quoted-string> DELAY <delay> { ; <action> ... }
|
|
|
|
The "GO UNTIL" command is equivalent to "BREAK -T" and "GO". Multiple
|
|
<stop-address>es, separated by commas, may be specified. For example, "GO 5
|
|
UNTIL 10,20" sets temporary breakpoints at addresses 10 and 20 and then
|
|
resumes simulator execution at address 5. As with the BREAK command,
|
|
specifying a DELAY value sets a temporary delay of the specified number of
|
|
event ticks before execution stops. If a DELAY value is not given, the
|
|
breakpoint uses the default delay set by an earlier BREAK DELAY command, or a
|
|
zero delay if the default has not been overridden.
|
|
|
|
The STEP, CONTINUE, and BOOT commands are unaltered. They are handled here
|
|
so that all execution commands may set the "SIM_SW_HIDE" command switch to
|
|
suppress step and breakpoint messages while executing in command files. This
|
|
allows automated prompt/response pairs to be displayed without cluttering up
|
|
the output with intervening "Step completed" and "Breakpoint" messages.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The DO executor sets a SIGINT handler to permit interrupting an infinite
|
|
command loop. We save and restore this handler around the call to the
|
|
standard RUN command handler because that routine restores the default
|
|
handler instead of the previous handler when it completes. This action
|
|
would cancel the DO executor's handler installation if we did not save
|
|
and restore it here.
|
|
|
|
We save the prior handler by installing the default handler, which is the
|
|
condition the standard RUN handler expects on entry.
|
|
|
|
2. A DO command in concurrent mode must be handled outside of the VM's
|
|
instruction execution routine. This is because we want to permit
|
|
unrestricted commands, such as GO UNTIL, to enable prompt/response
|
|
entries in the command file. But we cannot execute the DO command after
|
|
exiting our routine, e.g., by setting up the command as a breakpoint
|
|
action and then returning, because if we've been called by an enclosing
|
|
command file, returning will advance the file pointer, so that the
|
|
command that invoked us won't be reexecuted. Instead, we must call the
|
|
DO command processor here and then reenter the instruction execution
|
|
routine if no error exists.
|
|
|
|
3. The DO command handler clears "sim_switches" for each command invocation,
|
|
so we must save the run switches and restore them after handling a
|
|
concurrent DO command.
|
|
|
|
4. The global "concurrent_run" flag may be examined within a DO command file
|
|
executing in concurrent mode. The flag is initially FALSE. We save and
|
|
restore the flag on entry and exit to ensure that it remains TRUE if
|
|
simulation is stopped within the DO file (if we set it FALSE on exit, it
|
|
would show FALSE when examined after a command file breakpoint (e.g.),
|
|
even though we were still executing within the context of a
|
|
currently-running session, which would resume when the DO file is
|
|
finished.
|
|
|
|
5. All errors from a concurrent DO file invocation are reported but do not
|
|
stop CPU execution. The exception is the EXIT command, which not only
|
|
stops the CPU but also exits the simulator (the alternative would be to
|
|
treat EXIT as a NOP, but then the DO file would exhibit different
|
|
behavior, depending on whether or not it was invoked in concurrent mode).
|
|
*/
|
|
|
|
static t_stat ex_run_cmd (int32 flag, char *cptr)
|
|
{
|
|
SIG_HANDLER prior_handler;
|
|
char gbuf [CBUFSIZE], pbuf [CBUFSIZE];
|
|
t_stat status;
|
|
t_bool entry_concurrency = concurrent_run; /* save the concurrent run status on entry */
|
|
int32 entry_switches = sim_switches; /* save a copy of the entry switches */
|
|
|
|
keyboard_mode = Console; /* always start in console mode */
|
|
|
|
if (*cptr != '\0' && (flag == RU_RUN || flag == RU_GO)) { /* if something follows and this is a RUN or GO */
|
|
if (*cptr == 'U' || *cptr == 'u') /* then if an UNTIL clause follows */
|
|
pbuf [0] = '\0'; /* there there is no new P value */
|
|
else /* otherwise */
|
|
cptr = get_glyph (cptr, pbuf, 0); /* get the new P value */
|
|
|
|
if (*cptr == '\0') /* if nothing follows the new P value */
|
|
cptr = pbuf; /* then point at the P value */
|
|
|
|
else { /* otherwise */
|
|
cptr = get_glyph (cptr, gbuf, 0); /* get the next glyph */
|
|
|
|
if (strcmp (gbuf, "UNTIL") == 0) /* if this is an UNTIL clause */
|
|
if (*cptr == '\0') /* then if nothing follows */
|
|
return SCPE_MISVAL; /* then report that the address is missing */
|
|
|
|
else if (*cptr == 'D' || *cptr == 'd') /* otherwise if it is immediately followed by a DELAY clause */
|
|
return SCPE_ARG; /* then report a syntax error */
|
|
|
|
else { /* otherwise */
|
|
sim_switches |= SWMASK ('T'); /* add the temporary breakpoint flag */
|
|
|
|
status = ex_break_cmd (SSH_ST, cptr); /* process and set the breakpoint */
|
|
|
|
sim_switches = entry_switches; /* restore the original switches */
|
|
|
|
if (status == SCPE_OK) /* if the breakpoint parsed correctly */
|
|
cptr = pbuf; /* then point at the P value */
|
|
else /* otherwise a parse error occurred */
|
|
return status; /* so report it */
|
|
}
|
|
|
|
else /* otherwise something other than UNTIL follows */
|
|
return SCPE_ARG; /* so report a syntax error */
|
|
}
|
|
}
|
|
|
|
prior_handler = signal (SIGINT, SIG_DFL); /* install the default handler and save the current one */
|
|
|
|
if (prior_handler == SIG_ERR) /* if installation failed */
|
|
status = SCPE_SIGERR; /* then report an error */
|
|
|
|
else { /* otherwise */
|
|
concurrent_run = TRUE; /* mark the VM as running */
|
|
|
|
do { /* loop to process concurrent DO commands */
|
|
concurrent_do_ptr = NULL; /* clear the DO pointer */
|
|
|
|
status = run_handler (flag, cptr); /* call the base handler to run the simulator */
|
|
|
|
if (concurrent_do_ptr == NULL) /* if a DO command was not entered */
|
|
break; /* then fall out of the loop */
|
|
|
|
else { /* otherwise a concurrent DO command was entered */
|
|
strcpy (gbuf, concurrent_do_ptr); /* so copy the command parameters locally */
|
|
status = ex_do_handler (1, gbuf); /* and execute the DO command */
|
|
|
|
if (status != SCPE_OK && status != SCPE_EXIT) { /* if the command failed */
|
|
printf ("%s\n", sim_error_text (status)); /* then print the error message */
|
|
|
|
if (sim_log) /* if the console is logging */
|
|
fprintf (sim_log, "%s\n", /* then write it to the log file as well */
|
|
sim_error_text (status));
|
|
|
|
status = SCPE_OK; /* continue execution unless it was an EXIT command */
|
|
}
|
|
|
|
if (sim_vm_post != NULL) /* if the VM wants command notification */
|
|
(*sim_vm_post) (TRUE); /* then let it know we executed a command */
|
|
|
|
sim_switches = entry_switches; /* restore the original switches */
|
|
}
|
|
}
|
|
while (status == SCPE_OK); /* continue to execute in the absence of errors */
|
|
|
|
concurrent_run = entry_concurrency; /* return to the previous VM-running state */
|
|
|
|
signal (SIGINT, prior_handler); /* restore the prior handler */
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
|
|
|
|
/* Execute the DO command.
|
|
|
|
This command processing routine enhances the existing DO command to permit
|
|
CTRL+C to abort a command file or a nested series of command files. The
|
|
actual command file processing is handled by a subsidiary routine.
|
|
|
|
It also executes commands in a new global initialization file at system
|
|
startup. It looks for the file "simh.ini" in the current directory; if not
|
|
found, it then looks for the file in the HOME or USERPROFILE directory. The
|
|
file is optional; if it exists, it is executed before the command-line file
|
|
or the simulator-specific file.
|
|
|
|
Therefore, the search order for "simh.ini" is the current directory first,
|
|
then the HOME directory if the HOME variable exists, or else the USERPROFILE
|
|
directory if that variable exists. So a user may override a "simh.ini" file
|
|
present in the HOME or USERPROFILE directories by one in the current
|
|
directory.
|
|
|
|
An error encountered in the global initialization file is reported and stops
|
|
execution of that file but does not inhibit execution of the simulator-
|
|
specific initialization file.
|
|
|
|
On entry, the "cptr" parameter points at the invocation string after the DO
|
|
keyword. The "file" parameter is NULL to indicate that the routine is to
|
|
open the filename present at the start of the "cptr" string. The "flag"
|
|
parameter indicates the source of the call and nesting level and contains one
|
|
of these values:
|
|
|
|
< 0 = initialization file (no alternate if not found)
|
|
0 = startup command line file
|
|
1 = "DO" command
|
|
> 1 = nested DO or CALL command
|
|
|
|
For a nested command call, "flag" contains the nesting level in bits 0-3 and
|
|
the value of the invoking switches in bits 4-29. This allows the switch
|
|
settings to propagate to nested command files.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. This routine is always called during system startup before the main
|
|
command loop is entered. It is called to execute the command file
|
|
specified on the command line, or, if there is none, the command file
|
|
associated with the simulator (e.g., "hp2100.ini"). The call occurs even
|
|
if neither of these files exist. We detect this initial call and execute
|
|
the global initialization file before either of these command files.
|
|
|
|
2. We save the command-line switches before executing the global
|
|
initialization file, so that they will apply to the command-line file or
|
|
the local initialization file. Otherwise, execution of a command in the
|
|
global file would reset the switches before the second file is executed.
|
|
|
|
3. The invocations of the global and command/simulator files are considered
|
|
to be separate executions. Consequently, an ABORT in the global file
|
|
terminates that execution but does not affect execution of the
|
|
command/simulator file.
|
|
|
|
4. If the simulator was invoked with command-line parameters, we pass them
|
|
to the global initialization file. So, for example, "%1" will be the
|
|
command filename that will be executed, "%2" will be the first parameter
|
|
passed to that file, etc. In this case, the "flag" parameter will be 0.
|
|
If we are called to execute the simulator-specific initialization file,
|
|
then there must not have been any command-line parameters, and "flag"
|
|
will be -1.
|
|
|
|
5. We use a filename buffer of twice the standard size, because the home
|
|
path and the command-line parameter string may each be up to almost a
|
|
full standard buffer size in length.
|
|
*/
|
|
|
|
static t_stat ex_do_cmd (int32 flag, char *cptr)
|
|
{
|
|
static t_bool first_call = TRUE; /* TRUE if this is the first DO call of the session */
|
|
SIG_HANDLER prior_handler;
|
|
t_stat status;
|
|
int32 entry_switches;
|
|
char separator, filename [CBUFSIZE * 2], *home;
|
|
|
|
prior_handler = signal (SIGINT, wru_handler); /* install our WRU handler in place of the current one */
|
|
|
|
if (prior_handler == SIG_ERR) /* if installation failed */
|
|
status = SCPE_SIGERR; /* then report an error */
|
|
|
|
else { /* otherwise */
|
|
if (first_call) { /* if this is the startup call */
|
|
first_call = FALSE; /* then clear the flag for subsequent calls */
|
|
entry_switches = sim_switches; /* and save the command-line switches */
|
|
|
|
strcpy (filename, "simh.ini "); /* start with the filename in the working directory */
|
|
|
|
if (flag == 0) /* if command-line parameters were specified */
|
|
strcat (filename, cptr); /* then append them to the filename */
|
|
|
|
status = ex_do_handler (-1, filename); /* try to execute the global startup file */
|
|
|
|
if (status == SCPE_OPENERR) { /* if it was not found */
|
|
home = getenv ("HOME"); /* then get the home directory */
|
|
|
|
if (home == NULL) /* if it's not defined */
|
|
home = getenv ("USERPROFILE"); /* then try the profile directory */
|
|
|
|
if (home != NULL) { /* if it's defined */
|
|
separator = home [strcspn (home, "/\\")]; /* then look for a directory separator */
|
|
|
|
if (separator == '\0') /* if there isn't one */
|
|
separator = '/'; /* then guess that "/" will do */
|
|
|
|
sprintf (filename, "%s%csimh.ini %s", /* form the home path and global filename */
|
|
home, separator, /* and add any command-line parameters */
|
|
(flag == 0 ? cptr : "")); /* if they are present */
|
|
|
|
status = ex_do_handler (-1, filename); /* try to execute the global startup file */
|
|
}
|
|
}
|
|
|
|
sim_switches = entry_switches; /* restore the entry switches */
|
|
}
|
|
|
|
status = execute_file (NULL, flag, cptr); /* execute the indicated command file */
|
|
|
|
if (status == SCPE_ABORT && flag <= 1) /* if an abort occurred and we're at the outermost level */
|
|
status = SCPE_OK; /* then clear the error to suppress the abort message */
|
|
|
|
signal (SIGINT, prior_handler); /* restore the prior handler */
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
|
|
|
|
/* Execute the IF command.
|
|
|
|
This command processing routine adds a new IF command to test a condition and
|
|
execute the associated command(s) if the condition is true. The routine
|
|
processes commands of the form:
|
|
|
|
IF { -I } <comparative-expression> <action> { ; <action> ... }
|
|
|
|
Where the comparative expression forms are:
|
|
|
|
<Boolean-expression>
|
|
<Boolean-expression> <logical> <comparative-expression>
|
|
|
|
...and the Boolean expression forms are:
|
|
|
|
<quoted-string> <equality> <quoted-string>
|
|
<quoted-string> IN <quoted-string> { , <quoted-string> ...}
|
|
<quoted-string> NOT IN <quoted-string> { , <quoted-string> ...}
|
|
EXIST <quoted-string>
|
|
NOT EXIST <quoted-string>
|
|
|
|
The logical operators are && (And) and || (Or). The equality operators are
|
|
== (equal to) and != (not equal to).
|
|
|
|
The IN operation returns true if the first quoted string is equal to any of
|
|
the listed quoted strings, and the NOT IN operation returns true if the first
|
|
quoted string is not equal to any of the listed strings.
|
|
|
|
The EXIST operation returns true if the file specified by the quoted string
|
|
exists. The NOT EXIST operation returns true if the file does not exist.
|
|
|
|
If the comparative expression is true, the associated actions are executed.
|
|
If the expression is false, the actions have no effect. Adding the "-I"
|
|
switch causes the comparisons to be made case-insensitively. Evaluation is
|
|
strictly from left to right; embedded parentheses to change the evaluation
|
|
order are not accepted.
|
|
|
|
Typically, one quoted-string is a percent-enclosed substitution variable and
|
|
the other is a literal string. Comparisons are always textual, so, for
|
|
example, "3" is not equal to "03".
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. For a true comparison, the action part of the IF command line is copied
|
|
to a temporary buffer, and the break action pointer is set to point at
|
|
the buffer. This is done instead of simply setting "sim_brk_act" to
|
|
"cptr" because the "memcpy" and "strncpy" functions that are used to copy
|
|
each command into the command buffer produce implementation-defined
|
|
results if the buffers overlap ("cptr" points into the command buffer, so
|
|
"sim_brk_act" would be copying a command within the buffer to the start
|
|
of the same buffer).
|
|
|
|
2. An IF command may appear as an action within a breakpoint command or
|
|
another IF command. To support this, any actions of a true IF command
|
|
are prefixed to any remaining breakpoint or IF actions by concatenating
|
|
the two sets of actions in a temporary buffer. Were this not done, the
|
|
remaining actions would be lost when "sim_brk_act" is pointed at the new
|
|
IF actions.
|
|
|
|
3. Any unexecuted actions must be copied to a separate buffer before
|
|
prefixing, as they will reside in the same buffer that will hold the
|
|
prefix if they are the result of a prior IF command.
|
|
*/
|
|
|
|
typedef enum { /* test operators */
|
|
Comparison, /* == or != operator */
|
|
Existence, /* EXIST or NOT EXIST operator */
|
|
Inclusion /* IN or NOT IN operator */
|
|
} TEST_OP;
|
|
|
|
typedef enum { /* logical operators */
|
|
Assign, /* null operator */
|
|
And, /* AND operator */
|
|
Or /* OR operator */
|
|
} LOGICAL_OP;
|
|
|
|
static t_stat ex_if_cmd (int32 flag, char *cptr)
|
|
{
|
|
static char tempbuf [CBUFSIZE];
|
|
struct stat statbuf;
|
|
int result, condition = 0; /* silence spurious gcc-9.2.0 compiler warning */
|
|
char abuf [CBUFSIZE], bbuf [CBUFSIZE], *tptr;
|
|
int32 bufsize;
|
|
t_bool upshift, invert;
|
|
TEST_OP test;
|
|
LOGICAL_OP logical = Assign;
|
|
t_bool not_done = TRUE; /* TRUE if more comparisons are present */
|
|
|
|
cptr = get_sim_sw (cptr); /* get a possible case-sensitivity switch */
|
|
|
|
if (cptr == NULL) /* if an invalid switch was present */
|
|
return SCPE_INVSW; /* then report it */
|
|
|
|
else if (*cptr == '\0') /* otherwise if the first operand is missing */
|
|
return SCPE_2FARG; /* then report it */
|
|
|
|
upshift = (sim_switches & SWMASK ('I')) != 0; /* TRUE if the comparison is case-insensitive */
|
|
|
|
do { /* loop until all conditionals are processed */
|
|
test = Comparison; /* assume a comparison until proven otherwise */
|
|
|
|
if (*cptr == '\'' || *cptr == '"') { /* if a quoted string is present */
|
|
cptr = parse_quoted_string (cptr, abuf, upshift); /* then get the first operand */
|
|
|
|
if (cptr == NULL) /* if the operand isn't quoted properly */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
|
|
else if (*cptr == '\0') /* otherwise if the operator is missing */
|
|
return SCPE_2FARG; /* then report it */
|
|
|
|
else { /* otherwise */
|
|
cptr = get_glyph (cptr, bbuf, 0); /* parse the next token */
|
|
|
|
if (strcmp (bbuf, "==") == 0) /* if the operator is "equal to" */
|
|
invert = FALSE; /* then we want a true test */
|
|
|
|
else if (strcmp (bbuf, "!=") == 0) /* otherwise if the operator is "not equal to" */
|
|
invert = TRUE; /* then we want an inverted test */
|
|
|
|
else { /* otherwise */
|
|
invert = (strcmp (bbuf, "NOT") == 0); /* a NOT operator inverts the result */
|
|
|
|
if (invert) /* if it was NOT */
|
|
cptr = get_glyph (cptr, bbuf, 0); /* then get another token */
|
|
|
|
if (strcmp (bbuf, "IN") == 0) { /* if it is IN */
|
|
test = Inclusion; /* then this is a membership test */
|
|
result = invert; /* so set the initial matching condition */
|
|
}
|
|
|
|
else /* otherwise the operator is invalid */
|
|
return SCPE_ARG; /* so report it */
|
|
}
|
|
}
|
|
}
|
|
|
|
else { /* otherwise it may be a unary operator */
|
|
cptr = get_glyph (cptr, abuf, 0); /* so get the next token */
|
|
|
|
invert = (strcmp (abuf, "NOT") == 0); /* a NOT operator inverts the result */
|
|
|
|
if (invert) /* if it was NOT */
|
|
cptr = get_glyph (cptr, abuf, 0); /* then get another token */
|
|
|
|
if (strcmp (abuf, "EXIST") == 0) /* if it is EXIST */
|
|
test = Existence; /* then this is a file existence check */
|
|
else /* otherwise */
|
|
return SCPE_ARG; /* the operator is unknown */
|
|
}
|
|
|
|
do { /* loop for membership tests */
|
|
if (*cptr != '\'' && *cptr != '"') /* if a quoted string is not present */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
|
|
cptr = parse_quoted_string (cptr, bbuf, /* get the second operand and upshift it */
|
|
upshift && test != Existence); /* if requested and not a filename */
|
|
|
|
if (cptr == NULL) /* if the operand isn't properly quoted */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
|
|
else if (test == Inclusion) { /* otherwise if this is a membership test */
|
|
if (invert) /* then */
|
|
result &= (strcmp (abuf, bbuf) != 0); /* AND an exclusive check */
|
|
else /* otherwise */
|
|
result |= (strcmp (abuf, bbuf) == 0); /* OR an inclusive check */
|
|
|
|
if (*cptr == ',') /* if the membership list continues */
|
|
while (isspace (*++cptr)); /* then discard the comma and any trailing spaces */
|
|
else /* otherwise */
|
|
test = Comparison; /* exit the membership loop */
|
|
}
|
|
|
|
else if (test == Existence) /* otherwise if this is an existence check */
|
|
result = (stat (bbuf, &statbuf) == 0) ^ invert; /* then test the filename */
|
|
|
|
else /* otherwise compare the operands */
|
|
result = (strcmp (abuf, bbuf) == 0) ^ invert; /* with the appropriate test */
|
|
}
|
|
while (test == Inclusion); /* continue if additional members are present */
|
|
|
|
switch (logical) { /* apply the logical operator */
|
|
case Assign: /* for a null operator */
|
|
condition = result; /* use the condition directly */
|
|
break;
|
|
|
|
case And: /* for a logical AND operator */
|
|
condition = condition & result; /* AND the two results */
|
|
break;
|
|
|
|
case Or: /* for a logical OR operator */
|
|
condition = condition | result; /* OR the two results */
|
|
break;
|
|
} /* all cases are handled */
|
|
|
|
if (*cptr == '\0') /* if the rest of the command is missing */
|
|
return SCPE_2FARG; /* then report it */
|
|
|
|
else if (strncmp (cptr, "&&", 2) == 0) { /* otherwise if an AND operator is present */
|
|
logical = And; /* then record it */
|
|
cptr++; /* and skip over it and continue */
|
|
}
|
|
|
|
else if (strncmp (cptr, "||", 2) == 0) { /* otherwise if an OR operator is present */
|
|
logical = Or; /* then record it */
|
|
cptr++; /* and skip over it and continue */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
not_done = FALSE; /* this is the end of the condition */
|
|
cptr--; /* so back up to point at the action string */
|
|
}
|
|
|
|
while (isspace (*++cptr)); /* discard any trailing spaces */
|
|
}
|
|
while (not_done); /* continue to process logical comparisons until done */
|
|
|
|
if (condition) /* if the comparison is true */
|
|
if (sim_brk_act == NULL) /* then if no unexecuted actions remain */
|
|
sim_brk_act = strcpy (tempbuf, cptr); /* then copy our action string to a buffer */
|
|
|
|
else { /* otherwise */
|
|
strcpy (abuf, sim_brk_act); /* save the unexecuted actions in a separate buffer */
|
|
|
|
tptr = tempbuf; /* point at the action buffer */
|
|
bufsize = CBUFSIZE; /* and initialize the remaining size */
|
|
|
|
copy_string (&tptr, &bufsize, cptr, 0); /* copy the current actions as a prefix */
|
|
|
|
if (bufsize > 1) { /* if space remains */
|
|
*tptr++ = ';'; /* then append the action separator */
|
|
bufsize = bufsize - 1; /* and account for the space */
|
|
|
|
copy_string (&tptr, &bufsize, abuf, 0); /* copy the unexecuted actions */
|
|
}
|
|
|
|
sim_brk_act = tempbuf; /* point at the concatenated action list */
|
|
}
|
|
|
|
return SCPE_OK; /* either way, the command succeeded */
|
|
}
|
|
|
|
|
|
/* Execute the DELETE command.
|
|
|
|
This command processing routine adds a new DELETE command to delete the
|
|
specified file. The routine processes commands of the form:
|
|
|
|
DELETE <filename>
|
|
|
|
It provides a platform-independent way to delete files from a command file
|
|
(e.g., temporary files created by a diagnostic program).
|
|
*/
|
|
|
|
static t_stat ex_delete_cmd (int32 flag, char *cptr)
|
|
{
|
|
if (*cptr == '\0') /* if the filename is missing */
|
|
return SCPE_2FARG; /* then report it */
|
|
|
|
else if (remove (cptr) == 0) /* otherwise if the delete succeeds */
|
|
return SCPE_OK; /* then return success */
|
|
|
|
else /* otherwise */
|
|
return SCPE_OPENERR; /* report that the file could not be opened */
|
|
}
|
|
|
|
|
|
/* Execute the FLUSH command.
|
|
|
|
This command processing routine adds a new FLUSH command to flush all
|
|
buffered files to disc. The routine processes commands of the form:
|
|
|
|
FLUSH
|
|
|
|
For files that are open, the command flushes the console and debug log files,
|
|
the files attached to all of the units of all devices, and log files
|
|
associated with terminal multiplexer lines.
|
|
|
|
The command provides a way to flush buffered file contents to disc without
|
|
having to stop and restart simulated execution. As such, it is useful when
|
|
the console is in concurrent mode. In nonconcurrent mode, stopping the
|
|
simulator to enter the FLUSH command will automatically flush all open files,
|
|
so the command is redundant in that case.
|
|
*/
|
|
|
|
static t_stat ex_flush_cmd (int32 flag, char *cptr)
|
|
{
|
|
if (*cptr != '\0') /* if something follows */
|
|
return SCPE_2MARG; /* then report extraneous characters */
|
|
|
|
else { /* otherwise */
|
|
fflush (NULL); /* flush all open files */
|
|
return SCPE_OK;
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute a restricted command.
|
|
|
|
This command processing routine is called when the user attempts to execute
|
|
from the command line a command that is restricted to command files.
|
|
Commands such as GOTO have no meaning when executed interactively, so we
|
|
simply return "Command not allowed" status here.
|
|
*/
|
|
|
|
static t_stat ex_restricted_cmd (int32 flag, char *ptr)
|
|
{
|
|
return SCPE_NOFNC; /* the command is not allowed interactively */
|
|
}
|
|
|
|
|
|
/* Execute the SET command.
|
|
|
|
This command processing routine enhances the existing SET command to add
|
|
setting environment variables and to extend console modes to include
|
|
concurrent command execution and serial port support. The routine processes
|
|
commands of the form:
|
|
|
|
SET ENVIRONMENT ...
|
|
SET CONSOLE ...
|
|
|
|
The other SET commands are handled by the standard handler.
|
|
*/
|
|
|
|
static CTAB ex_set_table [] = { /* the SET extension table */
|
|
{ "ENVIRONMENT", &ex_set_environment, 0 }, /* SET ENVIRONMENT */
|
|
{ "CONSOLE", &ex_set_console, 0 }, /* SET CONSOLE */
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static t_stat ex_set_cmd (int32 flag, char *cptr)
|
|
{
|
|
char *tptr, gbuf [CBUFSIZE];
|
|
CTAB *cmdp;
|
|
|
|
tptr = get_glyph (cptr, gbuf, 0); /* get the SET target */
|
|
|
|
cmdp = find_ctab (ex_set_table, gbuf); /* find the associated command handler */
|
|
|
|
if (cmdp == NULL) /* if the target is not one of ours */
|
|
return set_handler (flag, cptr); /* then let the base handler process it */
|
|
else /* otherwise */
|
|
return cmdp->action (cmdp->arg, tptr); /* call our handler */
|
|
}
|
|
|
|
|
|
/* Execute the SHOW command.
|
|
|
|
This command processing routine enhances the existing SHOW command to add
|
|
pending string breakpoint and reply displays and to extend console modes to
|
|
display the concurrent command execution mode. The routine processes
|
|
commands of the form:
|
|
|
|
SHOW BREAK ...
|
|
SHOW REPLY ...
|
|
SHOW DELAYS
|
|
SHOW CONSOLE ...
|
|
|
|
The other SHOW commands are handled by the standard handler.
|
|
*/
|
|
|
|
static SHTAB ex_show_table [] = { /* the SHOW extension table */
|
|
{ "BREAK", &ex_show_break, 0 }, /* SHOW BREAK */
|
|
{ "REPLY", &ex_show_reply, 0 }, /* SHOW REPLY */
|
|
{ "DELAYS", &ex_show_delays, 0 }, /* SHOW DELAYS */
|
|
{ "CONSOLE", &ex_show_console, 0 }, /* SHOW CONSOLE */
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static t_stat ex_show_cmd (int32 flag, char *cptr)
|
|
{
|
|
char *tptr, gbuf [CBUFSIZE];
|
|
SHTAB *cmdp;
|
|
t_stat status;
|
|
|
|
cptr = get_sim_sw (cptr); /* get any command-line switches */
|
|
|
|
if (cptr == NULL) /* if an invalid switch was present */
|
|
return SCPE_INVSW; /* then report it */
|
|
|
|
else { /* otherwise */
|
|
tptr = get_glyph (cptr, gbuf, 0); /* get the SHOW target */
|
|
|
|
cmdp = find_shtab (ex_show_table, gbuf); /* find the associated command handler */
|
|
|
|
if (cmdp == NULL) /* if the target is not one of ours */
|
|
return show_handler (flag, cptr); /* then let the base handler process it */
|
|
|
|
else { /* otherwise */
|
|
status = cmdp->action (stdout, NULL, NULL, /* report the option on the console */
|
|
cmdp->arg, tptr);
|
|
|
|
if (sim_log != NULL) /* if a console log is defined */
|
|
cmdp->action (sim_log, NULL, NULL, /* then report again on the log file */
|
|
cmdp->arg, tptr);
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute the SET ENVIRONMENT command.
|
|
|
|
This command processing routine adds a new SET ENVIRONMENT command to create,
|
|
set, and clear variables in the host system's environment. The routine
|
|
processes commands of the form:
|
|
|
|
SET ENVIRONMENT <name>=<value>
|
|
SET ENVIRONMENT <name>=
|
|
|
|
...where <name> is the name of the variable, and value is the (unquoted)
|
|
string value to be set. If the value is missing, the variable is cleared.
|
|
Legal names and values are host-system dependent.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. MSVC does not offer the "setenv" function, so we use their "_putenv"
|
|
function instead. However, that function takes the full "name=value"
|
|
string, rather than separate name and value parameters.
|
|
|
|
2. We explicitly check for an equals sign before calling "get_glyph" because
|
|
while that function will separate the name and value correctly at the
|
|
equals sign, it will also allow the separation character to be a space.
|
|
That would be confusing; for example, "SET ENV a b c" would set variable
|
|
"a" to value "b c".
|
|
|
|
3. While the 4.x version of the SET ENVIRONMENT processor calls "get_glyph",
|
|
which forces all variable names to uppercase, the POSIX standard allows
|
|
lowercase environment variable names and requires them to be distinct
|
|
from uppercase names. This can lead to unexpected behavior on POSIX
|
|
systems. Therefore, we call "get_glyph_nc" to preserve the case of the
|
|
variable name. Note that Windows treats environment variable names
|
|
case-insensitively, so this change only affects POSIX systems.
|
|
*/
|
|
|
|
static t_stat ex_set_environment (int32 flag, char *cptr)
|
|
{
|
|
int result;
|
|
char *bptr;
|
|
|
|
if (*cptr == '\0') /* if no name is present */
|
|
return SCPE_2FARG; /* then report a missing argument */
|
|
|
|
bptr = cptr + strlen (cptr); /* point at the end of the string */
|
|
|
|
while (isspace (*--bptr)) /* if trailing spaces exist */
|
|
*bptr = '\0'; /* then remove them */
|
|
|
|
#if defined (_MSC_VER)
|
|
|
|
result = _putenv (cptr); /* enter the equate into the environment */
|
|
|
|
#else
|
|
|
|
if (cptr [strcspn (cptr, "= ")] != '=') /* if there's no equals sign */
|
|
result = -1; /* then report a bad argument */
|
|
|
|
else { /* otherwise */
|
|
char vbuf [CBUFSIZE]; /* declare a buffer to hold the variable name */
|
|
|
|
bptr = get_glyph_nc (cptr, vbuf, '='); /* split the variable name and value, preserving case */
|
|
result = setenv (vbuf, bptr, 1); /* and enter the equate into the environment */
|
|
}
|
|
|
|
#endif
|
|
|
|
if (result == 0) /* if the assignment succeeds */
|
|
return SCPE_OK; /* then report success */
|
|
else /* otherwise */
|
|
return SCPE_ARG; /* report a bad argument */
|
|
}
|
|
|
|
|
|
/* Execute the SET CONSOLE command.
|
|
|
|
This command processing routine enhances the existing SET CONSOLE command to
|
|
add configuration for concurrent command execution and serial port support.
|
|
The routine processes commands of the form:
|
|
|
|
SET CONSOLE CONCURRENT
|
|
SET CONSOLE NOCONCURRENT
|
|
SET CONSOLE SERIAL ...
|
|
SET CONSOLE NOSERIAL
|
|
|
|
It also intercepts the SET CONSOLE TELNET command to close an existing serial
|
|
connection if a Telnet connection is being established.
|
|
|
|
Because the SET CONSOLE command accepts a comma-separated set of options, we
|
|
must parse each option and decide whether it is a standard option or an
|
|
extension option.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We parse each option without case conversion and then separate the option
|
|
name from its value, if present, with conversion to upper case. This is
|
|
necessary because the option name must be uppercase to match the command
|
|
table, but the option value might be case-sensitive, such as a serial
|
|
port name.
|
|
|
|
2. The SET CONSOLE LOG/NOLOG and SET CONSOLE DEBUG/NODEBUG commands print
|
|
messages to report logging initiation or completion. When executing
|
|
command files, we normally suppress messages by setting "sim_quiet" to 1.
|
|
However, the console messages are useful even in a command-file setting,
|
|
so we restore the saved setting before calling the SET processor. This
|
|
is redundant but causes no harm if a command file is not executing.
|
|
*/
|
|
|
|
static CTAB set_console_table [] = { /* the SET CONSOLE extension table */
|
|
{ "CONCURRENT", &ex_set_concurrent, 1 }, /* SET CONSOLE CONCURRENT */
|
|
{ "NOCONCURRENT", &ex_set_concurrent, 0 }, /* SET CONSOLE NOCONCURRENT */
|
|
{ "SERIAL", &ex_set_serial, 1 }, /* SET CONSOLE SERIAL */
|
|
{ "NOSERIAL", &ex_set_serial, 0 }, /* SET CONSOLE NOSERIAL */
|
|
{ "TELNET", &ex_set_serial, 2 }, /* SET CONSOLE TELNET */
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static t_stat ex_set_console (int32 flag, char *cptr)
|
|
{
|
|
char *tptr, gbuf [CBUFSIZE], cbuf [CBUFSIZE];
|
|
CTAB *cmdp;
|
|
t_stat status = SCPE_OK;
|
|
|
|
sim_quiet = ex_quiet; /* restore the global quiet setting */
|
|
|
|
if (cptr == NULL || *cptr == '\0') /* if no options follow */
|
|
return SCPE_2FARG; /* then report them as missing */
|
|
|
|
else while (*cptr != '\0') { /* otherwise loop through the argument list */
|
|
cptr = get_glyph_nc (cptr, gbuf, ','); /* get the next argument without altering case */
|
|
tptr = get_glyph (gbuf, cbuf, '='); /* and then just the option name in upper case */
|
|
|
|
cmdp = find_ctab (set_console_table, cbuf); /* get the associated command handler */
|
|
|
|
if (cmdp == NULL) /* if the target is not one of ours */
|
|
status = sim_set_console (flag, gbuf); /* then let the base handler process it */
|
|
else /* otherwise */
|
|
status = cmdp->action (cmdp->arg, tptr); /* call our handler */
|
|
|
|
if (status != SCPE_OK) /* if the command failed */
|
|
break; /* then bail out now */
|
|
}
|
|
|
|
return status; /* return the resulting status */
|
|
}
|
|
|
|
|
|
/* Execute the SET CONSOLE CONCURRENT/NOCONCURRENT commands.
|
|
|
|
This command processing routine adds new SET CONSOLE [NO]CONCURRENT commands
|
|
to enable or disable concurrent command mode. The routine processes commands
|
|
of the form:
|
|
|
|
SET CONSOLE CONCURRENT
|
|
SET CONSOLE NOCONCURRENT
|
|
|
|
The mode is enabled if the "flag" parameter is 1 and disabled if it is 0.
|
|
*/
|
|
|
|
static t_stat ex_set_concurrent (int32 flag, char *cptr)
|
|
{
|
|
if (flag == 1) /* if this is the CONCURRENT option */
|
|
concurrent_mode = TRUE; /* then enable concurrent mode */
|
|
else /* otherwise */
|
|
concurrent_mode = FALSE; /* disable concurrent mode */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Execute the SET CONSOLE SERIAL/NOSERIAL commands.
|
|
|
|
This command processing routine adds new SET CONSOLE [NO]SERIAL commands to
|
|
connect or disconnect the console to or from a serial port. The routine
|
|
processes commands of the form:
|
|
|
|
SET CONSOLE SERIAL=<port>[;<configuration>]
|
|
SET CONSOLE NOSERIAL
|
|
|
|
On entry, the "flag" parameter is set to 1 to connect or 0 to disconnect. If
|
|
connecting, the "cptr" parameter points to the serial port name and optional
|
|
configuration string. If present, the configuration string must be separated
|
|
from the port name with a semicolon and has this form:
|
|
|
|
<rate>-<charsize><parity><stopbits>
|
|
|
|
where:
|
|
|
|
rate = communication rate in bits per second
|
|
charsize = character size in bits (5-8, including optional parity)
|
|
parity = parity designator (N/E/O/M/S for no/even/odd/mark/space parity)
|
|
stopbits = number of stop bits (1, 1.5, or 2)
|
|
|
|
As an example:
|
|
|
|
SET CONSOLE SERIAL=com1;9600-8n1
|
|
|
|
The supported rates, sizes, and parity options are host-specific. If a
|
|
configuration string is not supplied, then host system defaults for the
|
|
specified port are used.
|
|
|
|
This routine is also called for the SET CONSOLE TELNET command with the
|
|
"flag" parameter set to 2. In this case, we check for a serial connection
|
|
and perform an automatic SET CONSOLE NOSERIAL first before calling the
|
|
standard command handler to attach the Telnet port.
|
|
*/
|
|
|
|
static t_stat ex_set_serial (int32 flag, char *cptr)
|
|
{
|
|
t_stat status;
|
|
|
|
if (flag == 2) { /* if this is a SET CONSOLE TELNET command */
|
|
if (serial_line (sim_con_tmxr.ldsc) != NULL) /* then if a serial connection exists */
|
|
ex_tmxr_detach_line (&sim_con_tmxr, NULL); /* then detach the serial port first */
|
|
|
|
status = sim_set_telnet (flag, cptr); /* call the base handler to set the Telnet connection */
|
|
}
|
|
|
|
else if (flag == 1) { /* otherwise if this is a SET CONSOLE SERIAL command */
|
|
sim_set_notelnet (flag, NULL); /* then detach any existing Telnet connection first */
|
|
|
|
if (serial_line (sim_con_tmxr.ldsc) != NULL) /* if already connected to a serial port */
|
|
status = SCPE_ALATT; /* then reject the command */
|
|
|
|
else { /* otherwise */
|
|
status = ex_tmxr_attach_line (&sim_con_tmxr, /* try to attach the serial port */
|
|
NULL, cptr);
|
|
|
|
if (status == SCPE_OK) { /* if the attach succeeded */
|
|
ex_tmxr_poll_conn (&sim_con_tmxr); /* then poll to complete the connection */
|
|
sim_con_tmxr.ldsc [0].rcve = 1; /* and enable reception */
|
|
}
|
|
}
|
|
}
|
|
|
|
else { /* otherwise this is a SET CONSOLE NOSERIAL command */
|
|
status = ex_tmxr_detach_line (&sim_con_tmxr, NULL); /* so detach the serial port */
|
|
sim_con_tmxr.ldsc [0].rcve = 0; /* and disable reception */
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
|
|
|
|
/* Execute the SHOW BREAK command.
|
|
|
|
This command processing routine enhances the existing SHOW BREAK command to
|
|
display string breakpoints. The routine processes commands of the form:
|
|
|
|
SHOW { <types> } BREAK { ALL | <address-list> }
|
|
|
|
Where:
|
|
|
|
<types> = a dash and one or more letters denoting breakpoint types
|
|
|
|
String breakpoints are displayed in this form:
|
|
|
|
<unit>: <types> <quoted-string> { DELAY <delay> } { ; <action> ... }
|
|
|
|
...and are displayed only if the <address-list> is omitted and the <types>
|
|
are either omitted or matches the type of the string breakpoint. In practice,
|
|
this means that string breakpoints could be displayed for these commands:
|
|
|
|
SHOW BREAK { ALL }
|
|
SHOW -T BREAK { ALL }
|
|
SHOW -T <other-types> BREAK { ALL }
|
|
|
|
Persistent string breakpoints are displayed only with the first form. All
|
|
three forms will display temporary string breakpoints. But a SHOW -N BREAK
|
|
command would not display any string breakpoints because none have an "N"
|
|
type.
|
|
|
|
The ALL keyword is redundant but is accepted for compatibility with the
|
|
standard SHOW BREAK command.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The matching string is stored in the breakpoint structure in decoded
|
|
form, i.e., control characters are stored explicitly rather than as a
|
|
character escape. So the string must be encoded for display.
|
|
*/
|
|
|
|
static t_stat ex_show_break (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
char gbuf [CBUFSIZE];
|
|
int32 types;
|
|
uint32 sw;
|
|
t_bool sep;
|
|
SBPTR bp = sb_list;
|
|
|
|
if (sim_switches == 0) /* if no type switches were specified */
|
|
types = BP_STRING; /* then match any string breakpoint */
|
|
else /* otherwise */
|
|
types = sim_switches; /* match only the specified type(s) */
|
|
|
|
get_glyph (cptr, gbuf, 0); /* parse the first option, if any */
|
|
|
|
if (*cptr == '\0' || strcmp (gbuf, "ALL") == 0) /* if no address list or ALL was specified */
|
|
while (bp != NULL) { /* then while string breakpoints exist */
|
|
if (bp->type & types) { /* then if breakpoint matches the type */
|
|
fprintf (stream, "%s:\t", /* then report the associated unit */
|
|
(bp->uptr ? sim_uname (bp->uptr) : "CONS"));
|
|
|
|
sep = FALSE; /* no separator is needed to start */
|
|
|
|
for (sw = 0; sw < 26; sw++) /* check the type letters */
|
|
if (bp->type >> sw & 1) { /* if this type is indicated */
|
|
if (sep) /* then if a separator is needed */
|
|
fprintf (stream, ", "); /* then output it first */
|
|
|
|
fputc (sw + 'A', stream); /* output the type letter */
|
|
sep = TRUE; /* and indicate that a separator will be needed */
|
|
}
|
|
|
|
if (bp->count > 0) /* if the count is defined */
|
|
fprintf (stream, " [%d]", bp->count); /* then output it */
|
|
|
|
fprintf (stream, "%s%s%s%.0d", /* output the breakpoint */
|
|
(sep || bp->count > 0 ? " " : ""),
|
|
encode (bp->match),
|
|
(bp->delay ? " delay " : ""),
|
|
bp->delay);
|
|
|
|
if (bp->action [0] != '\0') /* if actions are defined */
|
|
fprintf (stream, " ; %s", bp->action); /* then output them */
|
|
|
|
fprintf (stream, "\n"); /* terminate the line */
|
|
}
|
|
|
|
bp = bp->next; /* move on to the next breakpoint */
|
|
}
|
|
|
|
return show_break (stream, dptr, uptr, flag, cptr); /* let the base handler show any numeric breakpoints */
|
|
}
|
|
|
|
|
|
/* Execute the SHOW REPLY command.
|
|
|
|
This command processing routine adds a new SHOW REPLY command to display
|
|
pending replies. The routine processes commands of the form:
|
|
|
|
SHOW REPLY
|
|
|
|
Replies are displayed in this form:
|
|
|
|
<unit>: <quoted-string> { DELAY <delay> }
|
|
|
|
If a delay is present, then the value displayed is the remaining delay before
|
|
the first character of the string is output. If a delay was originally
|
|
specified but is not displayed, then the reply is already underway.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The output string is stored in the reply structure in decoded form, i.e.,
|
|
control characters are stored explicitly rather than as a character
|
|
escape. So the string must be encoded for display.
|
|
*/
|
|
|
|
static t_stat ex_show_reply (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
int32 delay;
|
|
RPPTR rp = rp_list;
|
|
|
|
if (*cptr != '\0') /* if something follows */
|
|
return SCPE_2MARG; /* then report extraneous characters */
|
|
|
|
else if (rp == NULL) /* otherwise if no replies are pending */
|
|
fprintf (stream, "No replies pending\n"); /* then report it as such */
|
|
|
|
else { /* otherwise report the replies */
|
|
delay = rp->trigger - sim_gtime (); /* get the relative delay time */
|
|
|
|
if (delay < 0) /* if the reply has already started */
|
|
delay = 0; /* then suppress reporting the delay */
|
|
|
|
fprintf (stream, "%s:\t%s%s%.0d\n", /* display the reply */
|
|
(rp->uptr ? sim_uname (rp->uptr) : "CONS"),
|
|
encode (rp->reply),
|
|
(delay ? " delay " : ""), delay);
|
|
}
|
|
|
|
return SCPE_OK; /* report the success of the command */
|
|
}
|
|
|
|
|
|
/* Execute the SHOW DELAYS command.
|
|
|
|
This command processing routine adds a new SHOW DELAYS command to display the
|
|
global delay settings for string breakpoints and replies. The routine
|
|
processes commands of the form:
|
|
|
|
SHOW DELAYS
|
|
|
|
The delay values are reported in units of event ticks.
|
|
*/
|
|
|
|
static t_stat ex_show_delays (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
if (*cptr != '\0') /* if something follows */
|
|
return SCPE_2MARG; /* then report extraneous characters */
|
|
|
|
else { /* otherwise */
|
|
fprintf (stream, "Break delay = %d\n", break_delay); /* report the break */
|
|
fprintf (stream, "Reply delay = %d\n", reply_delay); /* and reply delays */
|
|
|
|
return SCPE_OK; /* return success */
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute the SHOW CONSOLE command.
|
|
|
|
This command processing routine enhances the existing SHOW CONSOLE command to
|
|
add configuration displays for concurrent command execution and serial port
|
|
support. The routine processes commands of the form:
|
|
|
|
SHOW CONSOLE CONCURRENT
|
|
SHOW CONSOLE SERIAL
|
|
|
|
It also intercepts the SHOW CONSOLE TELNET command to convert it from a
|
|
two-state report (i.e., connected to Telnet or console window) to a
|
|
three-state report (Telnet, serial, or console window).
|
|
|
|
Because the SHOW CONSOLE command accepts a comma-separated set of options, we
|
|
must parse each option and decide whether it is a standard option or an
|
|
extension option.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We parse each option without case conversion and then separate the option
|
|
name from its value, if present, with conversion to upper case. This is
|
|
necessary because the option name must be uppercase to match the command
|
|
table, but the option value might be case-sensitive, such as a serial
|
|
port name.
|
|
|
|
2. For the SHOW CONSOLE command with no specified options (i.e., SHOW ALL),
|
|
we cannot simply call the standard "sim_show_console" routine to display
|
|
the base set of values because it calls "sim_show_telnet", which displays
|
|
"Connected to console window" if no Telnet connection exists. This is
|
|
incorrect if a serial connection exists. Instead, we call that routine
|
|
with a command line consisting of all options except the TELNET option.
|
|
In that way, we get the base display and can then add our own line for
|
|
the Telnet/serial/window connection.
|
|
|
|
If the base set of options is changed, we must also change the "show_set"
|
|
value below to match.
|
|
|
|
3. For SHOW CONSOLE (i.e., SHOW ALL), we loop through the extension table to
|
|
call the individual SHOW executors. However, we don't want to call
|
|
"ex_show_serial" twice, or we'll get the connection report twice, so we
|
|
use the otherwise-unused "arg" values as a flag to skip the call, which
|
|
we do after processing the table.
|
|
*/
|
|
|
|
#define SH_SER -2
|
|
#define SH_TEL -1
|
|
#define SH_NONE 0
|
|
|
|
static SHTAB show_console_table [] = { /* the SHOW CONSOLE extension table */
|
|
{ "CONCURRENT", &ex_show_concurrent, 0 }, /* SHOW CONSOLE CONCURRENT */
|
|
{ "SERIAL", &ex_show_serial, SH_SER }, /* SHOW CONSOLE SERIAL */
|
|
{ "TELNET", &ex_show_serial, SH_TEL }, /* SHOW CONSOLE TELNET */
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static char show_set [] = "WRU,BRK,DEL,PCHAR,LOG,DEBUG"; /* the standard set of options */
|
|
|
|
static t_stat ex_show_console (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
char *tptr, gbuf [CBUFSIZE], cbuf [CBUFSIZE];
|
|
SHTAB *cmdp;
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (*cptr == '\0') { /* if no options follow */
|
|
sim_show_console (stream, NULL, NULL, flag, show_set); /* then show the base console options first */
|
|
|
|
for (cmdp = show_console_table; cmdp->name != NULL; cmdp++) /* loop through the extension options */
|
|
if (cmdp->arg >= 0) /* and if it's not to be omitted */
|
|
cmdp->action (stream, NULL, NULL, cmdp->arg, ""); /* then show the option */
|
|
|
|
ex_show_serial (stream, NULL, NULL, SH_NONE, ""); /* add the console connection status */
|
|
}
|
|
|
|
else do { /* otherwise loop through the option list */
|
|
cptr = get_glyph_nc (cptr, gbuf, ','); /* get the next option and value without altering case */
|
|
tptr = get_glyph (gbuf, cbuf, '='); /* and then just the option name in upper case */
|
|
|
|
cmdp = find_shtab (show_console_table, cbuf); /* get the associated command handler */
|
|
|
|
if (cmdp == NULL) /* if the target is not one of ours */
|
|
status = sim_show_console (stream, NULL, NULL, flag, gbuf); /* then let the base handler process it */
|
|
else /* otherwise */
|
|
status = cmdp->action (stream, NULL, NULL, cmdp->arg, tptr); /* call our handler */
|
|
|
|
if (status != SCPE_OK) /* if the command failed */
|
|
break; /* then bail out now */
|
|
}
|
|
while (*cptr != '\0'); /* continue until all options are processed */
|
|
|
|
return status; /* return the resulting status */
|
|
}
|
|
|
|
|
|
/* Execute the SHOW CONSOLE CONCURRENT command.
|
|
|
|
This command processing routine adds a new SHOW CONSOLE CONCURRENT command
|
|
to display the concurrent command mode. The routine processes commands
|
|
of the form:
|
|
|
|
SHOW CONSOLE CONCURRENT
|
|
|
|
The global mode value is reported.
|
|
*/
|
|
|
|
static t_stat ex_show_concurrent (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
if (*cptr != '\0') /* if something follows */
|
|
return SCPE_2MARG; /* then report extraneous characters */
|
|
|
|
else { /* otherwise */
|
|
if (concurrent_mode) /* if concurrent mode is enabled */
|
|
fprintf (stream, "Concurrent mode enabled\n"); /* then report it as such */
|
|
else /* otherwise */
|
|
fprintf (stream, "Concurrent mode disabled\n"); /* report that it is disabled */
|
|
|
|
return SCPE_OK; /* return command success */
|
|
}
|
|
}
|
|
|
|
|
|
/* Execute the SHOW CONSOLE SERIAL/TELNET command.
|
|
|
|
This command processing routine adds a new SHOW CONSOLE SERIAL command and
|
|
enhances the existing SHOW CONSOLE TELNET command to display the connection
|
|
status of the system console. The routine processes commands of the form:
|
|
|
|
SHOW CONSOLE SERIAL
|
|
SHOW CONSOLE TELNET
|
|
|
|
It is also called as part of the SHOW CONSOLE processing. The three calls
|
|
are differentiated by the value of the "flag" parameter, as follows:
|
|
|
|
Flag Meaning
|
|
------- -----------
|
|
SH_SER SHOW SERIAL
|
|
SH_TEL SHOW TELNET
|
|
SH_NONE SHOW
|
|
|
|
For the SHOW SERIAL and SHOW TELNET commands, if the console is connected to
|
|
the specified type of port, the port status (connection and transmit/receive
|
|
counts) is printed. Otherwise, the command simply reports the connection
|
|
type. So, for example, a SHOW SERIAL command reports "Connected to Telnet
|
|
port" or "Connected to console window" if the console is not using a serial
|
|
port.
|
|
|
|
The plain SHOW command reports the port status of the connection in use.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We must duplicate the code from the "sim_show_telnet" routine in
|
|
"sim_console.c" here because that routine reports a console window
|
|
connection if Telnet is not used, and there is no way to suppress that
|
|
report when a serial port is used.
|
|
*/
|
|
|
|
static t_stat ex_show_serial (FILE *stream, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
|
|
{
|
|
if (*cptr != '\0') /* if something follows */
|
|
return SCPE_2MARG; /* then report extraneous characters */
|
|
|
|
else { /* otherwise */
|
|
if (serial_line (sim_con_tmxr.ldsc) != NULL) /* if a serial connection exists */
|
|
if (flag == SH_TEL) /* but we've asked for Telnet status explicitly */
|
|
fputs ("Connected to serial port\n", stream); /* then report the other connection type */
|
|
|
|
else { /* otherwise */
|
|
fprintf (stream, "Connected to "); /* report */
|
|
tmxr_fconns (stream, sim_con_tmxr.ldsc, -1); /* the connection port and time */
|
|
tmxr_fstats (stream, sim_con_tmxr.ldsc, -1); /* and the transmit and receive counts */
|
|
}
|
|
|
|
else if (sim_con_tmxr.master != 0) /* otherwise if a Telnet connection exists */
|
|
if (flag == SH_SER) /* but we've asked for serial status explicitly */
|
|
fputs ("Connected to Telnet port\n", stream); /* then report the other connection type */
|
|
|
|
else if (sim_con_tmxr.ldsc [0].conn == 0) /* otherwise if the client hasn't connected yet */
|
|
fprintf (stream, "Listening on port %d\n", /* then report that we're still listening */
|
|
sim_con_tmxr.port);
|
|
|
|
else { /* otherwise report the socket connection */
|
|
fprintf (stream, "Listening on port %d, connected to socket %d\n",
|
|
sim_con_tmxr.port, sim_con_tmxr.ldsc [0].conn);
|
|
|
|
tmxr_fconns (stream, sim_con_tmxr.ldsc, -1); /* report the connection address and time */
|
|
tmxr_fstats (stream, sim_con_tmxr.ldsc, -1); /* and the transmit and receive counts */
|
|
}
|
|
|
|
else /* otherwise no port connection exists */
|
|
fprintf (stream, "Connected to console window\n"); /* so report the default association */
|
|
|
|
return SCPE_OK; /* return command success */
|
|
}
|
|
}
|
|
|
|
|
|
/* Hooked command extension replacement routines */
|
|
|
|
|
|
/* Substitute arguments into a command line.
|
|
|
|
This hook routine extends the standard "sub_args" routine to perform token
|
|
substitution as well as parameter substitution. The standard behavior
|
|
substitutes parameter placeholders of the form "%n", where "n" is a digit
|
|
from 0-9, with the strings pointed to by the corresponding elements in the
|
|
supplied "args" array.
|
|
|
|
In addition to the parameter substitution, built-in and environment tokens
|
|
surrounded by percent signs are replaced. These substitutions provide the
|
|
value of environment variables, as well as the current date and time in
|
|
various formats that may be used with the REPLY facility to set the target OS
|
|
time on bootup. They also provide some internal simulator values, such as
|
|
the path to the binary and the current code version, that may be used in
|
|
command files.
|
|
|
|
The routine interprets these character sequences as follows:
|
|
|
|
%0 - %9 = parameter substitution from the supplied "args" array
|
|
|
|
%token% = substitute a value corresponding to the predefined token
|
|
|
|
%envvar% = substitute the value of an environment variable
|
|
|
|
%% = substitute a literal percent sign
|
|
|
|
Tokens consist of characters without intervening blanks. For example,
|
|
"%TOKEN%" is a token, but "100% PURE" is not (however, this should be
|
|
represented as "100%% PURE" to designate the literal percent sign
|
|
explicitly). If a token or environment variable is not defined, it will be
|
|
replaced with a null value.
|
|
|
|
On entry, "iptr" points at the buffer containing the command line to scan for
|
|
substitutions, "optr" points at a temporary output buffer of the same size
|
|
as the input buffer, "bufsize" is the size of the buffers, and "args" points
|
|
at an array of parameter substitution values. On return, the "iptr" buffer
|
|
contains the substituted line. If substitution overflows the output buffer,
|
|
the result will be truncated at "bufsize" characters with no error
|
|
indication.
|
|
|
|
This routine is also called for commands entered at the simulation console in
|
|
addition to those appearing in command files. The numeric arguments in the
|
|
former case represent the parameters provided at SIMH invocation.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. A literal percent sign is designated by a pair of percent signs (i.e.,
|
|
"%%"). This change from the earlier practice of using a backslash as the
|
|
escape character is for compatibility with version 4.x.
|
|
|
|
2. The C "str..." functions offer no performance advantage over inline code
|
|
and are used for clarity only.
|
|
|
|
3. If supplied, the "args" array must contain 10 elements. If "args" is
|
|
NULL, then parameter substitution will not occur.
|
|
*/
|
|
|
|
static void ex_substitute_args (char *iptr, char *optr, int32 bufsize, char *args [])
|
|
{
|
|
char *start, *end, *in, *out, *env;
|
|
int32 i;
|
|
|
|
start = strchr (iptr, '%'); /* see if any % characters are present */
|
|
|
|
if (start == NULL) /* if not (the usual case) */
|
|
return; /* then we are done */
|
|
|
|
in = iptr; /* at least one % is present */
|
|
out = optr; /* so set up the input and output buffer pointers */
|
|
|
|
bufsize = bufsize - 1; /* ensure there is space for the trailing NUL */
|
|
|
|
do { /* copy string segments to a temporary buffer */
|
|
if (start - in != 0) /* if the leading substring is not empty */
|
|
copy_string (&out, &bufsize, in, start - in); /* then copy up to the next % */
|
|
|
|
if (isdigit (start [1])) { /* if the next character is a digit */
|
|
i = (int32) (start [1] - '0'); /* then convert to an index */
|
|
|
|
if (i < 10 && args != NULL && args [i] != NULL) /* if the corresponding argument is present */
|
|
copy_string (&out, &bufsize, args [i], 0); /* then copy the value */
|
|
|
|
in = start + 2; /* skip over the argument substitution */
|
|
}
|
|
|
|
else { /* otherwise it might be a keyword substitution */
|
|
end = strpbrk (start + 1, "% "); /* search for an embedded blank before the next % */
|
|
|
|
if (end == NULL) { /* if there is no ending % */
|
|
in = start; /* then copy the remainder of the line */
|
|
break; /* to the output buffer */
|
|
}
|
|
|
|
else if (bufsize > 0) /* otherwise we'll need at least one more character */
|
|
if (*end != '%') { /* if there is an intervening blank */
|
|
*out++ = *start; /* then the initial % does not start a token */
|
|
*out = '\0'; /* so copy the character */
|
|
|
|
in = start + 1; /* advance over it */
|
|
bufsize = bufsize - 1; /* and count it */
|
|
}
|
|
|
|
else if (end == start + 1) { /* otherwise if the % signs are adjacent */
|
|
*out++ = '%'; /* then output a literal % */
|
|
*out = '\0'; /* and terminate the string */
|
|
|
|
in = start + 2; /* advance over it */
|
|
bufsize = bufsize - 1; /* and count it */
|
|
}
|
|
|
|
else { /* otherwise a substitution token may be present */
|
|
in = end + 1; /* so skip over it */
|
|
|
|
*end = '\0'; /* temporarily terminate the string at the second % */
|
|
|
|
env = getenv (start + 1); /* search the environment for a match */
|
|
|
|
*end = '%'; /* restore the % before testing */
|
|
|
|
if (env) /* if the keyword is an environment variable */
|
|
copy_string (&out, &bufsize, env, 0); /* then copy the value */
|
|
else /* otherwise */
|
|
replace_token (&out, &bufsize, start); /* replace the token if it's defined */
|
|
}
|
|
}
|
|
|
|
start = strchr (in, '%'); /* search for the next initial % */
|
|
} /* and continue to copy and substitute */
|
|
while (start != NULL && bufsize > 0); /* until there are no more tokens */
|
|
|
|
if (bufsize > 0) /* if any space remains */
|
|
copy_string (&out, &bufsize, in, 0); /* then copy any remaining input to the output buffer */
|
|
|
|
strcpy (iptr, optr); /* replace the original buffer */
|
|
return; /* and return */
|
|
}
|
|
|
|
|
|
/* Get a specified radix from the command-line switches or keyword.
|
|
|
|
This hook routine extends the standard "sim_get_radix" routine to permit a
|
|
request for binary interpretation of numeric data. The standard behavior
|
|
permits commands such as EXAMINE to override the default radix of the data
|
|
item by specifying one of the following switches on the command line:
|
|
|
|
Switch Interpretation
|
|
------ -------------------
|
|
-O An octal value
|
|
-D A decimal value
|
|
-H A hexadecimal value
|
|
|
|
...and to set the default radix for a device with these commands:
|
|
|
|
Command Interpretation
|
|
----------------- -------------------
|
|
SET <dev> OCTAL An octal value
|
|
SET <dev> DECIMAL A decimal value
|
|
SET <dev> HEX A hexadecimal value
|
|
|
|
This routine extends the interpretation to add -B and SET <dev> BINARY for
|
|
binary value interpretations.
|
|
|
|
On entry, if the "cptr" parameter is non-null, then it points at the keyword
|
|
for a SET <dev> command. If the keyword is "BINARY", the returned radix is
|
|
2. Otherwise, it is 0 to indicate that the keyword was not understood by
|
|
this routine.
|
|
|
|
Otherwise, the "cptr" parameter is NULL, and the "switches" parameter is
|
|
checked for the presence of the O, D, H, or B flags. If one is present, the
|
|
corresponding radix is returned. Otherwise, the "default_radix" parameter
|
|
value is returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The order of switch interpretation matches that of the standard SCP
|
|
routine, so that the same precedence is used when multiple conflicting
|
|
switches are present.
|
|
|
|
2. This must be implemented as an extension, as several simulators already
|
|
use -B for their own purposes (e.g., for the PDP11, it indicates a byte
|
|
access).
|
|
*/
|
|
|
|
static int32 ex_get_radix (const char *cptr, int32 switches, int32 default_radix)
|
|
{
|
|
if (cptr != NULL)
|
|
if (strncmp (cptr, "BINARY", strlen (cptr)) == 0) /* if the keyword is "BINARY" */
|
|
return 2; /* then return radix 2 */
|
|
else /* otherwise */
|
|
return 0; /* indicate that the keyword is not recognized */
|
|
|
|
else if (switches & SWMASK ('O')) /* otherwise if the -O switch was specified */
|
|
return 8; /* then interpret data as octal */
|
|
|
|
else if (switches & SWMASK ('D')) /* otherwise if the -D switch was specified */
|
|
return 10; /* then interpret data as decimal */
|
|
|
|
else if (switches & SWMASK ('H')) /* otherwise if the -H switch was specified */
|
|
return 16; /* then interpret data as hex */
|
|
|
|
else if (sim_switches & SWMASK ('B')) /* otherwise if the -B switch was specified */
|
|
return 2; /* then interpret data as binary */
|
|
|
|
else /* otherwise */
|
|
return default_radix; /* use the default radix for the data item */
|
|
}
|
|
|
|
|
|
/* Local SCP command extension routines */
|
|
|
|
|
|
/* Execute commands in a text file.
|
|
|
|
This routine is called to execute the SCP commands present in a text file.
|
|
It is called by the CALL and DO command executors, where it processes
|
|
commands of the form:
|
|
|
|
DO { <switches> } <filename> { <param 1> { <param 2> { ... <param 9> } } }
|
|
|
|
The <filename> must be present. Up to nine optional parameters may be
|
|
specified, which may be referenced within the command file with the
|
|
substitutions "%1" through "%9", respectively. If a parameter is omitted,
|
|
the corresponding substitution will be the empty string. Parameters are
|
|
normally delimited by spaces. However, spaces may be embedded in a parameter
|
|
if it is quoted, either with single (') or double (") quote marks. The
|
|
starting and ending quotes must match, or an "Invalid argument" error is
|
|
indicated.
|
|
|
|
Three optional switches are recognized:
|
|
|
|
-E = continue execution in the presence of errors
|
|
-V = echo each command line as it is executed
|
|
-A = echo all simulation stop and ATTACH/DETACH messages
|
|
|
|
The -E switch causes command file execution to continue regardless of any
|
|
error conditions. If -E is present, only EXIT or an ASSERT failure will stop
|
|
execution. Without -E, execution stops if a command returns an SCP error.
|
|
|
|
Specifying the -V switch will print each command line before it is executed.
|
|
The filename containing the command is printed as a prompt, e.g.:
|
|
|
|
cmdfile> echo Hello!
|
|
|
|
If -V is absent, then the command line is printed only if the command fails.
|
|
Simulation stops, including the expiration of a STEP command, are not
|
|
considered errors and so do not cause the associated commands to be printed.
|
|
|
|
The -A switch causes all simulation stops and messages from the ATTACH and
|
|
DETACH commands to be reported. In its absence, the "Breakpoint" and "Step
|
|
expired" messages that would normally be printed in response to the
|
|
associated stop condition are suppressed, as are the informational messages
|
|
from ATTACH and DETACH, such as "Creating new file." This provides a cleaner
|
|
console display log when automated prompts and responses are used. As an
|
|
example, if "cmdfile" contains:
|
|
|
|
GO UNTIL "Memory size? " ; ATTACH -N MS0 new.tape ; REPLY "1024\r" ; GO
|
|
|
|
...then "DO cmdfile" would display:
|
|
|
|
Memory size? 1024
|
|
|
|
...whereas "DO -A cmdfile" would display:
|
|
|
|
Memory size?
|
|
Breakpoint, P: 37305 (CLF 10)
|
|
MS: creating new file
|
|
1024
|
|
|
|
All three switches propagate from top-level command files to nested files.
|
|
For example, invoking a top-level command file with "DO -V" will verbosely
|
|
list not only that file's commands but also the commands within any DO files
|
|
invoked therein.
|
|
|
|
On entry, the "cptr" parameter points at the invocation string after the DO
|
|
or CALL keyword (i.e., points at the optional switches or command filename).
|
|
The "file" parameter is NULL if the routine is to open the filename present
|
|
at the start of the "cptr" string or is a file stream pointer to an open
|
|
file. The "flag" parameter indicates the source of the call and nesting
|
|
level and contains one of these values:
|
|
|
|
< 0 = global or local initialization file (no error if not found)
|
|
0 = startup command line file
|
|
1 = "DO" command
|
|
> 1 = nested DO or CALL command
|
|
|
|
For a nested command call, the parameter contains the nesting level in bits
|
|
0-3 and the value of the invoking switches in bits 4-29.
|
|
|
|
The routine begins by separating the nesting level from the propagated
|
|
switches. If the nesting level is too deep, the routine returns with an
|
|
error.
|
|
|
|
It then obtains any switches specified on the command line and merges them
|
|
into the propagated switches. Then it separates the command line parameters,
|
|
removing any leading or trailing spaces (unless the parameter is quoted). If
|
|
the "file" parameter is NULL, it then attempts to open the specified command
|
|
file. If the open fails, and this is not an initialization file request,
|
|
then the attempt is repeated after appending the ".sim" extension to the
|
|
specified name. If this second attempt also fails, the command fails with a
|
|
"File open error" message.
|
|
|
|
The routine then enters the command loop. The next command line is obtained,
|
|
either from a pending breakpoint action or from the command file, and
|
|
argument substitutions are made. If the end of the file was reached, the
|
|
loop exits with success status. Otherwise, if the resulting line is empty
|
|
(e.g., is a blank or comment line), the loop continues with the next command
|
|
line.
|
|
|
|
If the line contains a command, it is echoed if the -V switch was present.
|
|
If it's a label line, it is ignored, and the loop continues. Otherwise, the
|
|
global switches are cleared, and the "sim_quiet" value is set if neither the
|
|
-A (audible) nor -V (verbose) switch was specified. This suppresses certain
|
|
noise messages unless they were specifically requested by the invocation.
|
|
Then the command keyword is parsed, and the action routine is obtained.
|
|
|
|
If it is a DO command, the DO executor is called with the nesting level
|
|
increased by one. On reentry here, the nesting level is checked, and an
|
|
error occurs if the limit is exceeded.
|
|
|
|
If the command is RUN (or any of the execution commands), and neither the
|
|
-A nor -V switches was specified, the SIM_SW_HIDE switch will be passed to
|
|
the RUN executor to suppress BREAK and STEP messages. Then the associated
|
|
command handler is invoked, and its status result is obtained.
|
|
|
|
If the command is one of those restricted to use within a command file (CALL,
|
|
RETURN, GOTO, or ABORT), the appropriate action is taken. Otherwise, if the
|
|
command succeeds, the command loop continues. If the command fails, actions
|
|
then depend on the specific error code and switch settings. Three
|
|
determinations are made: whether command file execution continues, whether
|
|
the command line is printed, and whether the error message text is printed.
|
|
|
|
Execution stays in the command loop if the error is not due to an EXIT or
|
|
ABORT command or an ASSERT failure -- these always terminate DO execution --
|
|
and the code is for a simulation stop (all VM-defined status codes and the
|
|
STEP expired code) or the -E switch was specified. That is, the loop
|
|
terminates for an EXIT, ABORT, ASSERT that fails, or an SCP error and the -E
|
|
switch was not given.
|
|
|
|
The failing command line is printed if an SCP error occurred, the -V switch
|
|
was not given, and the command was not DO unless it failed because the file
|
|
could not be opened. This excludes simulator stops, which are not considered
|
|
errors in a command file. it also avoids printing the line a second time if
|
|
it was previously printed by the -V switch. Finally, because the status of a
|
|
failing command is passed back as the status of the enclosing DO or CALL
|
|
command to ensure that execution stops for a failure in a nested invocation,
|
|
the command line is not printed unless the DO command itself failed. This
|
|
determination is made independently of the determination to stay, because we
|
|
want command lines causing errors to be printed, regardless of whether or not
|
|
we will ignore the error.
|
|
|
|
The error text is printed if it is an SCP error and either the command file
|
|
is nested or the error will be ignored. Simulation stops have already been
|
|
printed by the "fprint_stopped" call in the RUN handler, and if the DO
|
|
command was initiated from the command line and not by a nested DO or CALL
|
|
invocation, and the execution loop will be exited, then the main command loop
|
|
will print the error text when this routine returns.
|
|
|
|
Finally, we call the VM's post-command handler if it is defined and check for
|
|
a CTRL+C stop. If it is detected, a message is printed, any pending
|
|
breakpoint actions are cleared, and the status return is set to propagate the
|
|
abort back through all nesting levels.
|
|
|
|
Once the command loop exits, either for an error, or because the command file
|
|
is exhausted, the file is closed if we had opened it, and the last command
|
|
status is returned as the status of the invoking DO or CALL. If we will be
|
|
exiting the simulator, the end-of-session detach for all units is performed
|
|
before returning, so that the same quietness as was used for the attaches is
|
|
used.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The nesting limit is arbitrary and is intended mainly to prevent a stack
|
|
overflow abort if a command file executes a recursive DO in an infinite
|
|
loop. Note that CALL invocations count toward the nesting limit. Note
|
|
also that four bits of the "flag" parameter are allocated to the nesting
|
|
level, so the limit must be less than 15.
|
|
|
|
2. The command line switches are not extracted here when processing the
|
|
initialization or command-line files. This is because the switches were
|
|
already processed and stripped in the "main" routine before we were
|
|
called.
|
|
|
|
3. The standard ATTACH and DETACH commands are too noisy for command-file
|
|
execution. Specifically, ATTACH reports "unit is read only" for files
|
|
without write permission, "creating new file" for newly created
|
|
files, and "buffering file in memory" when the unit has the UNIT_BUFABLE
|
|
flag. DETACH reports "writing buffer to file" for a unit with
|
|
UNIT_BUFABLE. All of the messages report conditions over which the user
|
|
of an executing command file has no control.
|
|
|
|
4. The "do_arg" array is an array of string pointers, rather than an array
|
|
of strings, that point into the command line buffer that is passed to
|
|
this routine. That buffer must not be constant, as we separate the
|
|
parameters by writing a NUL after each parsed parameter.
|
|
|
|
5. The command file is opened as a binary, rather than text, file on Windows
|
|
systems. This is because "fgetpos" and "fsetpos" (used in the CALL
|
|
executor) fail to work properly on text files. Opening as binary means
|
|
that lines obtained with "fgets" have trailing CRLFs instead of LFs, but
|
|
the "read_line" routine strips both line termination characters, so the
|
|
result is as expected.
|
|
|
|
6. The HP simulators look for SIM_SW_HIDE in "sim_switches" and return
|
|
SCPE_OK status in lieu of the SCPE_STEP or STOP_BRKPNT codes. In turn,
|
|
this suppresses the "Step expired" and "Breakpoint" simulation stop
|
|
messages that otherwise would be interspersed with prompts and responses
|
|
supplied by the GO UNTIL and REPLY commands.
|
|
|
|
7. Execution commands are identified by the "help_base" field of their
|
|
corresponding CTAB entries being non-null. This is a hack; see the
|
|
comments for the "ex_cmds" structure declaration for details.
|
|
|
|
8. A failing command within a command file returns an error status that
|
|
causes the bad command to be echoed to identify the cause of the error.
|
|
That status is also returned as the command file execution status, which
|
|
would normally cause the invoking DO or CALL command to be echoed as
|
|
well. This is undesirable, as the command file name has already been
|
|
printed as part of the echo, so this is suppressed.
|
|
|
|
An exception is made for the SCPE_OPENERR status returned when the DO
|
|
command file cannot be opened; otherwise, there would be no indication
|
|
what caused the error message. However, when this status is passed back
|
|
to nested invocations, the exception would cause each invoking DO command
|
|
to be listed, which would be wrong (the outer DOs did not fail with file
|
|
open errors).
|
|
|
|
To fix this, we tag the innermost nested return only with SCPE_DOFAILED,
|
|
and use this to implement the exception. This suppresses the propagating
|
|
errors, so that only the DO command that actually encountered the file
|
|
open error is echoed.
|
|
|
|
9. The CALL command currently does not take switches (they aren't parsed
|
|
from the command line string), so the called context uses the same switch
|
|
environment as the caller.
|
|
*/
|
|
|
|
#define ARG_COUNT 10 /* number of DO command arguments */
|
|
#define NEST_LIMIT 10 /* DO command nesting limit (must be <= 15) */
|
|
|
|
#define LEVEL_SHIFT 4 /* bits allocated to the level value */
|
|
#define LEVEL_MASK ((1u << LEVEL_SHIFT) - 1) /* mask for the level value */
|
|
|
|
static t_stat execute_file (FILE *file, int32 flag, char *cptr)
|
|
{
|
|
const t_bool interactive = (flag > 0); /* TRUE if DO was issued interactively */
|
|
static t_bool must_detach = TRUE; /* TRUE until "detach_all" has been called during exit */
|
|
FILE *do_file;
|
|
CTAB *cmdp;
|
|
char term, *kptr, *do_arg [ARG_COUNT], cbuf [CBUFSIZE], kbuf [CBUFSIZE];
|
|
int32 level, switches, audible, errignore, verbose, count;
|
|
t_bool is_do;
|
|
t_bool staying = TRUE;
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (interactive) { /* if we were originally called from the SCP prompt */
|
|
level = flag & LEVEL_MASK; /* then isolate the nesting level */
|
|
switches = flag >> LEVEL_SHIFT; /* and propagated switches from the parameter */
|
|
|
|
if (level >= NEST_LIMIT) /* if the call nesting level is too deep */
|
|
return SCPE_NEST; /* then report the error */
|
|
|
|
cptr = get_sim_sw (cptr); /* get any command line switches */
|
|
|
|
if (cptr == NULL) /* if an invalid switch was present */
|
|
return SCPE_INVSW; /* then report it */
|
|
}
|
|
|
|
else { /* otherwise this is an initialization file call */
|
|
level = 1; /* so start out at level 1 */
|
|
switches = 0; /* with no propagated switches */
|
|
}
|
|
|
|
switches |= sim_switches; /* merge specified and propagated switches */
|
|
|
|
audible = switches & SWMASK ('A'); /* set the audible flag if -A is present */
|
|
errignore = switches & SWMASK ('E'); /* and the error ignore flag if -E is present */
|
|
verbose = switches & SWMASK ('V'); /* and the verbose flag if -V is present */
|
|
|
|
for (count = 0; count < ARG_COUNT; count++) { /* parse the arguments */
|
|
if (cptr == NULL || *cptr == '\0') /* if the argument list is exhausted */
|
|
do_arg [count] = NULL; /* then invalidate the remaining pointers */
|
|
|
|
else { /* otherwise */
|
|
if (*cptr == '\'' || *cptr == '"') /* if a quoted string is present */
|
|
term = *cptr++; /* then save the terminator */
|
|
else /* otherwise */
|
|
term = ' '; /* use a space as the terminator */
|
|
|
|
do_arg [count] = cptr; /* point at the start of the parameter */
|
|
|
|
cptr = strchr (cptr, term); /* find the terminator */
|
|
|
|
if (cptr != NULL) { /* if the parameter was terminated */
|
|
*cptr++ = '\0'; /* then separate the parameters */
|
|
|
|
while (isspace (*cptr)) /* discard any trailing spaces */
|
|
cptr++; /* that follow the parameter */
|
|
}
|
|
|
|
else if (term != ' ') /* otherwise if a quoted string is not terminated */
|
|
return SCPE_ARG; /* then report a bad argument */
|
|
}
|
|
}
|
|
|
|
if (do_arg [0] == NULL) /* if the command filename is missing */
|
|
return SCPE_2FARG; /* then report it */
|
|
|
|
else if (file != NULL) /* otherwise if a stream was specified */
|
|
do_file = file; /* then use it */
|
|
|
|
else { /* otherwise */
|
|
do_file = fopen (do_arg [0], "rb"); /* open the specified command file */
|
|
|
|
if (do_file == NULL) { /* if the file failed to open as is */
|
|
if (flag < 0) /* then if this is an initialization file */
|
|
return SCPE_OPENERR; /* then do not try an alternate filename */
|
|
|
|
strcat (strcpy (cbuf, do_arg [0]), ".sim"); /* append a ".sim" extension to the filename */
|
|
do_file = fopen (cbuf, "rb"); /* and try again */
|
|
|
|
if (do_file == NULL) { /* if the open failed a second time */
|
|
if (flag == 0) /* then if we're executing the startup file */
|
|
fprintf (stderr, "Can't open file %s\n", /* then report the failure */
|
|
do_arg [0]);
|
|
|
|
if (level > 1) /* if this is a nested DO call */
|
|
return SCPE_OPENERR | SCPE_DOFAILED; /* then return failure with the internal flag */
|
|
|
|
else /* otherwise this is a top-level call */
|
|
return SCPE_OPENERR; /* so simply return failure */
|
|
}
|
|
}
|
|
}
|
|
|
|
stop_requested = FALSE; /* clear any pending WRU stop */
|
|
|
|
do {
|
|
cptr = sim_brk_getact (cbuf, CBUFSIZE); /* get any pending breakpoint actions */
|
|
|
|
if (cptr == NULL) /* if there are no pending actions */
|
|
cptr = read_line (cbuf, CBUFSIZE, do_file); /* then get a line from the command file */
|
|
|
|
ex_substitute_args (cbuf, kbuf, CBUFSIZE, do_arg); /* substitute arguments in the command */
|
|
|
|
if (cptr == NULL) { /* if the end of the command file was reached */
|
|
status = SCPE_OK; /* then set normal status */
|
|
break; /* and exit the command loop */
|
|
}
|
|
|
|
else if (*cptr == '\0') /* otherwise if the line is blank */
|
|
continue; /* then ignore it */
|
|
|
|
if (verbose) { /* if command echo is specified */
|
|
printf ("%s> %s\n", do_arg [0], cptr); /* then output the command filename and line */
|
|
|
|
if (sim_log) /* if the console is logging */
|
|
fprintf (sim_log, "%s> %s\n", /* then write it to the log file as well */
|
|
do_arg [0], cptr);
|
|
}
|
|
|
|
if (*cptr == ':') /* if this is a label line */
|
|
continue; /* then ignore it */
|
|
|
|
sim_switches = 0; /* initialize the switches for the new command */
|
|
sim_quiet = ! (audible | verbose); /* and quiet the noise if not specifically requested */
|
|
|
|
kptr = get_glyph (cptr, kbuf, 0); /* parse the command keyword */
|
|
status = get_command (kbuf, &cmdp); /* and get the associated descriptor */
|
|
|
|
if (status == SCPE_OK) { /* if the command is valid */
|
|
is_do = (cmdp->action == ex_do_handler); /* then set a flag if this is a DO command */
|
|
|
|
if (is_do) /* if this is a DO command */
|
|
status = cmdp->action (switches << LEVEL_SHIFT /* then execute the DO */
|
|
| level + 1, kptr); /* and propagate the switches */
|
|
|
|
else { /* otherwise */
|
|
if (cmdp->help_base != NULL && sim_quiet) /* if this is a quiet RUN (GO, etc.) command */
|
|
sim_switches = SIM_SW_HIDE; /* then suppress BREAK and STEP stop messages */
|
|
|
|
status = cmdp->action (cmdp->arg, kptr); /* execute the command */
|
|
|
|
if (cmdp->action == ex_restricted_cmd) /* if this is a restricted command */
|
|
if (cmdp->arg == EX_GOTO) /* then if this is a GOTO command */
|
|
status = goto_label (do_file, kptr); /* then transfer control to the label */
|
|
|
|
else if (cmdp->arg == EX_CALL) /* otherwise if this is a CALL command */
|
|
status = gosub_label (do_file, do_arg [0], /* then transfer control */
|
|
switches << LEVEL_SHIFT
|
|
| level + 1,
|
|
kptr);
|
|
|
|
else if (cmdp->arg == EX_RETURN) { /* otherwise if this is a RETURN command */
|
|
status = SCPE_OK; /* then clear the error status */
|
|
break; /* and drop out of the loop */
|
|
}
|
|
|
|
else if (cmdp->arg == EX_ABORT) { /* otherwise if this is an ABORT command */
|
|
stop_requested = TRUE; /* then set the flag */
|
|
status = SCPE_ABORT; /* and abort status */
|
|
}
|
|
}
|
|
}
|
|
|
|
else /* otherwise the command is invalid */
|
|
is_do = FALSE; /* so it can't be a DO command */
|
|
|
|
staying = (status != SCPE_ABORT /* stay if not aborting */
|
|
&& status != SCPE_EXIT /* and not exiting */
|
|
&& status != SCPE_AFAIL /* and no assertion failed */
|
|
&& (errignore /* and errors are ignored */
|
|
|| status < SCPE_BASE /* or a simulator stop occurred */
|
|
|| status == SCPE_STEP)); /* or a step expired */
|
|
|
|
if (! staying) /* if leaving due to an error */
|
|
sim_brk_clract (); /* then cancel any pending actions */
|
|
|
|
if (status >= SCPE_BASE /* if an SCP error occurred */
|
|
&& status != SCPE_EXIT && status != SCPE_STEP) { /* other then EXIT or STEP */
|
|
if (! verbose /* then if the line has not already been printed */
|
|
&& ! is_do || status & SCPE_DOFAILED) { /* and it's not a command status returned by DO */
|
|
printf("%s> %s\n", do_arg [0], cptr); /* then print the offending command line */
|
|
|
|
if (sim_log) /* if the console is logging */
|
|
fprintf (sim_log, "%s> %s\n", /* then write it to the log file as well */
|
|
do_arg [0], cptr);
|
|
}
|
|
|
|
if (is_do) /* if it's a DO command that has failed */
|
|
status &= ~SCPE_DOFAILED; /* then remove the failure location flag */
|
|
}
|
|
|
|
if (status >= SCPE_BASE && status <= SCPE_LAST /* if an SCP error occurred */
|
|
&& (staying || ! interactive)) { /* and we're staying or were not invoked from the SCP prompt */
|
|
printf ("%s\n", sim_error_text (status)); /* then print the error message */
|
|
|
|
if (sim_log) /* if the console is logging */
|
|
fprintf (sim_log, "%s\n", /* then write it to the log file as well */
|
|
sim_error_text (status));
|
|
}
|
|
|
|
if (sim_vm_post != NULL) /* if the VM defined a post-processor */
|
|
sim_vm_post (TRUE); /* then call it, specifying SCP origin */
|
|
|
|
if (stop_requested) { /* if a stop was detected via a signal */
|
|
stop_requested = FALSE; /* then clear the request */
|
|
|
|
printf ("Command file execution aborted\n");
|
|
|
|
if (sim_log)
|
|
fprintf (sim_log, "Command file execution aborted\n");
|
|
|
|
sim_brk_clract (); /* cancel any pending actions */
|
|
|
|
staying = FALSE; /* end command file execution */
|
|
status = SCPE_ABORT; /* and set status to clear nested invocations */
|
|
}
|
|
}
|
|
|
|
while (staying); /* continue execution until a terminating condition */
|
|
|
|
if (status == SCPE_EXIT && must_detach) { /* if we are exiting the simulator and haven't detached */
|
|
detach_all (0, TRUE); /* then detach all units while still quiet */
|
|
must_detach = FALSE; /* and clear flag to avoid repeating if nested */
|
|
}
|
|
|
|
sim_quiet = ex_quiet; /* restore the original quiet setting */
|
|
|
|
if (file == NULL) /* if we opened the command file */
|
|
fclose (do_file); /* then close it before leaving */
|
|
|
|
return status; /* return the termination status */
|
|
}
|
|
|
|
|
|
/* Execute the GOTO command.
|
|
|
|
This routine is called to process a GOTO command within a command file. The
|
|
command form is:
|
|
|
|
GOTO <label>
|
|
|
|
Where:
|
|
|
|
<label> is an identifier that appears in a label statement somewhere within
|
|
the current command file.
|
|
|
|
This routine transfers command file execution to the labeled statement
|
|
specified by the "cptr" parameter by positioning the file specified by the
|
|
"stream" parameter to the line following the label. On entry, "cptr" points
|
|
at the remainder of the GOTO command line, which should contain a transfer
|
|
label. If it is missing or is followed by additional characters, "Too few
|
|
arguments" or "Too many arguments" errors are returned. Otherwise, a search
|
|
for the label is performed by rewinding the file stream and reading lines
|
|
until either the label is found or the EOF is reached. In the latter case,
|
|
an "Invalid argument" error is returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. A colon preceding the specified label is ignored. Either "GOTO :label"
|
|
or "GOTO label" is accepted.
|
|
|
|
2. A simple linear search from the start of the file is performed. Most
|
|
labels will follow their GOTOs in the file, but optimizing the search
|
|
into two parts (i.e., from the current point to the end, and then from
|
|
the start to the current point) isn't worth the complexity.
|
|
|
|
3. The search is case-sensitive.
|
|
*/
|
|
|
|
static t_stat goto_label (FILE *stream, char *cptr)
|
|
{
|
|
char cbuf [CBUFSIZE], label [CBUFSIZE], *lptr;
|
|
|
|
lptr = label; /* save a pointer to the target label buffer */
|
|
|
|
cptr = get_glyph_nc (cptr, lptr, 0); /* parse the label from the remaining command line */
|
|
|
|
if (*cptr != '\0') /* if there are extraneous characters following */
|
|
return SCPE_2MARG; /* then return the "Too many parameters" error */
|
|
|
|
else if (*lptr == '\0') /* otherwise if the label is missing */
|
|
return SCPE_2FARG; /* then return the "Too few parameters" error */
|
|
|
|
else if (*lptr == ':') /* otherwise if the label starts with a colon */
|
|
lptr++; /* then skip over it to point at the identifier */
|
|
|
|
rewind (stream); /* reposition the file to the start */
|
|
|
|
do { /* loop until the label or the EOF is found */
|
|
cptr = read_line (cbuf, CBUFSIZE, stream); /* read the next line from the file */
|
|
|
|
if (cptr == NULL) /* if the end of file was seen */
|
|
return SCPE_ARG; /* then report that the label was not found */
|
|
|
|
else if (*cptr == ':') { /* otherwise if this is a label line */
|
|
cptr++; /* then skip the leading colon */
|
|
|
|
if (strcmp (cptr, lptr) == 0) /* if this is the target label */
|
|
break; /* then terminate the search here */
|
|
}
|
|
}
|
|
while (TRUE); /* otherwise continue to search */
|
|
|
|
return SCPE_OK; /* return success as the label was found */
|
|
}
|
|
|
|
|
|
/* Execute the CALL command.
|
|
|
|
This routine is called to process a CALL command within a command file. The
|
|
command form is:
|
|
|
|
CALL <label> { <param 1> { <param 2> { ... <param 9> } } }
|
|
|
|
Where:
|
|
|
|
<label> is an identifier that appears in a label statement somewhere within
|
|
the current command file.
|
|
|
|
This routine saves the current position in the file specified by the "stream"
|
|
parameter and then transfers command file execution to the labeled statement
|
|
specified by the "cptr" parameter by positioning the file to the line
|
|
following the label. If positioning succeeds, the commands there are
|
|
executed until a RETURN or ABORT command is executed, or the end of the
|
|
command file is reached. At that point, the original position is restored,
|
|
and the command file continues to execute at the line after the original CALL
|
|
command.
|
|
|
|
On entry, "cptr" points at the remainder of the CALL command line, which
|
|
should contain a transfer label. The "filename" parameter points to the the
|
|
name of the current command file, "level" is set to the current nesting
|
|
level, and "switches" contain the set of switches that were used to invoke
|
|
the current command file. These three parameters are used to set up the same
|
|
command file environment for subroutine execution.
|
|
|
|
If the transfer label is missing, "Too few arguments" status is returned.
|
|
Otherwise, the current stream position is saved, and the "goto_label" routine
|
|
is called to position the stream to the start of the subroutine. If that
|
|
routine succeeds, the current filename is copied into a parameter buffer,
|
|
followed by the remainder of the CALL command line parameters. The switches
|
|
are reset, and the "execute_file" routine is called to execute the
|
|
subroutine. On return, the file position is restored, and the execution
|
|
status is returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "execute_file" routine parses the command line for switches and the
|
|
command filename, which must be the "zeroth" parameter. We must supply
|
|
these so that the subroutine executes in the same environment as the
|
|
containing command file, except that the parameters to the subroutine
|
|
are set to those passed in the CALL command rather than from the
|
|
original parameters to the command file invocation.
|
|
|
|
2. The saved file position is the line after the CALL statement, which is
|
|
where the called subroutine will return.
|
|
*/
|
|
|
|
static t_stat gosub_label (FILE *stream, char *filename, int32 flag, char *cptr)
|
|
{
|
|
char label [CBUFSIZE];
|
|
t_stat status;
|
|
fpos_t current;
|
|
|
|
cptr = get_glyph_nc (cptr, label, 0); /* parse the label from the remaining command line */
|
|
|
|
if (label [0] == '\0') /* if the label is missing */
|
|
return SCPE_2FARG; /* then return the "Too few parameters" error */
|
|
|
|
else { /* otherwise */
|
|
if (fgetpos (stream, ¤t) != 0) { /* save the current position; if that fails */
|
|
perror ("Saving the file position failed"); /* then report the error to the console */
|
|
return SCPE_ABORT; /* and abort command file execution */
|
|
}
|
|
|
|
status = goto_label (stream, label); /* position the file to the subroutine label */
|
|
|
|
if (status == SCPE_OK) { /* if positioning succeeded */
|
|
strcpy (label, filename); /* then form a parameter line */
|
|
strcat (label, " "); /* with the command filename */
|
|
strcat (label, cptr); /* preceding the CALL parameters */
|
|
|
|
status = execute_file (stream, flag, label); /* execute the subroutine */
|
|
|
|
if (fsetpos (stream, ¤t) != 0) { /* restore the file position; if that fails */
|
|
perror ("Restoring the file position failed"); /* then report the error to the console */
|
|
return SCPE_ABORT; /* and abort command file execution */
|
|
}
|
|
}
|
|
|
|
return status; /* return the command status */
|
|
}
|
|
}
|
|
|
|
|
|
/* Replace a predefined token.
|
|
|
|
This routine replaces a predefined token with its associated value. Token
|
|
references are character strings surrounded by percent signs ("%") and not
|
|
containing blanks. For example, "%token%" is a token reference, but "%not a
|
|
token%" is not.
|
|
|
|
On entry, "token_ptr" points at a valid token reference, "out_ptr" points at
|
|
a pointer to the buffer where the associated value is to be placed, and
|
|
"out_size" points at the size of the output buffer. The token is obtained by
|
|
stripping the percent signs and converting to upper case. The keyword table
|
|
is then searched for a match for the token. If it is found, the associated
|
|
substitution action directs the generation of the returned value.
|
|
|
|
For date and time values, the current time (or a time rescaled to the 20th
|
|
century) is formatted as directed by the keyword entry. Other values are
|
|
generally static and are copied to the output buffer. After copying, the
|
|
output pointer is advanced over the value, and the buffer size is decreased
|
|
appropriately.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We get the current local time unconditionally, as most of the token
|
|
substitutions requested will be for date or time values.
|
|
|
|
2. The "token_ptr" cannot be "const" because the "get_glyph" routine takes a
|
|
variable input parameter, even though it is not modified.
|
|
|
|
3. If insufficient space remains in the output buffer, the "strftime"
|
|
routine return 0 and leaves the buffer in an indeterminate state. In
|
|
this case, we store a NUL at the start of the buffer to ensure that the
|
|
buffer is properly terminated.
|
|
|
|
4. The SIM_MAJOR value is a constant and so cannot be the target of the
|
|
"ptr" field. Therefore, we must declare a variable that is set to this
|
|
value and point at that.
|
|
|
|
5. "sim_prog_name" is a pointer to the program name, rather than the program
|
|
name string itself. So the associated "ptr" field is a pointer to a
|
|
pointer to the name.
|
|
|
|
6. The "copy_string" routine updates its output pointer and size
|
|
parameters, so we avoid updating them again when that routine is called.
|
|
*/
|
|
|
|
typedef enum { /* substitution actions */
|
|
Format_Value, /* format an integer value */
|
|
Format_Date, /* format a date or time */
|
|
Rescale_Date, /* format a date rescaled to the 20th century */
|
|
Copy_String, /* copy a string value directly */
|
|
} ACTION;
|
|
|
|
typedef struct { /* the keyword descriptor */
|
|
const char *token; /* the token name */
|
|
void *ptr; /* a pointer to the substitution value */
|
|
const char *format; /* the substitution format */
|
|
ACTION substitution; /* the substitution action */
|
|
} KEYWORD;
|
|
|
|
static uint32 sim_major = SIM_MAJOR; /* the simulator major version number */
|
|
static char **sim_name_ptr = (char **) &sim_name; /* a pointer to the simulator name */
|
|
|
|
static const KEYWORD keys [] = {
|
|
/* Token Pointer Format Substitution */
|
|
/* -------------- ---------------- ------ ------------ */
|
|
{ "DATE_YYYY", NULL, "%Y", Format_Date }, /* four-digit year */
|
|
{ "DATE_YY", NULL, "%y", Format_Date }, /* two-digit year 00-99 */
|
|
{ "DATE_MM", NULL, "%m", Format_Date }, /* two-digit month 01-12 */
|
|
{ "DATE_MMM", NULL, "%b", Format_Date }, /* three-character month JAN-DEC */
|
|
{ "DATE_DD", NULL, "%d", Format_Date }, /* two-digit day of the month 01-31 */
|
|
{ "DATE_JJJ", NULL, "%j", Format_Date }, /* three-digit Julian day of the year 001-366 */
|
|
{ "DATE_RRRR", NULL, "%Y", Rescale_Date }, /* four-digit year rescaled to the 20th century 1972-1999 */
|
|
{ "DATE_RR", NULL, "%y", Rescale_Date }, /* two-digit year rescaled to the 20th century 72-99 */
|
|
{ "TIME_HH", NULL, "%H", Format_Date }, /* two-digit hour of the day 01-23 */
|
|
{ "TIME_MM", NULL, "%M", Format_Date }, /* two-digit minute of the hour 00-59 */
|
|
{ "TIME_SS", NULL, "%S", Format_Date }, /* two-digit second of the minute 00-59 */
|
|
{ "SIM_MAJOR", &sim_major, "%d", Format_Value }, /* the major version number of the simulator */
|
|
{ "SIM_NAME", &sim_name_ptr, NULL, Copy_String }, /* the name of the simulator */
|
|
{ "SIM_EXEC", &sim_prog_name, NULL, Copy_String }, /* the simulator executable path and filename */
|
|
{ "SIM_RUNNING", &concurrent_run, "%d", Format_Value }, /* non-zero if the simulator is running */
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
static void replace_token (char **out_ptr, int32 *out_size, char *token_ptr)
|
|
{
|
|
const KEYWORD *kptr;
|
|
char tbuf [CBUFSIZE];
|
|
size_t space;
|
|
time_t time_value;
|
|
struct tm *now;
|
|
|
|
get_glyph (token_ptr + 1, tbuf, '%'); /* copy the token and convert it to upper case */
|
|
|
|
for (kptr = keys; kptr->token != NULL; kptr++) /* search the keyword table for the token */
|
|
if (strcmp (tbuf, kptr->token) == 0) { /* if the current keyword matches */
|
|
time_value = time (NULL); /* then get the current UTC time */
|
|
now = localtime (&time_value); /* and convert to local time */
|
|
|
|
switch (kptr->substitution) { /* dispatch for the substitution */
|
|
|
|
case Rescale_Date: /* current date with year rescaled to 1972-1999 */
|
|
while (now->tm_year >= 100) /* reduce the current year */
|
|
now->tm_year = now->tm_year - 28; /* until it aligns with one in the 20th century */
|
|
|
|
/* fall through into Format_Date */
|
|
|
|
case Format_Date: /* current time/date */
|
|
space = strftime (*out_ptr, *out_size, /* format and store a time or date value */
|
|
kptr->format, now);
|
|
|
|
if (space == 0) /* if the value wasn't stored */
|
|
**out_ptr = '\0'; /* then be sure the output string is terminated */
|
|
break;
|
|
|
|
case Format_Value: /* integer value */
|
|
space = snprintf (*out_ptr, *out_size, /* format and store an integer value */
|
|
kptr->format,
|
|
*(int *) kptr->ptr);
|
|
break;
|
|
|
|
default: /* needed to quiet warning about "space" being undefined */
|
|
case Copy_String: /* string value */
|
|
copy_string (out_ptr, out_size, /* copy a string value */
|
|
*(char **) kptr->ptr, 0);
|
|
space = 0; /* output pointer and size have already been adjusted */
|
|
break;
|
|
} /* all cases are covered */
|
|
|
|
*out_ptr = *out_ptr + space; /* adjust the output pointer */
|
|
*out_size = *out_size - space; /* and the size remaining for the copy */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Copy a string without overrun.
|
|
|
|
This routine copies a string to a buffer while ensuring that the buffer does
|
|
not overflow. On entry, "source" points to the string to copy, "source_size"
|
|
is zero if the entire string is to be copied or a positive value if only a
|
|
leading substring is to be copied, "target" points at a pointer to the target
|
|
buffer, and "target_size" points at the size of the buffer.
|
|
|
|
If "source_size" is zero, the string length is obtained. If the source
|
|
length is greater than the space available in the target buffer, the length
|
|
is reduced to match the space available. Then the string is copied. The
|
|
target buffer pointer is updated, and a NUL is appended to terminate the
|
|
string. Finally, the buffer size is decreased by the size of the copied
|
|
string.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. This routine is needed because the standard "strncpy" function does not
|
|
guarantee that a NUL is appended. Also, if space is available, the
|
|
remainder of the output buffer is filled with NULs, which is unnecessary
|
|
for our use.
|
|
*/
|
|
|
|
static void copy_string (char **target, int32 *target_size, const char *source, int32 source_size)
|
|
{
|
|
int32 copy_size;
|
|
|
|
if (source_size == 0) /* if we are asked to calculate the source length */
|
|
copy_size = strlen (source); /* then do it now */
|
|
else /* otherwise */
|
|
copy_size = source_size; /* use the supplied size */
|
|
|
|
if (copy_size > *target_size) /* if there is not enough space remaining */
|
|
copy_size = *target_size; /* then copy only enough to fill the buffer */
|
|
|
|
memcpy (*target, source, copy_size); /* copy the string */
|
|
|
|
*target = *target + copy_size; /* advance the output buffer pointer */
|
|
**target = '\0'; /* and terminate the copied string */
|
|
|
|
*target_size = *target_size - copy_size; /* drop the remaining space count */
|
|
return; /* and return */
|
|
}
|
|
|
|
|
|
/* Parse a quoted string.
|
|
|
|
A string delimited by single or double quotation marks is parsed from the
|
|
buffer pointed to by the "sptr" parameter and copied into the buffer pointed
|
|
to by the "dptr" parameter. These specialized escapes are decoded and
|
|
replaced with the indicated substitutions:
|
|
|
|
Escape Substitution
|
|
------ ------------
|
|
\" "
|
|
\' '
|
|
\\ \
|
|
\r CR
|
|
\n LF
|
|
|
|
In addition, a backslash followed by exactly three octal digits is replaced
|
|
with the corresponding ASCII code. The opening and closing quotes are not
|
|
copied, but any escaped embedded quotes are. All other characters are copied
|
|
verbatim, except that lowercase alphabetic characters are replaced with
|
|
uppercase characters if the "upshift" parameter is TRUE.
|
|
|
|
The function returns a pointer to the next character in the source buffer
|
|
after the closing quotation mark. If the opening and closing quotation marks
|
|
are not the same or the closing quotation mark is missing, the function
|
|
returns NULL.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The routine assumes that the first "sptr" character is the opening quote,
|
|
and it is this character that will be sought as the closing quote. It is
|
|
not necessary to escape the alternate quote character if it appears in
|
|
the string. For example, "It's great!" and 'Say "Hi"' are accepted as
|
|
legal.
|
|
*/
|
|
|
|
static char *parse_quoted_string (char *sptr, char *dptr, t_bool upshift)
|
|
{
|
|
char quote;
|
|
uint32 i, octal;
|
|
|
|
quote = *sptr++; /* save the opening quotation mark */
|
|
|
|
while (sptr [0] != '\0' && sptr [0] != quote) /* while characters remain */
|
|
if (sptr [0] == '\\') /* if an escape sequence follows */
|
|
if (sptr [1] == quote || sptr [1] == '\\') { /* then if it is a quote or backslash escape */
|
|
sptr++; /* then skip over the sequence identifier */
|
|
*dptr++ = *sptr++; /* and copy the escaped character verbatim */
|
|
}
|
|
|
|
else if (sptr [1] == 'r' || sptr [1] == 'R') { /* otherwise if it is a carriage return escape */
|
|
sptr = sptr + 2; /* then skip the escape pair */
|
|
*dptr++ = CR; /* and substitute a CR */
|
|
}
|
|
|
|
else if (sptr [1] == 'n' || sptr [1] == 'N') { /* otherwise if it is a line feed escape */
|
|
sptr = sptr + 2; /* then skip the escape pair */
|
|
*dptr++ = LF; /* and substitute a LF */
|
|
}
|
|
|
|
else if (isdigit (sptr [1])) { /* otherwise if it's a numeric escape */
|
|
sptr++; /* then skip over the sequence identifier */
|
|
|
|
for (i = octal = 0; i < 3; i++) /* look for three octal digits */
|
|
if (*sptr >= '0' && *sptr <= '7') /* if it's an octal digit */
|
|
octal = octal * 8 + *sptr++ - '0'; /* then accumulate the value */
|
|
else /* otherwise */
|
|
break; /* the escape is invalid */
|
|
|
|
if (i == 3 && octal <= DEL) /* if the result is valid */
|
|
*dptr++ = (char) octal; /* then copy the escaped value */
|
|
else /* otherwise */
|
|
return NULL; /* the numeric escape is invalid */
|
|
}
|
|
|
|
else /* otherwise the escape is unrecognized */
|
|
*dptr++ = *sptr++; /* so copy the character verbatim */
|
|
|
|
else if (upshift) /* otherwise if case conversion is requested */
|
|
*dptr++ = toupper (*sptr++); /* then copy the character and upshift */
|
|
else /* otherwise */
|
|
*dptr++ = *sptr++; /* copy the character verbatim */
|
|
|
|
*dptr = '\0'; /* terminate the destination buffer */
|
|
|
|
if (sptr [0] == '\0') /* if we did not see a closing quotation mark */
|
|
return NULL; /* then the string is invalid */
|
|
|
|
else { /* otherwise */
|
|
while (isspace (*++sptr)); /* discard any trailing spaces */
|
|
|
|
return sptr; /* return a pointer to the remainder of the source string */
|
|
}
|
|
}
|
|
|
|
|
|
/* Parse a DELAY clause.
|
|
|
|
This routine parses a clause of the form:
|
|
|
|
DELAY <delay>
|
|
|
|
...where <delay> is an unsigned count representing the number of event ticks
|
|
to delay an operation.
|
|
|
|
On entry, "cptr" points at a pointer to the input string, and "delay" points
|
|
at an integer variable that will receive the delay value. If the input
|
|
string does not begin with the DELAY keyword, the "delay" value is set to -1,
|
|
and SCPE_OK is returned. If the keyword is present, the following value is
|
|
converted to a number. If the value does not parse, SCPE_ARG status is
|
|
returned, and "delay" is unchanged. Otherwise, the value stored in the
|
|
variable indicated by "delay", "cptr" is advanced over the clause, and
|
|
SCPE_OK is returned.
|
|
*/
|
|
|
|
static t_stat parse_delay (char **cptr, int32 *delay)
|
|
{
|
|
char *tptr, vbuf [CBUFSIZE];
|
|
t_stat status;
|
|
|
|
tptr = get_glyph (*cptr, vbuf, 0); /* parse out the next glyph */
|
|
|
|
if (strcmp (vbuf, "DELAY") == 0) { /* if this is a DELAY keyword */
|
|
tptr = get_glyph (tptr, vbuf, 0); /* then get the delay value */
|
|
|
|
*delay = (int32) get_uint (vbuf, 10, INT_MAX, &status); /* parse the number */
|
|
|
|
if (status == SCPE_OK) /* if the parse succeeded */
|
|
*cptr = tptr; /* then advance the input pointer over the clause */
|
|
else /* otherwise */
|
|
return status; /* return the parse status */
|
|
}
|
|
|
|
else /* otherwise */
|
|
*delay = -1; /* the keyword is not DELAY */
|
|
|
|
return SCPE_OK; /* return success */
|
|
}
|
|
|
|
|
|
/* Encode a string for printing.
|
|
|
|
This routine encodes a string containing control characters into the
|
|
equivalent escaped form, surrounded by quote marks. On entry, "source"
|
|
points at the string to encode. Embedded quotes and control characters are
|
|
replaced with their escaped counterparts. The return value points at an
|
|
internal static buffer containing the encoded string, as the routine is
|
|
intended to be called from a print function and so used immediately.
|
|
|
|
The supported escapes are the same as those parsed for quoted strings.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. To avoid dealing with buffer overflows, we declare an output buffer large
|
|
enough to accommodate the largest decoded input string. This would be a
|
|
string where each character is a control character requiring four
|
|
encoding characters in the output buffer (plus three more for the
|
|
surrounding quotes and the trailing NUL). Consequently, we need not
|
|
check for the end of the buffer while encoding.
|
|
*/
|
|
|
|
static char *encode (const char *source)
|
|
{
|
|
static char encoding [CBUFSIZE * 4 + 3]; /* ensure there is always enough space */
|
|
char *eptr;
|
|
|
|
encoding [0] = '"'; /* start with a leading quote */
|
|
eptr = encoding + 1; /* and point at the next character */
|
|
|
|
while (*source != '\0') { /* while source characters remain */
|
|
if (iscntrl (*source) /* if the next character is a control character */
|
|
|| *source == '"' || *source == '\\') { /* or is a quote or backslash */
|
|
*eptr++ = '\\'; /* then escape it */
|
|
|
|
if (*source == '\r') /* if it's a CR character */
|
|
*eptr++ = 'r'; /* then replace it with the \r sequence */
|
|
|
|
else if (*source == '\n') /* otherwise if it's an LF character */
|
|
*eptr++ = 'n'; /* then replace it with the \n sequence */
|
|
|
|
else if (*source == '"' || *source == '\\') /* otherwise if it's a quote or backslash character */
|
|
*eptr++ = *source; /* then replace it with the \" or \\ sequence */
|
|
|
|
else { /* otherwise */
|
|
sprintf (eptr, "%03o", (int) *source); /* replace it */
|
|
eptr = eptr + 3; /* with the \ooo sequence */
|
|
}
|
|
}
|
|
|
|
else /* otherwise it's a normal character */
|
|
*eptr++ = *source; /* so copy it verbatim */
|
|
|
|
source++; /* bump the source pointer */
|
|
} /* and continue until all characters are encoded */
|
|
|
|
*eptr++ = '"'; /* add a closing quote */
|
|
*eptr = '\0'; /* and terminate the string */
|
|
|
|
return encoding; /* return a pointer to the encoded string */
|
|
}
|