root/src/dps8/dps8_console.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ta_flush
  2. ta_push
  3. ta_peek
  4. ta_get
  5. opc_reset
  6. check_attn_key
  7. console_init
  8. console_exit
  9. opc_autoinput_set
  10. clear_opc_autoinput
  11. add_opc_autoinput
  12. opc_autoinput_show
  13. console_attn
  14. console_attn_idx
  15. newlineOff
  16. newlineOn
  17. handleRCP
  18. sendConsole
  19. consoleProcessIdx
  20. consoleProcess
  21. opc_iom_cmd
  22. opc_svc
  23. opc_show_nunits
  24. opc_set_nunits
  25. opc_set_config
  26. opc_show_config
  27. opc_show_device_name
  28. opc_set_device_name
  29. opc_set_console_port
  30. opc_show_console_port
  31. opc_set_console_address
  32. opc_show_console_address
  33. opc_set_console_pw
  34. opc_show_console_pw
  35. console_putstr
  36. consolePutchar0
  37. console_putchar
  38. consoleConnectPrompt
  39. startRemoteConsole

   1 /*
   2  * vim: filetype=c:tabstop=4:ai:expandtab
   3  * SPDX-License-Identifier: ICU
   4  * SPDX-License-Identifier: Multics
   5  * scspell-id: 6421764a-f62d-11ec-b542-80ee73e9b8e7
   6  *
   7  * ---------------------------------------------------------------------------
   8  *
   9  * Copyright (c) 2007-2013 Michael Mondy
  10  * Copyright (c) 2012-2016 Harry Reed
  11  * Copyright (c) 2013-2016 Charles Anthony
  12  * Copyright (c) 2021-2024 The DPS8M Development Team
  13  *
  14  * This software is made available under the terms of the ICU License.
  15  * See the LICENSE.md file at the top-level directory of this distribution.
  16  *
  17  * ---------------------------------------------------------------------------
  18  *
  19  * This source file may contain code comments that adapt, include, and/or
  20  * incorporate Multics program code and/or documentation distributed under
  21  * the Multics License.  In the event of any discrepancy between code
  22  * comments herein and the original Multics materials, the original Multics
  23  * materials should be considered authoritative unless otherwise noted.
  24  * For more details and historical background, see the LICENSE.md file at
  25  * the top-level directory of this distribution.
  26  *
  27  * ---------------------------------------------------------------------------
  28  */
  29 
  30 #include <stdio.h>
  31 #include <unistd.h>
  32 #include <signal.h>
  33 #if !defined(__MINGW64__) && !defined(__MINGW32__)
  34 # include <termios.h>
  35 #endif /* if !defined(__MINGW64__) && !defined(__MINGW32__) */
  36 #include <ctype.h>
  37 
  38 #include "dps8.h"
  39 #include "dps8_iom.h"
  40 #include "dps8_sys.h"
  41 #include "dps8_console.h"
  42 #include "dps8_cable.h"
  43 #include "dps8_cpu.h"
  44 #include "dps8_faults.h"
  45 #include "dps8_scu.h"
  46 #include "dps8_mt.h"  // attachTape
  47 #include "dps8_disk.h"  // attachDisk
  48 #include "dps8_utils.h"
  49 #if defined(LOCKLESS)
  50 # include "threadz.h"
  51 #endif /* if defined(LOCKLESS) */
  52 
  53 #include "libtelnet.h"
  54 #if defined(CONSOLE_FIX)
  55 # include "threadz.h"
  56 #endif /* if defined(CONSOLE_FIX) */
  57 
  58 #if defined(SIM_NAME)
  59 # undef SIM_NAME
  60 #endif /* if defined(SIM_NAME) */
  61 #define SIM_NAME "DPS8M"
  62 
  63 #define DBG_CTR 1
  64 #define ASSUME0 0
  65 
  66 #if defined(FREE)
  67 # undef FREE
  68 #endif /* if defined(FREE) */
  69 #define FREE(p) do  \
  70   {                 \
  71     free((p));      \
  72     (p) = NULL;     \
  73   } while(0)
  74 
  75 // config switch -- The bootload console has a 30-second timer mechanism. When
  76 // reading from the console, if no character is typed within 30 seconds, the
  77 // read operation is terminated. The timer is controlled by an enable switch,
  78 // must be set to enabled during Multics and BCE
  79 
  80 static t_stat opc_reset (DEVICE * dptr);
  81 static t_stat opc_show_nunits (FILE *st, UNIT *uptr, int val,
  82                                const void *desc);
  83 static t_stat opc_set_nunits (UNIT * uptr, int32 value, const char * cptr,
  84                               void * desc);
  85 static t_stat opc_autoinput_set (UNIT *uptr, int32 val, const char *cptr,
  86                                  void *desc);
  87 static t_stat opc_autoinput_show (FILE *st, UNIT *uptr, int val,
  88                                   const void *desc);
  89 static t_stat opc_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
  90                               const char * cptr, UNUSED void * desc);
  91 static t_stat opc_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
  92                                UNUSED int  val, UNUSED const void * desc);
  93 static t_stat opc_set_console_port (UNIT * uptr, UNUSED int32 value,
  94                                     const char * cptr, UNUSED void * desc);
  95 static t_stat opc_show_console_port (UNUSED FILE * st, UNIT * uptr,
  96                                        UNUSED int val, UNUSED const void * desc);
  97 static t_stat opc_set_console_address (UNIT * uptr, UNUSED int32 value,
  98                                     const char * cptr, UNUSED void * desc);
  99 static t_stat opc_show_console_address (UNUSED FILE * st, UNIT * uptr,
 100                                        UNUSED int val, UNUSED const void * desc);
 101 static t_stat opc_set_console_pw (UNIT * uptr, UNUSED int32 value,
 102                                     const char * cptr, UNUSED void * desc);
 103 static t_stat opc_show_console_pw (UNUSED FILE * st, UNIT * uptr,
 104                                        UNUSED int val, UNUSED const void * desc);
 105 static t_stat opc_set_device_name (UNIT * uptr, UNUSED int32 value,
 106                                    const char * cptr, UNUSED void * desc);
 107 static t_stat opc_show_device_name (UNUSED FILE * st, UNIT * uptr,
 108                                     UNUSED int val, UNUSED const void * desc);
 109 
 110 static MTAB opc_mtab[] =
 111   {
 112     {
 113        MTAB_unit_nouc,     /* Mask          */
 114        0,                  /* Match         */
 115        "AUTOINPUT",        /* Print string  */
 116        "AUTOINPUT",        /* Match pstring */
 117        opc_autoinput_set,
 118        opc_autoinput_show,
 119        NULL,
 120        NULL
 121     },
 122 
 123     {
 124       MTAB_dev_valr,                        /* Mask               */
 125       0,                                    /* Match              */
 126       "NUNITS",                             /* Print string       */
 127       "NUNITS",                             /* Match string       */
 128       opc_set_nunits,                       /* Validation routine */
 129       opc_show_nunits,                      /* Display routine    */
 130       "Number of OPC units in the system",  /* Value descriptor   */
 131       NULL                                  /* Help               */
 132     },
 133 
 134     {
 135       MTAB_unit_uc,                   /* Mask               */
 136       0,                              /* Match              */
 137       (char *) "CONFIG",              /* Print string       */
 138       (char *) "CONFIG",              /* Match string       */
 139       opc_set_config,                 /* Validation routine */
 140       opc_show_config,                /* Display routine    */
 141       NULL,                           /* Value descriptor   */
 142       NULL,                           /* Help               */
 143     },
 144     {
 145       MTAB_XTD | MTAB_VUN | \
 146       MTAB_VALR | MTAB_NC,            /* Mask               */
 147       0,                              /* Match              */
 148       "NAME",                         /* Print string       */
 149       "NAME",                         /* Match string       */
 150       opc_set_device_name,            /* Validation routine */
 151       opc_show_device_name,           /* Display routine    */
 152       "Set the device name",          /* Value descriptor   */
 153       NULL                            /* Help               */
 154     },
 155 
 156     {
 157       MTAB_unit_valr_nouc,            /* Mask               */
 158       0,                              /* Match              */
 159       "PORT",                         /* Print string       */
 160       "PORT",                         /* Match string       */
 161       opc_set_console_port,           /* validation routine */
 162       opc_show_console_port,          /* Display routine    */
 163       "Set the console port number",  /* Value descriptor   */
 164       NULL                            /* Help               */
 165     },
 166 
 167     {
 168       MTAB_unit_valr_nouc,            /* Mask               */
 169       0,                              /* Match              */
 170       "ADDRESS",                      /* Print string       */
 171       "ADDRESS",                      /* Match string       */
 172       opc_set_console_address,        /* Validation routine */
 173       opc_show_console_address,       /* Display routine    */
 174       "Set the console IP Address",   /* Value descriptor   */
 175       NULL                            /* Help               */
 176     },
 177 
 178     {
 179       MTAB_unit_valr_nouc,            /* Mask               */
 180       0,                              /* Match              */
 181       "PW",                           /* Print string       */
 182       "PW",                           /* Match string       */
 183       opc_set_console_pw,             /* Validation routine */
 184       opc_show_console_pw,            /* Display routine    */
 185       "Set the console password",     /* Value descriptor   */
 186       NULL                            /* Help               */
 187     },
 188 
 189     MTAB_eol
 190 };
 191 
 192 static DEBTAB opc_dt[] =
 193   {
 194     { "NOTIFY", DBG_NOTIFY, NULL },
 195     { "INFO",   DBG_INFO,   NULL },
 196     { "ERR",    DBG_ERR,    NULL },
 197     { "WARN",   DBG_WARN,   NULL },
 198     { "DEBUG",  DBG_DEBUG,  NULL },
 199     { "ALL",    DBG_ALL,    NULL }, // Don't move as it messes up DBG message
 200     { NULL,     0,          NULL }
 201   };
 202 
 203 // Multics only supports a single operator console; but
 204 // it is possible to run multiple Multics instances in a
 205 // cluster. It is also possible to route message output to
 206 // alternate console(s) as I/O devices.
 207 
 208 #define N_OPC_UNITS 1 // default
 209 #define OPC_UNIT_IDX(uptr) ((uptr) - opc_unit)
 210 
 211 // sim_activate counts in instructions, is dependent on the execution
 212 // model
 213 #if defined(LOCKLESS)
 214 // The sim_activate calls are done by the controller thread, which
 215 // has a 1000Hz cycle rate.
 216 // 1K ~= 1 sec
 217 # define ACTIVATE_1SEC 1000
 218 #else
 219 // The sim_activate calls are done by the only thread, with a 4 MHz
 220 // cycle rate.
 221 // 4M ~= 1 sec
 222 # define ACTIVATE_1SEC 4000000
 223 #endif
 224 
 225 static t_stat opc_svc (UNIT * unitp);
 226 
 227 UNIT opc_unit[N_OPC_UNITS_MAX] = {
 228 #if defined(NO_C_ELLIPSIS)
 229   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 230   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 231   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 232   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 233   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 234   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 235   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 236   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
 237 #else
 238   [0 ... N_OPC_UNITS_MAX - 1] = {
 239     UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
 240   }
 241 #endif
 242 };
 243 
 244 DEVICE opc_dev = {
 245     "OPC",         /* Name                */
 246     opc_unit,      /* Units               */
 247     NULL,          /* Registers           */
 248     opc_mtab,      /* Modifiers           */
 249     N_OPC_UNITS,   /* #units              */
 250     10,            /* Address radix       */
 251     8,             /* Address width       */
 252     1,             /* Address increment   */
 253     8,             /* Address width       */
 254     8,             /* Data width          */
 255     NULL,          /* Examine routine     */
 256     NULL,          /* Deposit routine     */
 257     opc_reset,     /* Reset routine       */
 258     NULL,          /* Boot routine        */
 259     NULL,          /* Attach routine      */
 260     NULL,          /* Detach routine      */
 261     NULL,          /* Context             */
 262     DEV_DEBUG,     /* Flags               */
 263     0,             /* Debug control flags */
 264     opc_dt,        /* Debug flag names    */
 265     NULL,          /* Memory size change  */
 266     NULL,          /* Logical name        */
 267     NULL,          /* Help                */
 268     NULL,          /* Attach help         */
 269     NULL,          /* Help context        */
 270     NULL,          /* Description         */
 271     NULL           /* End                 */
 272 };
 273 
 274 enum console_model { m6001 = 0, m6004 = 1, m6601 = 2 };
 275 
 276 // Hangs off the device structure
 277 typedef struct opc_state_t
 278   {
 279     // Track progress of reads through the autoinput buffer
 280     unsigned char *tailp;
 281     unsigned char *readp;
 282 
 283     // Autoinput buffer pointers
 284     unsigned char *auto_input;
 285     unsigned char *autop;
 286 
 287     // stuff saved from the Read ASCII command
 288     time_t startTime;
 289     UNIT * unitp;
 290 
 291     // telnet connection to console
 292     uv_access console_access;
 293 
 294     enum console_model model;
 295     enum console_mode { opc_no_mode, opc_read_mode, opc_write_mode } io_mode;
 296 
 297     // stuff saved from the Read ASCII command
 298     uint tally;
 299     uint daddr;
 300     int chan;
 301 
 302     // ^T
 303     // Generate "accept" command when dial_ctl announces dialin console
 304     int autoaccept;
 305     // Replace empty console input with "@"
 306     int noempty;
 307     // ATTN flushes typeahead buffer
 308     int attn_flush;
 309 
 310     int simh_buffer_cnt;
 311 
 312     // Track the carrier position to allow tab expansion
 313     // (If the left margin is 1, then the tab stops are 11, 21, 31, 41, ...)
 314     int carrierPosition;
 315 
 316     bool echo;
 317 
 318     bool attn_pressed;
 319     bool simh_attn_pressed;
 320 
 321     bool bcd;
 322 
 323     // Handle escape sequence
 324     bool escapeSequence;
 325 
 326     char device_name [MAX_DEV_NAME_LEN];
 327 
 328 // Multics does console reads with a tally of 64 words; so 256 characters + NUL.
 329 // If the tally is smaller than the contents of the buffer, sendConsole will
 330 // issue a warning and discard the excess.
 331 #define bufsize 257
 332     unsigned char keyboardLineBuffer[bufsize];
 333     bool tabStops [bufsize];
 334 
 335 #define simh_buffer_sz 4096
 336     char simh_buffer[simh_buffer_sz];
 337  } opc_state_t;
 338 
 339 static opc_state_t console_state[N_OPC_UNITS_MAX];
 340 
 341 static char * bcd_code_page =
 342   "01234567"
 343   "89[#@;>?"
 344   " ABCDEFG"
 345   "HI&.](<\\"
 346   "^JKLMNOP"
 347   "QR-$*);'"
 348   "+/STUVWX"
 349   "YZ_,%=\"!";
 350 
 351 //
 352 // Typeahead buffer
 353 //
 354 
 355 #if !defined(TA_BUFFER_SIZE)
 356 # define TA_BUFFER_SIZE 65536
 357 #endif
 358 
 359 static int ta_buffer[TA_BUFFER_SIZE];
 360 static uint ta_cnt  = 0;
 361 static uint ta_next = 0;
 362 static bool ta_ovf  = false;
 363 
 364 static void ta_flush (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 365   {
 366     ta_cnt = ta_next = 0;
 367     ta_ovf = false;
 368   }
 369 
 370 static void ta_push (int c)
     /* [previous][next][first][last][top][bottom][index][help] */
 371   {
 372     // discard overflow
 373     if (ta_cnt >= TA_BUFFER_SIZE)
 374       {
 375         if (! ta_ovf)
 376           sim_print ("typeahead buffer overflow");
 377         ta_ovf = true;
 378         return;
 379       }
 380     ta_buffer [ta_cnt ++] = c;
 381   }
 382 
 383 static int ta_peek (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 384   {
 385     if (ta_next >= ta_cnt)
 386       return SCPE_OK;
 387     int c = ta_buffer[ta_next];
 388     return c;
 389   }
 390 
 391 static int ta_get (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 392   {
 393     if (ta_next >= ta_cnt)
 394       return SCPE_OK;
 395     int c = ta_buffer[ta_next ++];
 396     if (ta_next >= ta_cnt)
 397       ta_flush ();
 398     return c;
 399   }
 400 
 401 static t_stat opc_reset (UNUSED DEVICE * dptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 402   {
 403     for (uint i = 0; i < N_OPC_UNITS_MAX; i ++)
 404       {
 405         console_state[i].io_mode         = opc_no_mode;
 406         console_state[i].tailp           = console_state[i].keyboardLineBuffer;
 407         console_state[i].readp           = console_state[i].keyboardLineBuffer;
 408         console_state[i].carrierPosition = 1;
 409         (void)memset (console_state[i].tabStops, 0, sizeof (console_state[i].tabStops));
 410         console_state[i].escapeSequence  = false;
 411       }
 412     return SCPE_OK;
 413   }
 414 
 415 int check_attn_key (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 416   {
 417     for (uint i = 0; i < opc_dev.numunits; i ++)
 418       {
 419         opc_state_t * csp = console_state + i;
 420         if (csp->attn_pressed)
 421           {
 422              csp->attn_pressed = false;
 423              sim_usleep (1000);
 424              return (int) i;
 425           }
 426       }
 427     return -1;
 428   }
 429 
 430 // Once-only initialization
 431 
 432 void console_init (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 433   {
 434     opc_reset (& opc_dev);
 435     for (uint i = 0; i < N_OPC_UNITS_MAX; i ++)
 436       {
 437         opc_state_t * csp      = console_state + i;
 438         csp->model             = m6001;
 439         csp->auto_input        = NULL;
 440         csp->autop             = NULL;
 441         csp->attn_pressed      = false;
 442         csp->simh_attn_pressed = false;
 443         csp->simh_buffer_cnt   = 0;
 444         strcpy (csp->console_access.pw, "MulticsRulez");
 445 
 446         csp->autoaccept      = 0;
 447         csp->noempty         = 0;
 448         csp->attn_flush      = 1;
 449         csp->carrierPosition = 1;
 450         csp->escapeSequence  = 1;
 451         (void)memset (csp->tabStops, 0, sizeof (csp->tabStops));
 452       }
 453   }
 454 
 455 // Once-only shutdown
 456 
 457 void console_exit (void) {
     /* [previous][next][first][last][top][bottom][index][help] */
 458   for (uint i = 0; i < N_OPC_UNITS_MAX; i ++) {
 459     opc_state_t * csp = console_state + i;
 460     if (csp->auto_input) {
 461       FREE (csp->auto_input);
 462       csp->auto_input = NULL;
 463     }
 464     if (csp->console_access.telnetp) {
 465       sim_warn ("console_exit freeing console %u telnetp %p\r\n", i, csp->console_access.telnetp);
 466       telnet_free (csp->console_access.telnetp);
 467       csp->console_access.telnetp = NULL;
 468     }
 469   }
 470 }
 471 
 472 static int opc_autoinput_set (UNIT * uptr, UNUSED int32 val,
     /* [previous][next][first][last][top][bottom][index][help] */
 473                                 const char *  cptr, UNUSED void * desc)
 474   {
 475     int devUnitIdx = (int) OPC_UNIT_IDX (uptr);
 476     opc_state_t * csp = console_state + devUnitIdx;
 477 
 478     if (cptr)
 479       {
 480         unsigned char * new = (unsigned char *) strdupesc (cptr);
 481         if (csp-> auto_input)
 482           {
 483             size_t nl = strlen ((char *) new);
 484             size_t ol = strlen ((char *) csp->auto_input);
 485 
 486             unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 487             if (!old)
 488               {
 489                 (void)fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 490                               __func__, __FILE__, __LINE__);
 491 #if defined(USE_BACKTRACE)
 492 # if defined(SIGUSR2)
 493                 (void)raise(SIGUSR2);
 494                 /*NOTREACHED*/ /* unreachable */
 495 # endif /* if defined(SIGUSR2) */
 496 #endif /* if defined(USE_BACKTRACE) */
 497                 abort();
 498               }
 499             strcpy ((char *) old + ol, (char *) new);
 500             csp->auto_input = old;
 501             FREE (new);
 502           }
 503         else
 504           csp->auto_input = new;
 505       }
 506     else
 507       {
 508         if (csp->auto_input)
 509           FREE (csp->auto_input);
 510         csp->auto_input = NULL;
 511       }
 512     csp->autop = csp->auto_input;
 513     return SCPE_OK;
 514   }
 515 
 516 int clear_opc_autoinput (int32 flag, UNUSED const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 517   {
 518     opc_state_t * csp = console_state + flag;
 519     if (csp->auto_input)
 520       FREE (csp->auto_input);
 521     csp->auto_input = NULL;
 522     csp->autop = csp->auto_input;
 523     return SCPE_OK;
 524   }
 525 
 526 int add_opc_autoinput (int32 flag, const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 527   {
 528     opc_state_t * csp = console_state + flag;
 529     unsigned char * new = (unsigned char *) strdupesc (cptr);
 530     if (csp->auto_input)
 531       {
 532         size_t nl = strlen ((char *) new);
 533         size_t ol = strlen ((char *) csp->auto_input);
 534 
 535         unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 536         if (!old)
 537           {
 538             (void)fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 539                           __func__, __FILE__, __LINE__);
 540 #if defined(USE_BACKTRACE)
 541 # if defined(SIGUSR2)
 542             (void)raise(SIGUSR2);
 543             /*NOTREACHED*/ /* unreachable */
 544 # endif /* if defined(SIGUSR2) */
 545 #endif /* if defined(USE_BACKTRACE) */
 546             abort();
 547           }
 548         strcpy ((char *) old + ol, (char *) new);
 549         csp->auto_input = old;
 550         FREE (new);
 551       }
 552     else
 553       csp->auto_input = new;
 554     csp->autop = csp->auto_input;
 555     return SCPE_OK;
 556   }
 557 
 558 static int opc_autoinput_show (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
 559                                  UNUSED int val, UNUSED const void * desc)
 560   {
 561     int conUnitIdx = (int) OPC_UNIT_IDX (uptr);
 562     opc_state_t * csp = console_state + conUnitIdx;
 563     if (csp->auto_input)
 564       sim_print ("autoinput: '%s'", csp->auto_input);
 565     else
 566       sim_print ("autoinput: empty");
 567     return SCPE_OK;
 568   }
 569 
 570 static t_stat console_attn (UNUSED UNIT * uptr);
 571 
 572 static UNIT attn_unit[N_OPC_UNITS_MAX] = {
 573 #if defined(NO_C_ELLIPSIS)
 574   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 575   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 576   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 577   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 578   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 579   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 580   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 581   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
 582 #else
 583   [0 ... N_OPC_UNITS_MAX - 1] = {
 584     UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
 585   }
 586 #endif
 587 };
 588 
 589 static t_stat console_attn (UNUSED UNIT * uptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 590   {
 591     uint con_unit_idx  = (uint) (uptr - attn_unit);
 592     uint ctlr_port_num = 0; // Consoles are single ported
 593     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
 594     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
 595     uint dev_code      = 0; // Only a single console on the controller
 596 
 597     send_special_interrupt (iom_unit_idx, chan_num, dev_code, 0, 0);
 598     return SCPE_OK;
 599   }
 600 
 601 void console_attn_idx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 602   {
 603     console_attn (attn_unit + conUnitIdx);
 604   }
 605 
 606 #if !defined(__MINGW64__) && !defined(__MINGW32__)
 607 static struct termios ttyTermios;
 608 static bool ttyTermiosOk = false;
 609 
 610 static void newlineOff (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 611   {
 612     if (! isatty (0))
 613       return;
 614     if (! ttyTermiosOk)
 615       {
 616         int rc = tcgetattr (0, & ttyTermios); /* get old flags */
 617         if (rc)
 618            return;
 619         ttyTermiosOk = true;
 620       }
 621     struct termios runtty;
 622     runtty = ttyTermios;
 623     runtty.c_oflag &= (unsigned int) ~OPOST; /* no output edit */
 624 # if defined(__ANDROID__)
 625 #  define TCSA_TYPE TCSANOW
 626     (void)fflush(stdout);
 627     (void)fflush(stderr);
 628 # else
 629 #  define TCSA_TYPE TCSAFLUSH
 630 # endif
 631     tcsetattr (0, TCSA_TYPE, & runtty);
 632   }
 633 
 634 static void newlineOn (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 635   {
 636     if (! isatty (0))
 637       return;
 638     if (! ttyTermiosOk)
 639       return;
 640 # if defined(__ANDROID__)
 641     (void)fflush(stdout);
 642     (void)fflush(stderr);
 643 # endif
 644     tcsetattr (0, TCSA_TYPE, & ttyTermios);
 645   }
 646 #endif /* if !defined(__MINGW64__) && !defined(__MINGW32__) */
 647 
 648 static void handleRCP (uint con_unit_idx, char * text)
     /* [previous][next][first][last][top][bottom][index][help] */
 649   {
 650 // It appears that Cygwin doesn't grok "%ms"
 651 
 652 
 653 
 654 
 655 
 656 
 657 
 658 
 659     size_t len = strlen (text);
 660     char   label [len + 1];
 661     char   with  [len + 1];
 662     char   drive [len + 1];
 663     int rc = sscanf (text, "%*d.%*d RCP: Mount Reel %s %s ring on %s",
 664                 label, with, drive);
 665     if (rc == 3)
 666       {
 667         bool withring = (strcmp (with, "with") == 0);
 668         char labelDotTap[strlen (label) + strlen (".tap") + 1];
 669         strcpy (labelDotTap, label);
 670         strcat (labelDotTap, ".tap");
 671         attachTape (labelDotTap, withring, drive);
 672         return;
 673       }
 674 
 675     rc = sscanf (text, "%*d.%*d RCP: Remount Reel %s %s ring on %s",
 676                 label, with, drive);
 677     if (rc == 3)
 678       {
 679         bool withring = (strcmp (with, "with") == 0);
 680         char labelDotTap [strlen (label) + strlen (".tap") + 1];
 681         strcpy (labelDotTap, label);
 682         strcat (labelDotTap, ".tap");
 683         attachTape (labelDotTap, withring, drive);
 684         return;
 685       }
 686 
 687 //  1629  as   dial_ctl_: Channel d.h000 dialed to Initializer
 688 
 689     if (console_state[con_unit_idx].autoaccept)
 690       {
 691         rc = sscanf (text, "%*d  as   dial_ctl_: Channel %s dialed to Initializer",
 692                      label);
 693         if (rc == 1)
 694           {
 695             //sim_printf (" dial system <%s>\r\n", label);
 696             opc_autoinput_set (opc_unit + con_unit_idx, 0, "accept ", NULL);
 697             opc_autoinput_set (opc_unit + con_unit_idx, 0, label, NULL);
 698             opc_autoinput_set (opc_unit + con_unit_idx, 0, "\r", NULL);
 699 // XXX This is subject to race conditions
 700             if (console_state[con_unit_idx].io_mode != opc_read_mode)
 701               console_state[con_unit_idx].attn_pressed = true;
 702             return;
 703           }
 704       }
 705   }
 706 
 707 // Send entered text to the IOM.
 708 static void sendConsole (int conUnitIdx, word12 stati)
     /* [previous][next][first][last][top][bottom][index][help] */
 709   {
 710     opc_state_t * csp   = console_state + conUnitIdx;
 711     uint tally          = csp->tally;
 712     uint ctlr_port_num  = 0; // Consoles are single ported
 713     uint iomUnitIdx     = cables->opc_to_iom[conUnitIdx][ctlr_port_num].iom_unit_idx;
 714     uint chan_num       = cables->opc_to_iom[conUnitIdx][ctlr_port_num].chan_num;
 715     iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan_num];
 716 
 717     //ASSURE (csp->io_mode == opc_read_mode);
 718     if (csp->io_mode != opc_read_mode)
 719       {
 720         sim_warn ("%s called with io_mode != opc_read_mode (%d)\n",
 721                   __func__, csp->io_mode);
 722         return;
 723       }
 724 
 725     uint n_chars = (uint) (csp->tailp - csp->readp);
 726     uint n_words;
 727     if (csp->bcd)
 728         // + 2 for the !1 newline
 729         n_words = ((n_chars+2) + 5) / 6;
 730       else
 731         n_words = (n_chars + 3) / 4;
 732     // The "+1" is for them empty line case below
 733     word36 buf[n_words + 1];
 734     // Make Oracle lint happy
 735     (void)memset (buf, 0, sizeof (word36) * (n_words + 1));
 736     word36 * bufp = buf;
 737 
 738     // Multics doesn't seem to like empty lines; it the line buffer
 739     // is empty and there is room in the I/O buffer, send a line kill.
 740     if ((!csp->bcd) && csp->noempty && n_chars == 0 && tally)
 741       {
 742         n_chars = 1;
 743         n_words = 1;
 744         putbits36_9 (bufp, 0, '@');
 745         tally --;
 746       }
 747     else
 748       {
 749         int bcd_nl_state = 0;
 750         while (tally && csp->readp < csp->tailp)
 751           {
 752             if (csp->bcd)
 753               {
 754                 //* bufp = 0171717171717ul;
 755                 //* bufp = 0202020202020ul;
 756                 * bufp = 0;
 757                 for (uint charno = 0; charno < 4; ++ charno)
 758                   {
 759                     unsigned char c;
 760                     if (csp->readp >= csp->tailp)
 761                       {
 762                         if (bcd_nl_state == 0)
 763                           {
 764                             c = '!';
 765                             bcd_nl_state = 1;
 766                           }
 767                         else if (bcd_nl_state == 1)
 768                           {
 769                             c = '1';
 770                             bcd_nl_state = 2;
 771                           }
 772                         else
 773                           break;
 774                       }
 775                     else
 776                       c = (unsigned char) (* csp->readp ++);
 777                     c = (unsigned char) toupper (c);
 778                     int i;
 779                     for (i = 0; i < 64; i ++)
 780                       if (bcd_code_page[i] == c)
 781                         break;
 782                     if (i >= 64)
 783                       {
 784                         sim_warn ("Character %o does not map to BCD; replacing with '?'\n", c);
 785                         i = 017; //-V536
 786                       }
 787                     putbits36_6 (bufp, charno * 6, (word6) i);
 788                   }
 789               }
 790             else
 791               {
 792                 * bufp = 0ul;
 793                 for (uint charno = 0; charno < 4; ++ charno)
 794                   {
 795                     if (csp->readp >= csp->tailp)
 796                       break;
 797                     unsigned char c = (unsigned char) (* csp->readp ++);
 798                     c &= 0177;  // Multics get consternated about this
 799                     putbits36_9 (bufp, charno * 9, c);
 800                   }
 801               }
 802             bufp ++;
 803             tally --;
 804           }
 805         if (csp->readp < csp->tailp)
 806           {
 807             sim_warn ("opc_iom_io: discarding %d characters from end of line\n",
 808                       (int) (csp->tailp - csp->readp));
 809           }
 810       }
 811 
 812     iom_indirect_data_service (iomUnitIdx, chan_num, buf, & n_words, true);
 813 
 814     p->charPos = n_chars % 4;
 815     p->stati   = (word12) stati;
 816 
 817     csp->readp   = csp->keyboardLineBuffer;
 818     csp->tailp   = csp->keyboardLineBuffer;
 819     csp->io_mode = opc_no_mode;
 820 
 821     send_terminate_interrupt (iomUnitIdx, chan_num);
 822   }
 823 
 824 static void console_putchar (int conUnitIdx, char ch);
 825 static void console_putstr  (int conUnitIdx, char * str);
 826 
 827 // Process characters entered on keyboard or autoinput
 828 static void consoleProcessIdx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 829   {
 830     opc_state_t * csp = console_state + conUnitIdx;
 831     int c;
 832 
 833 //// Move data from keyboard buffers into type-ahead buffer
 834 
 835     for (;;)
 836       {
 837         c = sim_poll_kbd ();
 838         if (c == SCPE_OK)
 839           c = accessGetChar (& csp->console_access);
 840 
 841         // Check for stop signaled by scp
 842 
 843         if (breakEnable && stop_cpu)
 844           {
 845             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 846             return;
 847           }
 848 
 849         // Check for ^E
 850         //   (Windows doesn't handle ^E as a signal; need to explicitly test
 851         //   for it.)
 852 
 853         if (breakEnable && c == SCPE_STOP)
 854           {
 855             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 856             stop_cpu = 1;
 857             return; // User typed ^E to stop simulation
 858           }
 859 
 860         // Check for scp break
 861 
 862         if (breakEnable && c == SCPE_BREAK)
 863           {
 864             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 865             stop_cpu = 1;
 866             return; // User typed ^E to stop simulation
 867           }
 868 
 869         // End of available input
 870 
 871         if (c == SCPE_OK)
 872           break;
 873 
 874         // sanity test
 875 
 876         if (c < SCPE_KFLAG)
 877           {
 878             sim_printf ("impossible %d %o\n", c, c);
 879             continue; // Should be impossible
 880           }
 881 
 882         // translate to ascii
 883 
 884         int ch = c - SCPE_KFLAG;
 885 
 886         // XXX This is subject to race conditions
 887         // If the console is not in read mode and ESC or ^A
 888         // is pressed, signal Multics for ATTN.
 889         if (csp->io_mode != opc_read_mode)
 890           {
 891             if (ch == '\033' || ch == '\001') // escape or ^A
 892               {
 893                 if (csp->attn_flush)
 894                   ta_flush ();
 895                 csp->attn_pressed = true;
 896                 continue;
 897               }
 898           }
 899 
 900 
 901 
 902 
 903 
 904 
 905 
 906 
 907 
 908 
 909 
 910 
 911 
 912 
 913 
 914 
 915 
 916 
 917 
 918 
 919 
 920 
 921         // ^S
 922 
 923         if (ch == 023) // ^S scp command
 924           {
 925             if (! csp->simh_attn_pressed)
 926               {
 927                 ta_flush ();
 928                 csp->simh_attn_pressed = true;
 929                 csp->simh_buffer_cnt = 0;
 930                 console_putstr (conUnitIdx, "^S\r\n" SIM_NAME "> ");
 931               }
 932             continue;
 933           }
 934 
 935 //// ^P  Prompt
 936 
 937         // ATTN is ambiguous; in read mode, it cancels the read.
 938         // In write mode, is discards output and asks for a prompt.
 939 
 940         // ^P will check the mode, and if it is not input, will do an
 941         // ATTN signal. There is still a small race window; if Multics
 942         // is in the process of starting read mode, it may end up canceling
 943         // the read.
 944 
 945         if (ch == 020) { // ^P
 946           sim_usleep (1000);
 947           if (csp->io_mode != opc_read_mode) {
 948             if (csp->attn_flush)
 949               ta_flush ();
 950             csp->attn_pressed = true;
 951           }
 952           continue;
 953         }
 954 
 955 //// ^T
 956 
 957         if (ch == 024) // ^T
 958           {
 959             char buf[256];
 960             char cms[3] = "?RW";
 961             // XXX Assumes console 0
 962             (void)sprintf (buf, "^T attn %c %c cnt %d next %d\r\n",
 963                            console_state[0].attn_pressed+'0',
 964                            cms[console_state[0].io_mode],
 965                            ta_cnt, ta_next);
 966             console_putstr (conUnitIdx, buf);
 967             continue;
 968           }
 969 
 970 //// In ^S mode (accumulating a scp command)?
 971 
 972         if (csp->simh_attn_pressed)
 973           {
 974             ta_get ();
 975             if (ch == '\177' || ch == '\010')  // backspace/del
 976               {
 977                 if (csp->simh_buffer_cnt > 0)
 978                   {
 979                     -- csp->simh_buffer_cnt;
 980                     csp->simh_buffer[csp->simh_buffer_cnt] = 0;
 981                     console_putstr (conUnitIdx,  "\b \b");
 982                   }
 983                 return;
 984               }
 985 
 986             //// scp ^R
 987 
 988             if (ch == '\022')  // ^R
 989               {
 990                 console_putstr (conUnitIdx, "^R\r\n" SIM_NAME "> ");
 991                 for (int i = 0; i < csp->simh_buffer_cnt; i ++)
 992                   console_putchar (conUnitIdx, (char) (csp->simh_buffer[i]));
 993                 return;
 994               }
 995 
 996             //// scp ^U
 997 
 998             if (ch == '\025')  // ^U
 999               {
1000                 console_putstr (conUnitIdx, "^U\r\n" SIM_NAME "> ");
1001                 csp->simh_buffer_cnt = 0;
1002                 return;
1003               }
1004 
1005             //// scp CR/LF
1006 
1007             if (ch == '\012' || ch == '\015')  // CR/LF
1008               {
1009                 console_putstr (conUnitIdx,  "\r\n");
1010                 csp->simh_buffer[csp->simh_buffer_cnt] = 0;
1011 
1012                 char * cptr = csp->simh_buffer;
1013                 char gbuf[simh_buffer_sz];
1014                 cptr = (char *) get_glyph (cptr, gbuf, 0); /* get command glyph */
1015                 if (strlen (gbuf))
1016                   {
1017                     CTAB *cmdp;
1018                     if ((cmdp = find_cmd (gbuf))) /* lookup command */
1019                       {
1020                         t_stat stat = cmdp->action (cmdp->arg, cptr);
1021                            /* if found, exec */
1022                         if (stat != SCPE_OK)
1023                           {
1024                             char buf[4096];
1025                             (void)sprintf (buf, "\r%s returned %d '%s'\r\n",
1026                                            SIM_NAME, stat, sim_error_text (stat));
1027                             console_putstr (conUnitIdx,  buf);
1028                           }
1029                       }
1030                     else
1031                        console_putstr (conUnitIdx,
1032                          "\rUnrecognized " SIM_NAME " command.\r\n");
1033                   }
1034                 csp->simh_buffer_cnt   = 0;
1035                 csp->simh_buffer[0]    = 0;
1036                 csp->simh_attn_pressed = false;
1037                 return;
1038               }
1039 
1040             //// scp ESC/^D/^Z
1041 
1042             if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1043               {
1044                 console_putstr (conUnitIdx,  "\r\n" SIM_NAME " cancel\r\n");
1045                 // Empty input buffer
1046                 csp->simh_buffer_cnt   = 0;
1047                 csp->simh_buffer[0]    = 0;
1048                 csp->simh_attn_pressed = false;
1049                 return;
1050               }
1051 
1052             //// scp isprint?
1053 
1054             if (isprint (ch))
1055               {
1056                 // silently drop buffer overrun
1057                 if (csp->simh_buffer_cnt + 1 >= simh_buffer_sz)
1058                   return;
1059                 csp->simh_buffer[csp->simh_buffer_cnt ++] = (char) ch;
1060                 console_putchar (conUnitIdx, (char) ch);
1061                 return;
1062               }
1063             return;
1064           } // if (simh_attn_pressed)
1065 
1066         // Save the character
1067 
1068         ta_push (c);
1069       }
1070 
1071 //// Check for stop signaled by scp
1072 
1073     if (breakEnable && stop_cpu)
1074       {
1075         console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
1076         return;
1077       }
1078 
1079 //// Console is reading and autoinput is ready
1080 ////   Move line of text from autoinput buffer to console buffer
1081 
1082     if (csp->io_mode == opc_read_mode &&
1083         csp->autop != NULL)
1084       {
1085         int announce = 1;
1086         for (;;)
1087           {
1088             if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1089              {
1090                 sim_warn ("getConsoleInput: Buffer full; flushing autoinput.\n");
1091                 sendConsole (conUnitIdx, 04000); // Normal status
1092                 return;
1093               }
1094             unsigned char c = * (csp->autop);
1095             if (c == 4) // eot
1096               {
1097                 FREE (csp->auto_input);
1098                 csp->auto_input = NULL;
1099                 csp->autop      = NULL;
1100                 // Empty input buffer
1101                 csp->readp      = csp->keyboardLineBuffer;
1102                 csp->tailp      = csp->keyboardLineBuffer;
1103                 sendConsole (conUnitIdx, 04310); // Null line, status operator
1104                                                  // distracted
1105                 console_putstr (conUnitIdx,  "CONSOLE: RELEASED\r\n");
1106                 sim_usleep (1000);
1107                 return;
1108               }
1109             if (c == 030 || c == 031) // ^X ^Y
1110               {
1111                 // an expect string is in the autoinput buffer; wait for it
1112                 // to be processed
1113                 return;
1114               }
1115             if (c == 0)
1116               {
1117                 FREE (csp->auto_input);
1118                 csp->auto_input = NULL;
1119                 csp->autop      = NULL;
1120                 goto eol;
1121               }
1122             if (announce)
1123               {
1124                 console_putstr (conUnitIdx,  "[auto-input] ");
1125                 announce = 0;
1126               }
1127             csp->autop ++;
1128 
1129             if (c == '\012' || c == '\015')
1130               {
1131 eol:
1132                 if (csp->echo)
1133                   console_putstr (conUnitIdx,  "\r\n");
1134                 sendConsole (conUnitIdx, 04000); // Normal status
1135                 return;
1136               }
1137             else
1138               {
1139                 * csp->tailp ++ = c;
1140                 if (csp->echo)
1141                   console_putchar (conUnitIdx, (char) c);
1142               }
1143           } // for (;;)
1144       } // if (autop)
1145 
1146 //// Read mode and nothing in console buffer
1147 ////   Check for timeout
1148 
1149     if (csp->io_mode == opc_read_mode &&
1150         csp->tailp == csp->keyboardLineBuffer)
1151       {
1152         if (csp->startTime + 30 <= time (NULL))
1153           {
1154             console_putstr (conUnitIdx,  "CONSOLE: TIMEOUT\r\n");
1155             sim_usleep (1000);
1156             csp->readp = csp->keyboardLineBuffer;
1157             csp->tailp = csp->keyboardLineBuffer;
1158             sendConsole (conUnitIdx, 04310); // Null line, status operator
1159                                              // distracted
1160           }
1161       }
1162 
1163 //// Peek at the character in the typeahead buffer
1164 
1165     c = ta_peek ();
1166 
1167     // No data
1168     if (c == SCPE_OK)
1169         return;
1170 
1171     // Convert from scp encoding to ASCII
1172     if (c < SCPE_KFLAG)
1173       {
1174         sim_printf ("impossible %d %o\n", c, c);
1175         return; // Should be impossible
1176       }
1177 
1178     // translate to ascii
1179 
1180     int ch = c - SCPE_KFLAG;
1181 
1182 // XXX This is subject to race conditions
1183     if (csp->io_mode != opc_read_mode)
1184       {
1185         if (ch == '\033' || ch == '\001') // escape or ^A
1186           {
1187             ta_get ();
1188             csp->attn_pressed = true;
1189           }
1190         return;
1191       }
1192 
1193     if (ch == '\177' || ch == '\010')  // backspace/del
1194       {
1195         ta_get ();
1196         if (csp->tailp > csp->keyboardLineBuffer)
1197           {
1198             * csp->tailp = 0;
1199             -- csp->tailp;
1200             if (csp->echo)
1201               console_putstr (conUnitIdx,  "\b \b");
1202           }
1203         return;
1204       }
1205 
1206     if (ch == '\022')  // ^R
1207       {
1208         ta_get ();
1209         if (csp->echo)
1210           {
1211             console_putstr (conUnitIdx,  "^R\r\n");
1212             for (unsigned char * p = csp->keyboardLineBuffer; p < csp->tailp; p ++)
1213               console_putchar (conUnitIdx, (char) (*p));
1214             return;
1215           }
1216       }
1217 
1218     if (ch == '\025')  // ^U
1219       {
1220         ta_get ();
1221         console_putstr (conUnitIdx,  "^U\r\n");
1222         csp->tailp = csp->keyboardLineBuffer;
1223         return;
1224       }
1225 
1226     if (ch == '\030')  // ^X
1227       {
1228         ta_get ();
1229         console_putstr (conUnitIdx,  "^X\r\n");
1230         csp->tailp = csp->keyboardLineBuffer;
1231         return;
1232       }
1233 
1234     if (ch == '\012' || ch == '\015')  // CR/LF
1235       {
1236         ta_get ();
1237         if (csp->echo)
1238           console_putstr (conUnitIdx,  "\r\n");
1239         sendConsole (conUnitIdx, 04000); // Normal status
1240         return;
1241       }
1242 
1243     if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1244       {
1245         ta_get ();
1246         console_putstr (conUnitIdx,  "\r\n");
1247         // Empty input buffer
1248         csp->readp = csp->keyboardLineBuffer;
1249         csp->tailp = csp->keyboardLineBuffer;
1250         sendConsole (conUnitIdx, 04310); // Null line, status operator
1251                                          // distracted
1252         console_putstr (conUnitIdx,  "CONSOLE: RELEASED\n");
1253         sim_usleep (1000);
1254         return;
1255       }
1256 
1257     if (isprint (ch))
1258       {
1259         // silently drop buffer overrun
1260         ta_get ();
1261         if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1262           return;
1263 
1264         * csp->tailp ++ = (unsigned char) ch;
1265         if (csp->echo)
1266           console_putchar (conUnitIdx, (char) ch);
1267         return;
1268       }
1269     // ignore other chars...
1270     ta_get ();
1271     return;
1272   }
1273 
1274 void consoleProcess (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1275   {
1276     for (int conUnitIdx = 0; conUnitIdx < (int) opc_dev.numunits; conUnitIdx ++)
1277       consoleProcessIdx (conUnitIdx);
1278   }
1279 
1280 /*
1281  * opc_iom_cmd ()
1282  *
1283  * Handle a device command.  Invoked by the IOM while processing a PCW
1284  * or IDCW.
1285  */
1286 
1287 iom_cmd_rc_t opc_iom_cmd (uint iomUnitIdx, uint chan) {
     /* [previous][next][first][last][top][bottom][index][help] */
1288   iom_cmd_rc_t rc = IOM_CMD_PROCEED;
1289 #if defined(TESTING)
1290   cpu_state_t * cpup = _cpup;
1291 #endif
1292 
1293 #if defined(LOCKLESS)
1294   lock_libuv ();
1295 #endif
1296 
1297   iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan];
1298   uint con_unit_idx   = get_ctlr_idx (iomUnitIdx, chan);
1299   UNIT * unitp        = & opc_unit[con_unit_idx];
1300   opc_state_t * csp   = console_state + con_unit_idx;
1301 
1302   p->dev_code = p->IDCW_DEV_CODE;
1303   p->stati = 0;
1304   //int conUnitIdx = (int) d->devUnitIdx;
1305 
1306   // The 6001 only executes the PCW DCW command; the 6601 executes
1307   // the PCW DCW and (at least) the first DCW list item.
1308   // When Multics uses the 6601, the PCW DCW is always 040 RESET.
1309   // The 040 RESET will trigger the DCW list read.
1310   // will change this.
1311 
1312   // IDCW?
1313   if (IS_IDCW (p)) {
1314     // IDCW
1315 
1316     switch (p->IDCW_DEV_CMD) {
1317       case 000: // CMD 00 Request status
1318         sim_debug (DBG_DEBUG, & opc_dev, "%s: Status request\n", __func__);
1319         csp->io_mode = opc_no_mode;
1320         p->stati     = 04000;
1321         break;
1322 
1323       case 003:               // Read BCD
1324         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read BCD echoed\n", __func__);
1325         csp->io_mode = opc_read_mode;
1326         p->recordResidue --;
1327         csp->echo    = true;
1328         csp->bcd     = true;
1329         p->stati     = 04000;
1330         break;
1331 
1332       case 013:               // Write BCD
1333         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write BCD\n", __func__);
1334         p->isRead    = false;
1335         csp->bcd     = true;
1336         csp->io_mode = opc_write_mode;
1337         p->recordResidue --;
1338         p->stati     = 04000;
1339         break;
1340 
1341       case 023:               // Read ASCII
1342         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII echoed\n", __func__);
1343         csp->io_mode = opc_read_mode;
1344         p->recordResidue --;
1345         csp->echo    = true;
1346         csp->bcd     = false;
1347         p->stati     = 04000;
1348         break;
1349 
1350       case 033:               // Write ASCII
1351         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write ASCII\n", __func__);
1352         p->isRead    = false;
1353         csp->bcd     = false;
1354         csp->io_mode = opc_write_mode;
1355         p->recordResidue --;
1356         p->stati     = 04000;
1357         break;
1358 
1359 // Model 6001 vs. 6601.
1360 //
1361 // When Multics switches to 6601 mode, the PCW DCW is always 040 RESET; the
1362 // bootload and 6001 code never does that. Therefore, we use the 040
1363 // as an indication that the DCW list should be processed.
1364 // All of the other device commands will return IOM_CMD_DISCONNECT, stopping
1365 // parsing of the channel command program. This one will return IOM_CMD_PROCEED,
1366 // causing the parser to move to the DCW list.
1367 
1368       case 040:               // Reset
1369         sim_debug (DBG_DEBUG, & opc_dev, "%s: Reset\n", __func__);
1370         p->stati = 04000;
1371         // T&D probing
1372         //if (p->IDCW_DEV_CODE == 077) {
1373           // T&D uses dev code 77 to test for the console device;
1374           // it ignores dev code, and so returns OK here.
1375           //p->stati = 04502; // invalid device code
1376           // if (p->IDCW_CHAN_CTRL == 0) { sim_warn ("%s: TERMINATE_BUG\n", __func__); return IOM_CMD_DISCONNECT; }
1377         //}
1378         break;
1379 
1380       case 043:               // Read ASCII unechoed
1381         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII unechoed\n", __func__);
1382         csp->io_mode = opc_read_mode;
1383         p->recordResidue --;
1384         csp->echo    = false;
1385         csp->bcd     = false;
1386         p->stati     = 04000;
1387         break;
1388 
1389       case 051:               // Write Alert -- Ring Bell
1390         sim_debug (DBG_DEBUG, & opc_dev, "%s: Alert\n", __func__);
1391         p->isRead = false;
1392         console_putstr ((int) con_unit_idx,  "CONSOLE: ALERT\r\n");
1393         console_putchar ((int) con_unit_idx, '\a');
1394         p->stati  = 04000;
1395         sim_usleep (1000);
1396         if (csp->model == m6001 && p->isPCW) {
1397           rc = IOM_CMD_DISCONNECT;
1398           goto done;
1399         }
1400         break;
1401 
1402       case 057:               // Read ID (according to AN70-1)
1403         // FIXME: No support for Read ID; appropriate values are not known
1404         //[CAC] Looking at the bootload console code, it seems more
1405         // concerned about the device responding, rather than the actual
1406         // returned value. Make some thing up.
1407         sim_debug (DBG_DEBUG, & opc_dev, "%s: Read ID\n", __func__);
1408         p->stati = 04500;
1409         if (csp->model == m6001 && p->isPCW) {
1410           rc = IOM_CMD_DISCONNECT;
1411           goto done;
1412         }
1413         break;
1414 
1415       case 060:               // LOCK MCA
1416         sim_debug (DBG_DEBUG, & opc_dev, "%s: Lock\n", __func__);
1417         console_putstr ((int) con_unit_idx,  "CONSOLE: LOCK\r\n");
1418         p->stati = 04000;
1419         sim_usleep (1000);
1420         break;
1421 
1422       case 063:               // UNLOCK MCA
1423         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unlock\n", __func__);
1424         console_putstr ((int) con_unit_idx,  "CONSOLE: UNLOCK\r\n");
1425         p->stati = 04000;
1426         sim_usleep (1000);
1427         break;
1428 
1429       default:
1430         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unknown command 0%o\n", __func__, p->IDCW_DEV_CMD);
1431         p->stati = 04501; // command reject, invalid instruction code
1432         rc = IOM_CMD_ERROR;
1433         goto done;
1434     } // switch IDCW_DEV_CMD
1435     goto done;
1436   } // IDCW
1437 
1438   // Not IDCW; TDCW are captured in IOM, so must be IOTD or IOTP
1439   switch (csp->io_mode) {
1440     case opc_no_mode:
1441       sim_warn ("%s: Unexpected IOTx\n", __func__);
1442       rc = IOM_CMD_ERROR;
1443       goto done;
1444 
1445     case opc_read_mode: {
1446         if (csp->tailp != csp->keyboardLineBuffer) {
1447           sim_warn ("%s: Discarding previously buffered input.\n", __func__);
1448         }
1449         uint tally = p->DDCW_TALLY;
1450         uint daddr = p->DDCW_ADDR;
1451 
1452         if (tally == 0) {
1453           tally = 4096;
1454         }
1455 
1456         csp->tailp     = csp->keyboardLineBuffer;
1457         csp->readp     = csp->keyboardLineBuffer;
1458         csp->startTime = time (NULL);
1459         csp->tally     = tally;
1460         csp->daddr     = daddr;
1461         csp->unitp     = unitp;
1462         csp->chan      = (int) chan;
1463 
1464         // If Multics has gone seriously awry (eg crash
1465         // to BCE during boot), the autoinput will become
1466         // wedged waiting for the expect string 'Ready'.
1467         // We just went to read mode; if we are waiting
1468         // on an expect string, it is never coming because
1469         // console access is blocked by the expect code.
1470         // Throw out the script if this happens....
1471 
1472         // If there is autoinput and it is at ^X or ^Y
1473         if (csp->autop && (*csp->autop == 030 || *csp->autop == 031)) { // ^X ^Y
1474           // We are wedged.
1475           // Clear the autoinput buffer; this will cancel the
1476           // expect wait and any remaining script, returning
1477           // control of the console to the user.
1478           // Assuming opc0.
1479           clear_opc_autoinput (ASSUME0, NULL);
1480           ta_flush ();
1481           sim_printf \
1482               ("\r\nScript wedged and abandoned; autoinput and typeahead buffers flushed\r\n");
1483         }
1484         rc = IOM_CMD_PENDING; // command in progress; do not send terminate interrupt
1485         goto done;
1486       } // case opc_read_mode
1487 
1488     case opc_write_mode: {
1489         uint tally = p->DDCW_TALLY;
1490 
1491 // We would hope that number of valid characters in the last word
1492 // would be in DCW_18_20_CP, but it seems to reliably be zero.
1493 
1494         if (tally == 0) {
1495           tally = 4096;
1496         }
1497 
1498         word36 buf[tally];
1499         iom_indirect_data_service (iomUnitIdx, chan, buf, & tally, false);
1500         p->initiate = false;
1501 
1502 
1503 
1504 
1505 
1506 
1507 
1508 
1509 
1510 
1511 
1512 
1513 
1514 
1515 
1516 
1517 
1518 
1519 
1520 
1521 
1522 
1523 
1524 
1525 
1526 
1527 
1528 
1529 
1530 
1531 
1532         // Tally is in words, not chars.
1533         char text[tally * 4 + 1];
1534         char * textp = text;
1535         word36 * bufp = buf;
1536         * textp = 0;
1537 #if !defined(__MINGW64__)
1538         newlineOff ();
1539 #endif
1540         // 0 == no escape character seen
1541         // 1 ==  ! seen
1542         // 2 == !! seen
1543         int escape_cnt = 0;
1544 
1545         while (tally) {
1546           word36 datum = * bufp ++;
1547           tally --;
1548           if (csp->bcd) {
1549             // Lifted from tolts_util_.pl1:bci_to_ascii
1550             for (int i = 0; i < 6; i ++) {
1551               word36 narrow_char = datum >> 30; // slide leftmost char
1552                                                 //  into low byte
1553               datum = datum << 6; // lose the leftmost char
1554               narrow_char &= 077;
1555               char ch = bcd_code_page [narrow_char];
1556               if (escape_cnt == 2) {
1557                 console_putchar ((int) con_unit_idx, ch);
1558                 * textp ++ = ch;
1559                 escape_cnt = 0;
1560               } else if (ch == '!') {
1561                 escape_cnt ++;
1562               } else if (escape_cnt == 1) {
1563                 uint lp = (uint)narrow_char;
1564                 // !0 is mapped to !1
1565                 // !1 to !9, ![, !#, !@, !;, !>, !?    1 to 15 newlines
1566                 if (lp == 060 /* + */ || lp == 075 /* = */) { // POLTS
1567                   p->stati = 04320;
1568                   goto done;
1569                 }
1570                 if (lp == 0)
1571                   lp = 1;
1572                 if (lp >= 16) {
1573                   console_putstr ((int) con_unit_idx, "\f");
1574                   //* textp ++ = '\f';
1575                 } else {
1576                   for (uint i = 0; i < lp; i ++) {
1577                     console_putstr ((int) con_unit_idx, "\r\n");
1578                     //* textp ++ = '\r';
1579                     //* textp ++ = '\n';
1580                   }
1581                 }
1582                 escape_cnt = 0;
1583               } else if (ch == '?') {
1584                 escape_cnt = 0;
1585               } else {
1586                 console_putchar ((int) con_unit_idx, ch);
1587                 * textp ++ = ch;
1588                 escape_cnt = 0;
1589               }
1590             }
1591           } else {
1592             for (int i = 0; i < 4; i ++) {
1593               word36 wide_char = datum >> 27; // slide leftmost char
1594                                               //  into low byte
1595               datum = datum << 9; // lose the leftmost char
1596               char ch = wide_char & 0x7f;
1597               if (ch != 0177 && ch != 0) {
1598                 console_putchar ((int) con_unit_idx, ch);
1599                 * textp ++ = ch;
1600               }
1601             }
1602           }
1603         }
1604         * textp ++ = 0;
1605 
1606         // autoinput expect
1607         if (csp->autop && * csp->autop == 030) {
1608           //   ^xstring\0
1609           //size_t expl = strlen ((char *) (csp->autop + 1));
1610           //   ^xstring^x
1611           size_t expl = strcspn ((char *) (csp->autop + 1), "\030");
1612 
1613           if (strncmp (text, (char *) (csp->autop + 1), expl) == 0) {
1614             csp->autop += expl + 2;
1615             sim_usleep (1000);
1616             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1617           }
1618         }
1619         // autoinput expect
1620         if (csp->autop && * csp->autop == 031) {
1621           //   ^ystring\0
1622           //size_t expl = strlen ((char *) (csp->autop + 1));
1623           //   ^ystring^y
1624           size_t expl = strcspn ((char *) (csp->autop + 1), "\031");
1625 
1626           char needle [expl + 1];
1627           strncpy (needle, (char *) csp->autop + 1, expl);
1628           needle [expl] = 0;
1629           if (strstr (text, needle)) {
1630             csp->autop += expl + 2;
1631             sim_usleep (1000);
1632             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1633           }
1634         }
1635         handleRCP (con_unit_idx, text);
1636 #if !defined(__MINGW64__)
1637         newlineOn ();
1638 #endif
1639         p->stati = 04000;
1640         goto done;
1641       } // case opc_write_mode
1642   } // switch io_mode
1643 
1644 done:
1645 #if defined(LOCKLESS)
1646   unlock_libuv ();
1647 #endif
1648   return rc;
1649 } // opc_iom_cmd
1650 
1651 static t_stat opc_svc (UNIT * unitp)
     /* [previous][next][first][last][top][bottom][index][help] */
1652   {
1653     int con_unit_idx   = (int) OPC_UNIT_IDX (unitp);
1654     uint ctlr_port_num = 0; // Consoles are single ported
1655     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
1656     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
1657 
1658     opc_iom_cmd (iom_unit_idx, chan_num);
1659     return SCPE_OK;
1660   }
1661 
1662 static t_stat opc_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1663                                  UNUSED int val, UNUSED const void * desc)
1664   {
1665     sim_print ("%d units\n", opc_dev.numunits);
1666     return SCPE_OK;
1667   }
1668 
1669 static t_stat opc_set_nunits (UNUSED UNIT * uptr, int32 UNUSED value,
     /* [previous][next][first][last][top][bottom][index][help] */
1670                                 const char * cptr, UNUSED void * desc)
1671   {
1672     if (! cptr)
1673       return SCPE_ARG;
1674     int n = atoi (cptr);
1675     if (n < 1 || n > N_OPC_UNITS_MAX)
1676       return SCPE_ARG;
1677     opc_dev.numunits = (uint32) n;
1678     return SCPE_OK;
1679   }
1680 
1681 static config_value_list_t cfg_on_off[] =
1682   {
1683     { "off",     0 },
1684     { "on",      1 },
1685     { "disable", 0 },
1686     { "enable",  1 },
1687     { NULL,      0 }
1688   };
1689 
1690 static config_value_list_t cfg_model[] =
1691   {
1692     { "m6001", m6001 },
1693     { "m6004", m6004 },
1694     { "m6601", m6601 },
1695     { NULL,    0 }
1696   };
1697 
1698 static config_list_t opc_config_list[] =
1699   {
1700    { "autoaccept", 0, 1, cfg_on_off },
1701    { "noempty",    0, 1, cfg_on_off },
1702    { "attn_flush", 0, 1, cfg_on_off },
1703    { "model",      1, 0, cfg_model },
1704    { NULL,         0, 0, NULL }
1705   };
1706 
1707 static t_stat opc_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1708                               const char * cptr, UNUSED void * desc)
1709   {
1710     int devUnitIdx           = (int) OPC_UNIT_IDX (uptr);
1711     opc_state_t * csp        = console_state + devUnitIdx;
1712 // XXX Minor bug; this code doesn't check for trailing garbage
1713     config_state_t cfg_state = { NULL, NULL };
1714 
1715     for (;;)
1716       {
1717         int64_t v;
1718         int rc = cfg_parse (__func__, cptr, opc_config_list,
1719                            & cfg_state, & v);
1720         if (rc == -1) // done
1721           break;
1722 
1723         if (rc == -2) // error
1724           {
1725             cfg_parse_done (& cfg_state);
1726             return SCPE_ARG;
1727           }
1728         const char * p = opc_config_list[rc].name;
1729 
1730         if (strcmp (p, "autoaccept") == 0)
1731           {
1732             csp->autoaccept = (int) v;
1733             continue;
1734           }
1735 
1736         if (strcmp (p, "noempty") == 0)
1737           {
1738             csp->noempty = (int) v;
1739             continue;
1740           }
1741 
1742         if (strcmp (p, "attn_flush") == 0)
1743           {
1744             csp->attn_flush = (int) v;
1745             continue;
1746           }
1747 
1748         if (strcmp (p, "model") == 0)
1749           {
1750             csp->model = (enum console_model) v;
1751             continue;
1752           }
1753 
1754         sim_warn ("error: opc_set_config: Invalid cfg_parse rc <%d>\n",
1755                   rc);
1756         cfg_parse_done (& cfg_state);
1757         return SCPE_ARG;
1758       } // process statements
1759     cfg_parse_done (& cfg_state);
1760     return SCPE_OK;
1761   }
1762 
1763 static t_stat opc_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1764                                UNUSED int  val, UNUSED const void * desc)
1765   {
1766     int devUnitIdx    = (int) OPC_UNIT_IDX (uptr);
1767     opc_state_t * csp = console_state + devUnitIdx;
1768     sim_msg ("flags    : ");
1769     sim_msg ("autoaccept=%d, ", csp->autoaccept);
1770     sim_msg ("noempty=%d, ",    csp->noempty);
1771     sim_msg ("attn_flush=%d",   csp->attn_flush);
1772     return SCPE_OK;
1773   }
1774 
1775 static t_stat opc_show_device_name (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1776                                     UNUSED int val, UNUSED const void * desc)
1777   {
1778     int n = (int) OPC_UNIT_IDX (uptr);
1779     if (n < 0 || n >= N_OPC_UNITS_MAX)
1780       return SCPE_ARG;
1781     sim_printf("name     : OPC%d", n);
1782     return SCPE_OK;
1783   }
1784 
1785 static t_stat opc_set_device_name (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1786                                    const char * cptr, UNUSED void * desc)
1787   {
1788     int n = (int) OPC_UNIT_IDX (uptr);
1789     if (n < 0 || n >= N_OPC_UNITS_MAX)
1790       return SCPE_ARG;
1791     if (cptr)
1792       {
1793         strncpy (console_state[n].device_name, cptr, MAX_DEV_NAME_LEN-1);
1794         console_state[n].device_name[MAX_DEV_NAME_LEN-1] = 0;
1795       }
1796     else
1797       console_state[n].device_name[0] = 0;
1798     return SCPE_OK;
1799   }
1800 
1801 static t_stat opc_set_console_port (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1802                                     const char * cptr, UNUSED void * desc)
1803   {
1804     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1805     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1806       return SCPE_ARG;
1807 
1808     if (cptr)
1809       {
1810         int port = atoi (cptr);
1811         if (port < 0 || port > 65535) // 0 is 'disable'
1812           return SCPE_ARG;
1813         console_state[dev_idx].console_access.port = port;
1814         if (port != 0)
1815           sim_msg ("[OPC emulation: TELNET server port set to %d]\r\n", (int)port);
1816       }
1817     else
1818       console_state[dev_idx].console_access.port = 0;
1819     return SCPE_OK;
1820   }
1821 
1822 static t_stat opc_show_console_port (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1823                                        UNUSED int val, UNUSED const void * desc)
1824   {
1825     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1826     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1827       return SCPE_ARG;
1828     if (console_state[dev_idx].console_access.port)
1829       sim_printf("port     : %d", console_state[dev_idx].console_access.port);
1830     else
1831       sim_printf("port     : disabled");
1832     return SCPE_OK;
1833   }
1834 
1835 static t_stat opc_set_console_address (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1836                                     const char * cptr, UNUSED void * desc)
1837   {
1838     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1839     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1840       return SCPE_ARG;
1841 
1842     if (console_state[dev_idx].console_access.address)
1843       {
1844         FREE (console_state[dev_idx].console_access.address);
1845         console_state[dev_idx].console_access.address = NULL;
1846       }
1847 
1848     if (cptr)
1849       {
1850         console_state[dev_idx].console_access.address = strdup (cptr);
1851         if (!console_state[dev_idx].console_access.address)
1852           {
1853             (void)fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
1854                            __func__, __FILE__, __LINE__);
1855 #if defined(USE_BACKTRACE)
1856 # if defined(SIGUSR2)
1857             (void)raise(SIGUSR2);
1858             /*NOTREACHED*/ /* unreachable */
1859 # endif /* if defined(SIGUSR2) */
1860 #endif /* if defined(USE_BACKTRACE) */
1861             abort();
1862           }
1863         sim_msg ("\r[OPC emulation: OPC%d server address set to %s]\r\n",
1864                 dev_idx, console_state[dev_idx].console_access.address);
1865       }
1866 
1867     return SCPE_OK;
1868   }
1869 
1870 static t_stat opc_show_console_address (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1871                                        UNUSED int val, UNUSED const void * desc)
1872   {
1873     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1874     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1875       return SCPE_ARG;
1876     if (console_state[dev_idx].console_access.address)
1877       sim_printf("address  : %s", console_state[dev_idx].console_access.address);
1878     else
1879       sim_printf("address  : any");
1880     return SCPE_OK;
1881   }
1882 
1883 static t_stat opc_set_console_pw (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1884                                     const char * cptr, UNUSED void * desc)
1885   {
1886     long dev_idx = (int) OPC_UNIT_IDX (uptr);
1887     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1888       return SCPE_ARG;
1889 
1890     if (cptr && (strlen(cptr) > 0))
1891       {
1892         char token[strlen (cptr)+1];
1893         int rc = sscanf (cptr, "%s", token);
1894         if (rc != 1)
1895           return SCPE_ARG;
1896         if (strlen (token) > PW_SIZE)
1897           return SCPE_ARG;
1898         strcpy (console_state[dev_idx].console_access.pw, token);
1899       }
1900     else
1901       {
1902         sim_msg ("no password\n");
1903         console_state[dev_idx].console_access.pw[0] = 0;
1904       }
1905 
1906     return SCPE_OK;
1907   }
1908 
1909 static t_stat opc_show_console_pw (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1910                                        UNUSED int val, UNUSED const void * desc)
1911   {
1912     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1913     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1914       return SCPE_ARG;
1915     sim_printf("password : %s", console_state[dev_idx].console_access.pw);
1916     return SCPE_OK;
1917   }
1918 
1919 static void console_putstr (int conUnitIdx, char * str)
     /* [previous][next][first][last][top][bottom][index][help] */
1920   {
1921     size_t l = strlen (str);
1922     for (size_t i = 0; i < l; i ++)
1923       sim_putchar (str[i]);
1924     opc_state_t * csp = console_state + conUnitIdx;
1925     if (csp->console_access.loggedOn)
1926       accessStartWrite (csp->console_access.client, str,
1927                            (ssize_t) l);
1928   }
1929 
1930 static void consolePutchar0 (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1931   opc_state_t * csp = console_state + conUnitIdx;
1932   sim_putchar (ch);
1933   if (csp->console_access.loggedOn)
1934     accessStartWrite (csp->console_access.client, & ch, 1);
1935 }
1936 
1937 static void console_putchar (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1938   opc_state_t * csp = console_state + conUnitIdx;
1939   if (csp->escapeSequence) { // Prior character was an escape
1940     csp->escapeSequence = false;
1941     if (ch == '1') { // Set tab
1942       if (csp->carrierPosition >= 1 && csp->carrierPosition <= 256) {
1943         csp->tabStops[csp->carrierPosition] = true;
1944       }
1945     } else if (ch == '2') { // Clear all tabs
1946       (void)memset (csp->tabStops, 0, sizeof (csp->tabStops));
1947     } else { // Unrecognized
1948       sim_warn ("Unrecognized escape sequence \\033\\%03o\r\n", ch);
1949     }
1950   } else if (isprint (ch)) {
1951     consolePutchar0 (conUnitIdx, ch);
1952     csp->carrierPosition ++;
1953   } else if (ch == '\t') {  // Tab
1954     while (csp->carrierPosition < bufsize - 1) {
1955       consolePutchar0 (conUnitIdx, ' ');
1956       csp->carrierPosition ++;
1957       if (csp->tabStops[csp->carrierPosition])
1958         break;
1959     }
1960   } else if (ch == '\b') { // Backspace
1961       consolePutchar0 (conUnitIdx, ch);
1962       csp->carrierPosition --;
1963   } else if (ch == '\f' || ch == '\r') { // Formfeed, Carriage return
1964       consolePutchar0 (conUnitIdx, ch);
1965       csp->carrierPosition = 1;
1966   } else if (ch == '\033') { //-V536  // Escape
1967       csp->escapeSequence = true;
1968   } else { // Non-printing and we don't recognize a carriage motion character, so just print it...
1969       consolePutchar0 (conUnitIdx, ch);
1970   }
1971 }
1972 
1973 static void consoleConnectPrompt (uv_tcp_t * client)
     /* [previous][next][first][last][top][bottom][index][help] */
1974   {
1975     accessStartWriteStr (client, "password: \r\n");
1976     uv_access * console_access = (uv_access *) client->data;
1977     console_access->pwPos      = 0;
1978   }
1979 
1980 void startRemoteConsole (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1981   {
1982     for (int conUnitIdx = 0; conUnitIdx < N_OPC_UNITS_MAX; conUnitIdx ++)
1983       {
1984         console_state[conUnitIdx].console_access.connectPrompt = consoleConnectPrompt;
1985         console_state[conUnitIdx].console_access.connected     = NULL;
1986         console_state[conUnitIdx].console_access.useTelnet     = true;
1987 #if defined(CONSOLE_FIX)
1988 # if defined(LOCKLESS)
1989         lock_libuv ();
1990 # endif
1991 #endif
1992         uv_open_access (& console_state[conUnitIdx].console_access);
1993 #if defined(CONSOLE_FIX)
1994 # if defined(LOCKLESS)
1995         unlock_libuv ();
1996 # endif
1997 #endif
1998       }
1999   }

/* [previous][next][first][last][top][bottom][index][help] */