/*****************************************************************************/ /* screper.c ... a terminal screen screper, er scréper, um scraper. HOW IT WORKS ------------ Getting the VT100-style terminal output of a "regular" command-line application onto a browser HTML page. The parent application (script) using an XMLHttpRequest() activates a wrapper for the routines in this module. This spawns a subprocess which initiates the commnd-line application and a screen of terminal output is generated. This output is parsed with VT100/ANSI cursor, character attributes, and other sequences, building a virtual, in-memory screen. This generates HTML markup to represent the screen in a browser. The HTML is streamed as a response to the XMLHttpRequest() with the "snapshot" representing each terminal screen. A stream is a series of time-based snapshots. The idea is that a screen is built from output fairly quickly, and may have a well-defined update rate. The snapshotter ticks ten times a second and when the virtual screen has not received new display data for the specified number of ticks a snapshot of that virtual screen (in HTML markup) is sent to the parent running in the browser. For example, the MONITOR update rate is roughly 6 seconds. So a snapshot value of one-tenth second ticks of 55 will snapshot after 5.5 seconds of MONITOR quiesence. Just enough not to update too frequently but not to update in the middle of new data being received. There is probably a sweetspot for each update interval and application. This time-based approach is not suitable for continuously or randomly updating screens, and congested systems can result in partial or broken displays. An alternative to a time-based update is to trigger a snapshot on the detection of unique octet sequence. For example, if the clear-screen CSI sequence is used to begin a fresh screen display then the octet sequence, 0x1b 0x5b 0x32 0x4a, then detecting this in the terminal output can be used to generate a snapshot before the output is sent to to scraper for processing. Such a sentinal must be terminated by 2 null characters. Multiple sentinals can be inside the string by separating each with a single null. Obviously a null character cannot be used as part of the sentinal. To be detected a sentinal must be self-contained in a single output record. Here is an example os a single sentinal string. uchar example [] = { 0x1b, 0x5b, 0x32, 0x4a, 0x00, 0x00 }; SCREPER DOES NOT PRETEND ------------------------ to be an exhaustive VT terminal interpreter. The development baseline application was the MONITOR utility. Screper seems to handle MONITOR with considerable aplomb. However, no little wonder that terminal emulator authors seldom get it 100% and quickly move on to something else. SCREPER API ----------- ScreperInit() returns a pointer to an opaque structure (void*). ScreperDone() deallocates memory used during processing and returns a NULL. ScreperDo() is supplied a string which is processed to set parameters and/or perform a DCL command. Directives must be lower case, include at least the first three characters, and have no white space between the directive, equate symbol, and value. Some can only be used standlone. A single or multiple calls may be made to set up the virtual terminal session. The final call must contain the DCL commands. -7bit terminal 7 bits -8bit terminal 8 bits (default) -dcl= DCL command(s) (to end of line) -device= e.g. value from ttdef.h (default TT$_VT100) -exit supply a ^Z to the DCL command (also see -input) -input="" supply this string as input to the command -inspect= inspect the data stream at various levels -page=[+] number of lines on page (24..255) -repeat= repeat every seconds -sentinal="" when output contains this it triggers a snapshot -snapshot= tenths of a second before snapshot (-n, 0, 5..600) -timestamp[=""] begin the display with a timestamp -utility="" name of the utility displayed with the pause button -width= width of line, 80 or 132 ... 255 chars (default 80) -css return style sheet (standalone) -javascript return JavaScript (standalone) -pause return HTML for a "pause all" button (standalone) -resize return JavaScript to resize an iframe (standalone) -screen return HTML providing virtual screen (standalone) -version return version as string (standalone) The -input string, along with the -sentinal and -timestamp strings, must be quotation character delimited, and can include control codes and other "exotic" characters using the ^xx escape syntax. So inputing a control-Z would be ^1a, a literal hat character ^3e, and literal quotation ^22. The -timestamp has a default VMS date-time. A format string may be supplied allowing VMS date/time formatting (e.g. "!8%T", "!20%D") or strftime() formatting (e.g. "%c", "%A, %B %d, %Y"). Other characters and HTML markup may lead and trail the string formatting but the characters '!' and '%' are considered reserved and introduce the format string. HTML-entify if required as literals. See example applications for usage. RESOURCES --------- https://vt100.net/ https://vt100.net/docs/vt100-ug/chapter3.html https://vt100.net/docs/vt510-rm/chapter7.html http://fileformats.archiveteam.org/wiki/DEC_Special_Graphics_Character_Set https://en.wikipedia.org/wiki/VT100_encoding COPYRIGHT --------- Copyright (C) 2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION LOG ----------- 01-AUG-2021 MGD initial */ /*****************************************************************************/ #define SOFTWAREVN "v1.0.0" #define SOFTWARENM "SCREPER" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP" #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64" #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "screper.h" #define FI_LI "SCREPER",__LINE__ #define DC$_TERM 6 #ifndef UINT64PTR /* mainly to allow easy use of the __unaligned directive */ #define UINTPTR __unaligned unsigned int* #define ULONGPTR __unaligned unsigned long* #define USHORTPTR __unaligned unsigned short* #define UINT64PTR __unaligned uint64* #define INT64PTR __unaligned int64* #endif /* Each cursor position in the virtual screen is 32 bits deep. 0 7 8 15 16 23 24 31 |................|................|................|................| | 8 bit char | SGR bits | G0 alpha | G1 alpha | (these two fields are extravagant) */ #define SGR_BOLD 0x00000100 #define SGR_UNDER 0x00000200 #define SGR_BLINK 0x00000400 #define SGR_REVER 0x00000800 #define SGR_RESET 0x00004000 #define SGR_G1 0x00008000 #define CHAR_MASK 0x000000ff #define SGR_MASK 0x0000ff00 #define G0_MASK 0x00ff0000 #define G1_MASK 0xff000000 #define MAX_PAGE 255 #define MAX_WIDTH 255 #define BUTTON_PAUSE "| |" #define BUTTON_UNPAUSE ">>" #define BUTTON_PAUSEALL "Pause All" #define BUTTON_UNPAUSEALL "Resume All" #define DELTA64_100_MSEC ((int64)-1000000) static int64 refreshTimer64 = DELTA64_100_MSEC; static char SoftwareId [] = SOFTWAREID; # define CSAVE_MAX 16 #pragma __member_alignment __save #pragma __member_alignment static struct ScreperStruct { int AnsiMode, CharTableG0, CharTableG1, CurSave, EfnRead, EtxDetected, EtxLength, FontSize, InputLength, InspectStream, LibSpawnStatus, PromptLength, PtdBits8, PtdDevice, PtdPageLength, PtdPagePlus, PtdPageWidth, PtdReadAst, PtdReadStatus, PtdWriteStatus, RepeatSeconds, ScreenBufferSize, ScreenCol, ScreenRow, SgrBlink, SgrBold, SgrEoj, SgrGrone, SgrReverse, SgrG1, SgrShot1, SgrUnder, SnapshotDataCount, SnapshotSentinalLength, SnapshotTickCount, SnapshotTicks, SpecGraphMode, StxDetected, StxLength, TimeStampLength, UtilityLength; int ColSave [CSAVE_MAX], RowSave [CSAVE_MAX], SgrSave [CSAVE_MAX]; ulong LibSpawnPid, ScriptJpiPid; ulong inadr [2]; ulong chbuf [3]; ulong *ScreenBuffer; ushort PtdChan; char *CssString, *NextWritePtr, *PtdReadBuffer, *PtdWriteBuffer, *RepeatCmdPtr; char CliPrompt [24], DclBuffer [512], EtxSentinal [24], InputBuffer [64], PrcNam [16], PtdDevName [64], ScriptPidString [16], SgrBuffer [128], SnapshotSentinal [64], StxSentinal [24], TimeStamp [64], Utility [64]; FILE *ScrOut; } ScreperStruct; #pragma __member_alignment __restore typedef struct ScreperStruct SCREPER_STRUCT; #define PTD_READ_SIZE 18 * 512 #define PTD_WRITE_SIZE 2 * 512 #define PTD_BUFFER_PAGES 20 /* private prototypes */ static void PtdDelete (SCREPER_STRUCT*); static int PtdDollarWrite (ushort, char*, int); static void PtdPoof (SCREPER_STRUCT*, int); static void PtdRead (SCREPER_STRUCT*); static int PtdSpawn (SCREPER_STRUCT*); static void PtdSpawnAst (SCREPER_STRUCT*); static int PtdWrite (SCREPER_STRUCT*); static char* ScreenChar (SCREPER_STRUCT*, ulong); static void ScreenCursor (SCREPER_STRUCT*, char*, ...); static void ScreenDump (SCREPER_STRUCT*, char*, int); static void ScreenIndex (SCREPER_STRUCT*, int*, int*); static void ScreenNextLine (SCREPER_STRUCT*, int*, int*); static void ScreenReverseIndex (SCREPER_STRUCT*, int*, int*); static char* ScreperBegin (SCREPER_STRUCT*); static void ScreperData (SCREPER_STRUCT*, char*, int); static void ScreperDump (SCREPER_STRUCT*, char*, int); static char* ScreperHtmlEncode (char*); static void ScreperKnown (SCREPER_STRUCT*, char*, char*, char*); static void ScreperMore (SCREPER_STRUCT*, char*, int); static void ScreperUnknown (SCREPER_STRUCT*, char*, char*, char*); static void ScreenOutput (SCREPER_STRUCT*); static char* ScreperPauseAll (); static char* ScreperScreen (); static void ScreenReport (SCREPER_STRUCT*, char*, ...); static void ScreenReset (SCREPER_STRUCT*); static void ScreenScrape (SCREPER_STRUCT*, char*, int); static int ScreenSentinal (SCREPER_STRUCT*, char*, int); static void ScreenSnapshot (SCREPER_STRUCT*); static void ScreenTimeStamp (SCREPER_STRUCT*); static char* ConfigString (char*, char*, int, int*); /*************/ /* code page */ /*************/ static char *CharTable_0_31 [32] = { ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." }; static char *CharTable_32_94 [63] = { " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^" }; static char *CharTable_95_127 [33] = { "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "" }; /* special graphics */ static char *CharTable_SG_95_127 [33] = { " ", "♦", "░", "⇥", "⇊", "⇥", "⇊", "°", "±", "↤", "↧", "┘", "┐", "┌", "└", "├", "─", "─", "─", "─", "─", "├", "┤", "┴", "┬", "│", "≤", "≥", "π", "≠", "£", "•" }; static char *CharTable_128_255 [128] = { "Ä", "Å", "Ç", "É", "Ñ", "Ö", "Ü", "á", "à", "â", "ä", "ã", "å", "ç", "é", "è", "Ý", "°", "¢", "£", "§", "¸", "¶", "ß", "®", "©", "™", "´", "¨", "≠", "Æ","Ø", "×", "±", "≤", "≥", "¥", "µ", "¹", "²", "³", "π", "¦", "ª", "º", "▒", "æ", "ø", "¿", "¡", "¬", "½", "ƒ", "¼", "¾", "«", "»", "…", "�", "À", "Ã", "Õ", "Œ", "œ", "–", "—", "┘", "┐", "┌", "└", "÷", "◆", "ÿ", "Ÿ", "┼", "€", "Ð", "ð", "Þ", "þ", "ý", "·", "⎺", "⎻", "─", "Â", "Ê", "Á", "Ë", "È", "Í", "Î", "Ï", "Ì", "Ó", "Ô", "", "Ò", "Ú", "Û", "Ù", "⎼", "⎽", "├", "ড়", "┴", "┬", "│", "", "", "", "" }; /**************/ /* JavaScript */ /**************/ static char PageJavaScript [] = "\ \n"; /* ~~~~~~~~~~~~~~~~~~~~ Iframe meta characteristics can only be managed via the parent page where multiple terminals are displayed. */ static char *ResizeJavaScript = "\n"; /* ~~~~~~~~~~~~~~~~~~~~ Pause-all button on parent page used where multiple terminals displayed. */ static char *ButtonPauseAll = "\n\ \n"; /*******/ /* CSS */ /*******/ static char PageCSS [] = "\ \n\ "; /*****************************************************************************/ /* Allocate the structure and set some defaults. */ void* ScreperInit () { SCREPER_STRUCT *scrptr; /*********/ /* begin */ /*********/ scrptr = calloc (1, sizeof(SCREPER_STRUCT)); if (!scrptr) exit (vaxc$errno); scrptr->PtdBits8 = TT$M_EIGHTBIT; scrptr->PtdDevice = TT$_VT100; scrptr->PtdPageLength = 24; scrptr->PtdPageWidth = 80; scrptr->UtilityLength = sprintf (scrptr->Utility, "-utility"); scrptr->ScrOut = fdopen (dup (fileno (stdout)), "w"); return ((void*)scrptr); } /*****************************************************************************/ /* Release resources. */ void* ScreperDone (void *vscrptr) { SCREPER_STRUCT *scrptr; scrptr = (SCREPER_STRUCT*)vscrptr; if (scrptr->ScreenBuffer) free (scrptr->ScreenBuffer); fflush (scrptr->ScrOut); fclose (scrptr->ScrOut); free (scrptr); return (NULL); } /*****************************************************************************/ /* Parse the string of screper directives to set up the virtual terminal session. When the -dcl directive is encountered the rest of the string is considered and the terminal session is created and the DCL executed. Returns a pointer to a string which should begin with a DCL status value (e.g. %X00000001). */ char* ScreperDo (void *vscrptr, char *this) { int status; char *aptr, *cptr, *sptr, *zptr; SCREPER_STRUCT *scrptr; /*********/ /* begin */ /*********/ if (!strncmp (this, "-javascript", 4)) return (PageJavaScript); if (!strncmp (this, "-css", 4)) return (PageCSS); if (!strncmp (this, "-pause", 4)) return (ButtonPauseAll); if (!strncmp (this, "-resize", 4)) return (ResizeJavaScript); if (!strncmp (this, "-screen", 4)) return (ScreperScreen (scrptr)); if (!strncmp (this, "-version", 4)) return (SoftwareId); scrptr = (SCREPER_STRUCT*)vscrptr; if (!scrptr) return ("%X00000014 hurrump"); aptr = strdup (this); while (*aptr) { while (isspace(*aptr)) aptr++; cptr = aptr; while (*aptr && *aptr != '=' && !isspace(*aptr)) aptr++; if (*aptr == '=') aptr++; if (!strncmp (cptr, "-7bit", 4)) { scrptr->PtdBits8 = 0; while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-8bit", 4)) { scrptr->PtdBits8 = TT$M_EIGHTBIT; while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-dcl", 4)) { zptr = (sptr = scrptr->DclBuffer) + sizeof(scrptr->DclBuffer)-1; while (*aptr && sptr < zptr) *sptr++ = *aptr++; *sptr = '\0'; sptr = ScreperBegin (scrptr); return (sptr); } if (!strncmp (cptr, "-device", 4)) { scrptr->PtdDevice = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-exit", 4)) { scrptr->InputBuffer[0] = 0x1a; scrptr->InputLength = 1; while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-font", 4)) { scrptr->FontSize = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-input", 4)) { while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; aptr = ConfigString (cptr, scrptr->InputBuffer, sizeof(scrptr->InputBuffer), &scrptr->InputLength); continue; } if (!strncmp (cptr, "-inspect", 4)) { scrptr->InspectStream = atoi (aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-page", 4)) { if (*aptr == '+') scrptr->PtdPageLength = scrptr->PtdPagePlus = atoi(++aptr); else scrptr->PtdPageLength = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-repeat", 4)) { scrptr->RepeatSeconds = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-sentinal", 4)) { while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; aptr = ConfigString (cptr, scrptr->SnapshotSentinal, sizeof(scrptr->SnapshotSentinal), &scrptr->SnapshotSentinalLength); continue; } if (!strncmp (cptr, "-snapshot", 4)) { scrptr->SnapshotTicks = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } if (!strncmp (cptr, "-timestamp", 4)) { while (*cptr && *cptr != '=' && !isspace(*cptr)) cptr++; if (*cptr == '=') { if (*cptr) cptr++; aptr = ConfigString (cptr, scrptr->TimeStamp, sizeof(scrptr->TimeStamp), &scrptr->TimeStampLength); } if (!scrptr->TimeStamp[0]) strcpy (scrptr->TimeStamp, "!20%D

"); continue; } if (!strncmp (cptr, "-utility", 4)) { while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; aptr = ConfigString (cptr, scrptr->Utility, sizeof(scrptr->Utility), &scrptr->UtilityLength); continue; } if (!strncmp (cptr, "-width", 4)) { scrptr->PtdPageWidth = atoi(aptr); while (*aptr && !isspace(*aptr)) aptr++; continue; } while (*aptr && !isspace(*aptr)) aptr++; *aptr = '\0'; sptr = calloc (1, aptr - cptr + 32); sprintf (sptr, "%%X00000014 Unknown: %s", cptr); return (sptr); } return ("%X00000001"); } /*****************************************************************************/ /* Validate the terminal session charactersitics and execute the DCL. Return a string beginning with a VMS status (e.g. %X00000001). */ static char* ScreperBegin (SCREPER_STRUCT *scrptr) { static char sbuf [64]; int status; char *cptr; /*********/ /* begin */ /*********/ if (!(scrptr->PtdDevice >= TT$_VT100 && scrptr->PtdDevice <= TT$_VT132) && !(scrptr->PtdDevice >= TT$_LA36 && scrptr->PtdDevice <= TT$_LA210)) return ("%X00000014 device"); if (scrptr->TimeStamp[0]) scrptr->PtdPageLength += 2; if (scrptr->PtdPageLength < 24 || scrptr->PtdPageLength > MAX_PAGE) return ("%X00000014 page length (24..255)"); if (scrptr->PtdPageWidth < 80 || scrptr->PtdPageWidth > MAX_WIDTH) return ("%X00000014 page width (80, 132, ..255)"); if (scrptr->PtdBits8 && scrptr->PtdBits8 != TT$M_EIGHTBIT) return ("%X00000014 data bits (7, 8)"); if (scrptr->RepeatSeconds < 0) scrptr->RepeatSeconds = -scrptr->RepeatSeconds; if (scrptr->SnapshotTicks) { if (scrptr->RepeatSeconds) return ("%X00000014 cannot repeat snapshots"); if (scrptr->SnapshotTicks && (scrptr->SnapshotTicks < 5 || scrptr->SnapshotTicks > 600)) return ("%X00000014 1/10 second (5..600)"); } if (!scrptr->DclBuffer[0]) return ("%X00000014 requires DCL command(s)"); if (!scrptr->InspectStream) if (cptr = getenv ("SCREPER_INSPECT")) scrptr->InspectStream = atol (cptr); scrptr->AnsiMode = 1; status = PtdSpawn (scrptr); sprintf (sbuf, "%%X%08.08X", status); return (sbuf); } /*****************************************************************************/ /* Create a pseudo-terminal driver terminal session, spawn a process attached to that session, and write CLI commands to that process. */ static int PtdSpawn (SCREPER_STRUCT *scrptr) { static ulong DevNamItem = DVI$_DEVNAM; static ulong JpiMasterPidItem = JPI$_MASTER_PID; /* nowait nolognam noclisym nokeypad nocontrol */ static unsigned long flags = 0x6f; $DESCRIPTOR (PromptDsc, scrptr->CliPrompt); $DESCRIPTOR (DevNameDsc, scrptr->PtdDevName); $DESCRIPTOR (PrcNamDsc, scrptr->PrcNam); int status; uint64 time64; ushort slen; ulong JpiMasterPidPid; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (scrptr->InspectStream) fprintf (scrptr->ScrOut, "PtdSpawn()\n"); sys$gettim (&time64); scrptr->EtxLength = sprintf (scrptr->EtxSentinal, "!!ETX%11.11llx", time64); scrptr->StxLength = sprintf (scrptr->StxSentinal, "!!STX%11.11llx", time64); scrptr->PromptLength = sprintf (scrptr->CliPrompt, "%14.14llx$$", time64); status = lib$getjpi (&JpiMasterPidItem, 0, 0, &JpiMasterPidPid, 0, 0); if (!(status & 1)) return (status); status = lib$get_ef (&scrptr->EfnRead); if (!(status & 1)) return (status); /* if not trying to REstart the subprocess */ if (!scrptr->inadr[0]) { status = sys$expreg (PTD_BUFFER_PAGES, &scrptr->inadr, 0, 0); if (!(status & 1)) return (status); scrptr->PtdReadBuffer = (char*)scrptr->inadr[0]; scrptr->PtdWriteBuffer = (char*)scrptr->inadr[0] + PTD_READ_SIZE; /* set the terminal characteristics */ scrptr->chbuf[0] = (scrptr->PtdPageWidth << 16) | (scrptr->PtdDevice << 8) | DC$_TERM; scrptr->chbuf[1] = (scrptr->PtdPageLength << 24) | scrptr->PtdBits8 | TT$M_SCOPE | TT$M_WRAP | TT$M_MECHTAB | TT$M_LOWER | TT$M_TTSYNC; scrptr->chbuf[2] = TT2$M_EDIT | TT2$M_HANGUP | TT2$M_ANSICRT | TT2$M_AVO | TT2$M_DECCRT | TT2$M_DECCRT2; } memset ((char*)scrptr->inadr[0], 0, (char*)scrptr->inadr[1] - (char*)scrptr->inadr[0]); scrptr->PtdReadStatus = scrptr->LibSpawnPid = scrptr->LibSpawnStatus = scrptr->PtdWriteStatus = 0; status = ptd$create (&scrptr->PtdChan, 0, scrptr->chbuf, sizeof(scrptr->chbuf), 0, 0, 0, &scrptr->inadr); if (!(status & 1)) return (status); status = lib$getdvi (&DevNamItem, &scrptr->PtdChan, 0, 0, &DevNameDsc, &slen); scrptr->PtdDevName[DevNameDsc.dsc$w_length = slen] = '\0'; if (!(status & 1)) return (status); sprintf (scrptr->PrcNam, "screper_%4.4X", JpiMasterPidPid & 0xffff); PrcNamDsc.dsc$a_pointer = scrptr->PrcNam; PrcNamDsc.dsc$w_length = strlen(scrptr->PrcNam); PromptDsc.dsc$w_length = scrptr->PromptLength; status = lib$spawn (0, &DevNameDsc, &DevNameDsc, &flags, &PrcNamDsc, &scrptr->LibSpawnPid, &scrptr->LibSpawnStatus, 0, &PtdSpawnAst, scrptr, &PromptDsc, 0, 0); if (!(status & 1)) return (status); for (int cnt = 10; cnt; cnt--) { sleep (1); if (scrptr->LibSpawnPid) break; } for (;;) { status = PtdWrite (scrptr); if (!(status && 1)) break; PtdRead (scrptr); /* wait for the subprocess output to finish */ sys$hiber(); if (!scrptr->RepeatSeconds) break; ScreenReset (scrptr); /* repeat every so many seconds */ sleep (scrptr->RepeatSeconds); } return (status); } /*****************************************************************************/ /* Subprocess self-destruct. */ static void PtdPoof (SCREPER_STRUCT *scrptr, int status) { ulong pid; /*********/ /* begin */ /*********/ PtdDelete (scrptr); if (!scrptr->SnapshotTicks) return; if (!(pid = scrptr->LibSpawnPid)) return; scrptr->LibSpawnPid = 0; sys$forcex (&pid, 0, status); } /*****************************************************************************/ /* Delete the pseudo-terminal. */ static void PtdDelete (SCREPER_STRUCT *scrptr) { /*********/ /* begin */ /*********/ if (scrptr->PtdChan) ptd$delete (scrptr->PtdChan); scrptr->PtdChan = 0; } /*****************************************************************************/ /* Subpocess completion AST. */ static void PtdSpawnAst (SCREPER_STRUCT *scrptr) { /*********/ /* begin */ /*********/ if (scrptr->PtdChan) ptd$delete (scrptr->PtdChan); scrptr->LibSpawnPid = 0; sys$wake (0, 0); } /*****************************************************************************/ /* Expects a string containing newline separated, multiple DCL commands, and writes them successively to the subprocess' SYS$INPUT. Any preceding the final command are considered "setup" commands and not scraped. Uses two sentinals to delineate the start (STX) and end (ETX) of command output. These are detected by PtdRead(). Immediately before the final DCL command (i.e. not '\n' terminated) the STX terminal is written as a commented "command", is echoed and detectible by PtdRead(). When detected, the next read will be from the final command and therefore valid data for screper to begin parsing. Immediately after the final command the ETX sentinal is similarly written as a commented "command" to disable screen scraping. */ static int PtdWrite (SCREPER_STRUCT *scrptr) { int bcnt, status; char *bptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ zptr = (bptr = scrptr->PtdWriteBuffer) + PTD_WRITE_SIZE; sptr = bptr + sizeof(ulong); if (scrptr->RepeatCmdPtr) scrptr->NextWritePtr = scrptr->RepeatCmdPtr; else { scrptr->NextWritePtr = scrptr->DclBuffer; for (cptr = "ON ERROR THEN EXIT\r"; *cptr && sptr < zptr; *sptr++ = *cptr++); } for (;;) { for (cptr = scrptr->NextWritePtr; *cptr && *cptr != '\n'; cptr++); while (*cptr == '\n') cptr++; if (!*cptr) { /* final command, start-of-text */ for (cptr = scrptr->StxSentinal; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\r'; } cptr = scrptr->RepeatCmdPtr = scrptr->NextWritePtr; /* copy CLI command compressing to just */ while (*cptr && *cptr != '\n' && sptr < zptr) if (*(USHORTPTR)cptr == '\r\n') cptr++; else *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\r'; while (*cptr == '\n') cptr++; scrptr->NextWritePtr = cptr; if (!*cptr) break; } /* any -input or -exit */ for (bcnt = 0; bcnt < scrptr->InputLength && sptr < zptr; bcnt++) *sptr++ = scrptr->InputBuffer[bcnt]; /* end-of-text */ for (cptr = scrptr->EtxSentinal; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\r'; if (!scrptr->RepeatSeconds) { /* ensure subprocess exits */ for (cptr = "EOJ\r"; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (sptr >= zptr) { status = SS$_RESULTOVF; PtdPoof (scrptr, status); return (status); } *sptr = '\0'; bcnt = sptr - bptr - sizeof(ulong); if (scrptr->InspectStream) fprintf (scrptr->ScrOut, "%s\n", bptr + sizeof(ulong)); /* synchronous write */ status = ptd$write (scrptr->PtdChan, 0, 0, bptr, bcnt, 0, 0); if (scrptr->InspectStream) { fprintf (scrptr->ScrOut, "%%X%08.08X %%X%08.08X %d\n", status, *(USHORTPTR)bptr, *(USHORTPTR)(bptr+2)); fflush (scrptr->ScrOut); } fprintf (scrptr->ScrOut, ""); fflush (scrptr->ScrOut); if (!(status & 1)) PtdPoof (scrptr, status); fprintf (scrptr->ScrOut, ""); fflush (scrptr->ScrOut); return (status); } /*****************************************************************************/ /* Synchronous write direct to the PTD returing a VMS status value. Record should be terminated with a carriage-return. */ static int PtdDollarWrite (ushort chan, char* bptr, int bcnt) { int status; /*********/ /* begin */ /*********/ if (!bptr || !bcnt) return (SS$_BUGCHECK); status = ptd$write (chan, 0, 0, bptr, bcnt, 0, 0); return (status); } /*****************************************************************************/ /* Read subprocess SYS$OUTPUT records. */ static void PtdRead (SCREPER_STRUCT *scrptr) { int bcnt, status; char *bptr, *cptr, *czptr; /*********/ /* begin */ /*********/ if (scrptr->PtdReadAst) { scrptr->PtdReadAst = 0; bptr = scrptr->PtdReadBuffer; scrptr->PtdReadStatus = *(USHORTPTR)bptr; if (!(scrptr->PtdReadStatus & 1)) PtdPoof (scrptr, scrptr->PtdReadStatus); bptr += sizeof(ushort); bcnt = *(USHORTPTR)bptr; bptr += sizeof(ushort); /* ensure the read looks nul-terminated */ *(USHORTPTR)(bptr + bcnt) = 0; if (scrptr->InspectStream >= 4) { fprintf (scrptr->ScrOut, "PtdRead(%d)\n", bcnt); ScreperData (scrptr, bptr, bcnt); } if (!scrptr->StxDetected) { /* detect the screen start sentinal */ czptr = (cptr = bptr) + bcnt; while (*(ULONGPTR)cptr != '$$!!' && cptr < czptr) cptr++; if (cptr < czptr) if (!memcmp (cptr+2, scrptr->StxSentinal, scrptr->StxLength)) scrptr->StxDetected++; } else { /* the first output after detection ..... */ scrptr->StxDetected++; /* look for any take-snapshot sentinal string */ if (scrptr->SnapshotSentinalLength) if (ScreenSentinal (scrptr, bptr, bcnt)) ScreenScrape (scrptr, NULL, 0); } if (scrptr->StxDetected > 2) { /* ..... is the final command echo */ if (bcnt < 48) { /* detect the end of output sentinal */ czptr = (cptr = bptr) + bcnt; while (*(ULONGPTR)cptr != '$$!!' && cptr < czptr) cptr++; if (cptr < czptr) if (!memcmp (cptr+2, scrptr->EtxSentinal, scrptr->EtxLength)) { scrptr->EtxDetected = 1; /* let the parent script know */ if (!scrptr->RepeatSeconds) fputs ("", scrptr->ScrOut); ScreenOutput (scrptr); fflush (scrptr->ScrOut); /* wake the hibernating PtdSpawn() */ sys$wake(0,0); return; } } ScreenScrape (scrptr, bptr, bcnt); } } /* asynchronous read */ scrptr->PtdReadAst = 1; status = ptd$read (scrptr->EfnRead, scrptr->PtdChan, PtdRead, scrptr, scrptr->PtdReadBuffer, PTD_READ_SIZE-(sizeof(ulong))); if (!(status & 1)) PtdPoof (scrptr, status); } /*****************************************************************************/ /* Process output from the pseudo-terminal session. The virtual terminal has a virtual cursor positioned by the current values of /row/ and /col/ variables. Columns are numbered 0..width-1 and rows 0..page-1. Each cursor position in the virtual screen is 32 bits deep. 0 7 8 15 16 23 24 31 |................|................|................|................| | 8 bit char | SGR bits | G0 alpha | G1 alpha | (these two fields are extravagant) Function returns true to continue scraping or false to conclude program. */ static void ScreenScrape (SCREPER_STRUCT *scrptr, char *record, int length) { #define MAX_PN 10 int ch, cnt, ccnt, col, inspect, nlcnt, page, pncnt, rcnt, row, size, tab, width; int pn [MAX_PN]; ulong value; ulong *screen; char *cptr, *czptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (!scrptr->ScreenBuffer) { /* initialise */ scrptr->ScreenBufferSize = scrptr->PtdPageLength * scrptr->PtdPageWidth * sizeof(ulong); scrptr->ScreenBuffer = (ulong*)calloc (1, scrptr->ScreenBufferSize); if (!scrptr->ScreenBuffer) exit (vaxc$errno); scrptr->CharTableG0 = 'B' << 16; scrptr->CharTableG1 = '0' << 24; scrptr->ScreenRow = scrptr->ScreenCol = 0; scrptr->CurSave = -1; /* kick off the ticker */ if (scrptr->SnapshotTicks) ScreenSnapshot (scrptr); } if (scrptr->InspectStream >= 4) ScreenDump (scrptr, "BEFORE", __LINE__); /* get local copies of structure data */ inspect = scrptr->InspectStream; screen = scrptr->ScreenBuffer; page = scrptr->PtdPageLength; width = scrptr->PtdPageWidth; row = scrptr->ScreenRow; col = scrptr->ScreenCol; if (inspect) { fprintf (scrptr->ScrOut, "ScreenScrape(%d) %dx%d\n", length, width, page); ScreperData (scrptr, record, length); } czptr = (cptr = record) + length; /* let the ticker know data has been received */ scrptr->SnapshotDataCount += length; if (scrptr->SnapshotTicks) scrptr->SnapshotTickCount = scrptr->SnapshotTicks; /************/ /* populate */ /************/ while (cptr < czptr) { if ((*cptr >= 32 && *cptr < 127) || (*cptr >= 160 && *cptr <= 255)) { /*************/ /* printable */ /*************/ /* replace leading nuls with with spaces */ for (ccnt = col-1; ccnt >= 0; ccnt--) if (!screen[(row*width)+ccnt]) screen[(row*width)+ccnt] = ' '; zptr = cptr; while (cptr < czptr && ((*cptr >= 32 && *cptr < 127) || (*cptr >= 160 && *cptr <= 255))) { value = *cptr++; if (scrptr->SgrBold) value |= SGR_BOLD; if (scrptr->SgrBlink) value |= SGR_BLINK; if (scrptr->SgrUnder) value |= SGR_UNDER; if (scrptr->SgrReverse) value |= SGR_REVER; if (scrptr->SgrG1) value |= SGR_G1; value |= scrptr->CharTableG0; value |= scrptr->CharTableG1; /* insert this character */ screen[(row*width)+col] = value; if (col < width) col++; } if (inspect) ScreperDump (scrptr, zptr, cptr - zptr); continue; } if ((*(USHORTPTR)cptr == '\x1b[') || /* 7 bit CSI */ (*cptr == '\x9b')) /* 8 bit CSI */ { /*******/ /* CSI */ /*******/ if (scrptr->InspectStream >= 4) ScreenDump (scrptr, FI_LI); if (*(USHORTPTR)cptr == '\x1b[') cptr += 2; else cptr++; if (inspect) ScreperKnown (scrptr, "CSI+", cptr, czptr); pncnt = 0; pn[pncnt++] = atoi(cptr); while (isdigit(*cptr) && cptr < czptr) cptr++; while (*cptr == ';') { if (cptr >= czptr) break; cptr++; pn[pncnt++] = atoi(cptr); if (pncnt >= MAX_PN) break; while (isdigit(*cptr) && cptr < czptr) cptr++; } if (cptr >= czptr) break; ch = *cptr++; if (ch == 'm') { /*******/ /* SGR */ /*******/ for (cnt = 0; cnt < pncnt; cnt++) { if (pn[cnt] == 0) scrptr->SgrBlink = scrptr->SgrBold = scrptr->SgrReverse = scrptr->SgrUnder = 0; else if (pn[cnt] == 1) scrptr->SgrBold = 1; else if (pn[cnt] == 4) scrptr->SgrUnder = 1; else if (pn[cnt] == 5) scrptr->SgrBlink = 1; else if (pn[cnt] == 7) scrptr->SgrReverse = 1; if (inspect) printf ("SGR " "%dx%d %d\n", row, col, pn[cnt]); } continue; } if (ch >= 'A' && ch <= 'G') { /********/ /* move */ /********/ if (ch == 'A') { /* move cursor up */ if (row - pn[0] >= 0) row -= pn[0]; } else if (ch == 'B') { /* move cursor down */ if (row + pn[0] < page) row += pn[0]; } else if (ch == 'C') { /* move cursor right */ if (col + pn[0] < width) col += pn[0]; } else if (ch == 'D') { /* move cursor left */ if (col - pn[0] >= 0) col -= pn[0]; } else if (ch == 'E') { /* moves cursor to beginning of lines down */ if (row + pn[0] < page) { row += pn[0]; col = 0; } } else if (ch == 'F') { /* moves cursor to beginning of lines up */ if (row + pn[0] >= 0) { row += pn[0]; col = 0; } } else if (ch == 'G') { /* move cursor to col */ if (pn[0] >= 0 && pn[0] < width) col = pn[0]; } if (inspect) ScreenCursor (scrptr, "ROWxCOL %dx%d", row, col); continue; } if (ch == 'H' || ch == 'f') { /*********/ /* go to */ /*********/ /* row and col */ /* our emulation has cols 0..width-1 and rows 0..page-1 */ if (pn[0]) pn[0]--; if (pn[1]) pn[1]--; if (pn[0] < page) row = pn[0]; else row = page-1; if (pn[1] < width) col = pn[1]; else col = width-1; if (inspect) ScreenCursor (scrptr, "ROWxCOL %dx%d", row, col); continue; } if (ch == 'J') { /*********/ /* erase */ /*********/ if (pn[0] == 0) { /* clear from current to end of screen */ for (ccnt = col; ccnt < width; ccnt++) screen[(row*width)+ccnt] = 0; /* then to end of screen */ for (rcnt = row+1; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE EOL %dx%d", row, col); continue; } if (pn[0] == 1) { /* clear start of screen to current */ for (rcnt = 0; rcnt <= row; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; /* then clear from start of row up until the current col */ for (ccnt = 0; ccnt <= col; ccnt++) screen[(row*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE SOS %dx%d", row, col); continue; } if (pn[0] == 2) { /* clear screen */ for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE CLS %dx%d", row, col); continue; } continue; } if (ch == 'K') { /*********/ /* clear */ /*********/ if (pn[0] == 0) { /* clear to end of line */ for (ccnt = col; ccnt < width; ccnt++) screen[(row*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE %dx%d EOL", row, col); continue; } if (pn[0] == 1) { /* clear from start of line to current */ for (ccnt = 0; ccnt <= col; ccnt++) screen[(row*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE %dx%d SOL", row, col); continue; } if (pn[0] == 2) { /* clear all lines */ for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; if (inspect) ScreenCursor (scrptr, "ERASE %dx%d CLS", row, col); continue; } continue; } if (ch == 'c') { /****************/ /* what are you */ /****************/ PtdDollarWrite (scrptr->PtdChan, "\x1b[?1;0c", 7); continue; } if (ch == 'n') { /*****************/ /* status report */ /*****************/ if (pn[0] == 0 || pn[0] == 5) PtdDollarWrite (scrptr->PtdChan, "\x1b[0n", 4); else if (pn[0] == 6) { char buf [32]; int len = sprintf (buf, "%c[%d;%dR", '\x1b', row+1, col+1); PtdDollarWrite (scrptr->PtdChan, buf, len); } else PtdDollarWrite (scrptr->PtdChan, "\x1b[3n", 4); continue; } if (ch == 's' || ch == 'u') { /**********/ /* cursor */ /**********/ if (ch == 's') { /* save cursor */ if (scrptr->CurSave < CSAVE_MAX) { scrptr->CurSave++; scrptr->RowSave[scrptr->CurSave] = row; scrptr->ColSave[scrptr->CurSave] = col; } continue; } if (ch == 'u') { /* restore cursor */ if (scrptr->CurSave >= 0) { row = scrptr->RowSave[scrptr->CurSave]; col = scrptr->ColSave[scrptr->CurSave]; scrptr->CurSave--; } continue; } /* drop through */ } if (*(USHORTPTR)cptr == '20') { /***********************/ /* line feed / newline */ /***********************/ cptr += 2; if (*cptr == 'h' || *cptr == 'l') { cptr++; ScreenNextLine (scrptr, &row, &col); continue; } } if (ch == 'r') { /********************/ /* scrolling region */ /********************/ /* ignore */ if (inspect) ScreenCursor (scrptr, "SCROLL %dx%d", pn[0] ? pn[0] : pn[0]+1, pn[1]); continue; } if (*cptr == '?') { /************/ /* VT modes */ /************/ /* ignore */ if (inspect) ScreperKnown (scrptr, "VT", cptr+1, czptr); continue; } /* unknown CSI */ if (inspect) ScreperUnknown (scrptr, "CSI?", cptr, czptr); continue; } if (*cptr == '\x1b' || /* 7 bit */ *cptr == '\x9b') /* 8 bit */ { /*******/ /* ESC */ /*******/ cptr++; ch = *cptr; cptr++; if (ch == '<') { /* enter ANSI mode */ scrptr->AnsiMode = 1; continue; } if (ch == '\\') { /* 7 bit string terminator */ continue; } if (ch == '=' || ch == '>') { /* keypad mode */ continue; } if (ch == 'Z') { /* what are you */ PtdDollarWrite (scrptr->PtdChan, "\x1b[?1;0c", 7); continue; } if (scrptr->AnsiMode) { /*************/ /* ANSI mode */ /*************/ if (ch == 'D') { /* IND Index */ ScreenIndex (scrptr, &col, &row); continue; } if (ch == 'E') { /* NEL Next Line (newline equiv) */ ScreenNextLine (scrptr, &row, &col); continue; } if (ch == 'M') { /* TI Reverse Index */ ScreenReverseIndex (scrptr, &row, &col); continue; } if (ch == '7') { /* save cursor */ if (scrptr->CurSave < CSAVE_MAX) { scrptr->CurSave++; scrptr->RowSave[scrptr->CurSave] = row; scrptr->ColSave[scrptr->CurSave] = col; scrptr->SgrSave[scrptr->CurSave] = screen[(rcnt*width)+ccnt] & SGR_MASK; screen[(rcnt*width)+ccnt] &= ~SGR_MASK; } continue; } if (ch == '8') { /* restore cursor */ if (scrptr->CurSave >= 0) { row = scrptr->RowSave[scrptr->CurSave]; col = scrptr->ColSave[scrptr->CurSave]; screen[(rcnt*width)+ccnt] &= ~SGR_MASK; screen[(rcnt*width)+ccnt] |= scrptr->SgrSave[scrptr->CurSave]; scrptr->CurSave--; } continue; } } /*****************/ /* non-printable */ /*****************/ if (ch == '\x9c') { /* 8 bit string terminator */ continue; } if (ch == '6') { /* back index */ continue; } if (ch == '9') { /* forward index */ continue; } if (ch == 'D') { /* index */ continue; } if (ch == 'c') { /* RIS reset */ continue; } if (ch == 'm') { /* reverse Index */ continue; } if (ch == '\\') { /* string terminator */ continue; } if (ch == '(') { /* G0 character set */ if (*cptr == 'A' || *cptr == 'B' || *cptr == '0' || *cptr == '1' || *cptr == '2') scrptr->CharTableG0 = (uchar)*cptr << 16; cptr++; continue; } if (ch == ')') { /* G1 character set */ if (*cptr == 'A' || *cptr == 'B' || *cptr == '0' || *cptr == '1' || *cptr == '2') scrptr->CharTableG1 = (uchar)*cptr << 24; cptr++; continue; } /* unknown ESC - span intervening chars to the next escape */ if (inspect) ScreperUnknown (scrptr, "ESC?", cptr, czptr); while (cptr < czptr) { if (*cptr == '\x1b' || *cptr == '\x9b') break; cptr++; } continue; } /***********/ /* control */ /***********/ if (*cptr == '\r') { cptr++; col = 0; continue; } if (*cptr == '\b') { cptr++; if (col > 0) col--; continue; } if (*cptr == '\n' || *cptr == '\f' || *cptr == '\v') { cptr++; ScreenNextLine (scrptr, &row, &col); continue; } if (*cptr == '\x0e') { /* SO Invoke G1 character set */ cptr++; if (inspect) { fprintf (scrptr->ScrOut, "G1 %dx%d ", row, col); ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr); fputs ("\n", scrptr->ScrOut); } scrptr->SgrG1 = SGR_G1; continue; } if (*cptr == '\x0f') { /* SI Invoke G0 character set */ cptr++; if (inspect) { fprintf (scrptr->ScrOut, "G0 %dx%d ", row, col); ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr); fputs ("\n", scrptr->ScrOut); } scrptr->SgrG1 = 0; continue; } if (*cptr == '\v') { if (row+1 < page) row++; cptr++; continue; } if (*cptr == '\t') { for (tab = 8; tab > 0; tab--) { // screen[(row*width)+col] = ' '; if (col+1 < width) col++; } cptr++; continue; } /* ignore this control char */ cptr++; if (row > page || col > width) ScreenReport (scrptr, "BUGCHECK: %d 0x%02x terminal %dx%d screen %dx%d\n", cptr - record, *cptr, width, page, col, row); } if (scrptr->InspectStream >= 4) ScreenDump (scrptr, "AFTER", __LINE__); /* return local copies to strcuture data */ scrptr->ScreenRow = row; scrptr->ScreenCol = col; } /*****************************************************************************/ /* Moves the cursor to the first position on the next line. If the cursor is at the bottom margin, the page scrolls up. */ static void ScreenNextLine (SCREPER_STRUCT *scrptr, int *rowptr, int *colptr) { /*********/ /* begin */ /*********/ ScreenIndex (scrptr, rowptr, colptr); *colptr = 0; } /*****************************************************************************/ /* Moves the cursor down one line in the same column. If the cursor is at the bottom margin, the page scrolls up. */ static void ScreenIndex (SCREPER_STRUCT *scrptr, int *rowptr, int *colptr) { int ccnt, col, page, rcnt, row, width; ulong *screen; /*********/ /* begin */ /*********/ screen = scrptr->ScreenBuffer; page = scrptr->PtdPageLength; width = scrptr->PtdPageWidth; row = *rowptr; col = *colptr; if (row+1 < page) row++; else { /* move all lines up one then clear the bottom row */ for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = screen[((rcnt+1)*width)+ccnt]; for (ccnt = 0; ccnt < width; ccnt++) screen[(row*width)+ccnt] = 0; } *rowptr = row; *colptr = col; } /*****************************************************************************/ /* Moves the cursor up one line in the same column. If the cursor is at the top margin, the page scrolls down. */ static void ScreenReverseIndex (SCREPER_STRUCT *scrptr, int *rowptr, int *colptr) { int ccnt, col, page, rcnt, row, width; ulong *screen; /*********/ /* begin */ /*********/ screen = scrptr->ScreenBuffer; page = scrptr->PtdPageLength; width = scrptr->PtdPageWidth; row = *rowptr; col = *colptr; if (row-1 >= 0) row--; else { /* move all lines down one then clear the top row */ for (rcnt = 0; rcnt+1 < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = screen[((rcnt+1)*width)+ccnt]; for (ccnt = row = 0; ccnt < width; ccnt++) screen[(row*width)+ccnt] = 0; } *rowptr = row; *colptr = col; } /*****************************************************************************/ /* Scanning from row 1, column 1, to row /n/, column /n/ output the HTML equivalent of the virtual terminal. Only output leading spaces if followed by a non-space character. Count empty lines and only output if followed by a non-empty line (so absorbing trailing empty lines). Each 64 bit character/attributes is is turned into an HTML string using ScreenChar(). */ static void ScreenOutput (SCREPER_STRUCT *scrptr) { int ccnt, col, inspect, nlcnt, page, rcnt, row, spacnt, width, valcnt; ulong value; ulong *screen; /*********/ /* begin */ /*********/ /* if no more data to display since last time */ if (!scrptr->SnapshotDataCount) return; scrptr->SnapshotDataCount = 0; /* get local copies of structure data */ inspect = scrptr->InspectStream; screen = scrptr->ScreenBuffer; page = scrptr->PtdPageLength; width = scrptr->PtdPageWidth; row = scrptr->ScreenRow; col = scrptr->ScreenCol; if (inspect) fprintf (scrptr->ScrOut, "ScreenOutput %dx%d\n", width, page); if (inspect >= 4) ScreenDump (scrptr, "OUTPUT", __LINE__); if (scrptr->TimeStamp[0]) ScreenTimeStamp (scrptr); nlcnt = 0; for (rcnt = 0; rcnt < page; rcnt++) { spacnt = valcnt = 0; for (ccnt = 0; ccnt < width; ccnt++) { value = screen[(rcnt*width)+ccnt]; /* count any leading space characters */ if (((value & CHAR_MASK) == ' ') && !valcnt && spacnt >= 0) { spacnt++; value = 0; } if (value) { if (spacnt >= 0) { /* there have been leading spaces, go back and output them */ valcnt = spacnt; ccnt -= spacnt + 1; spacnt = -1; continue; } if (nlcnt) { while (nlcnt--) fputs ("
", scrptr->ScrOut); nlcnt = 0; } valcnt++; fputs (ScreenChar (scrptr, value), scrptr->ScrOut); } } if (valcnt) fputs ("
", scrptr->ScrOut); else nlcnt++; } fputs (ScreenChar (scrptr, SGR_RESET), scrptr->ScrOut); scrptr->SgrBlink = scrptr->SgrBold = scrptr->SgrGrone = scrptr->SgrReverse = scrptr->SgrG1 = scrptr->SgrUnder = 0; fflush (scrptr->ScrOut); } /*****************************************************************************/ /* Return a pointer to a string with any SGR attributes and character or HTML character entity. */ static char* ScreenChar (SCREPER_STRUCT *scrptr, ulong value) { ulong ch; char *cptr, *sptr; /*********/ /* begin */ /*********/ sptr = scrptr->SgrBuffer; *sptr = '\0'; if (value & SGR_RESET) { if (scrptr->SgrBold) strcat (sptr, ""); if (scrptr->SgrBlink) strcat (sptr, ""); if (scrptr->SgrUnder) strcat (sptr, ""); if (scrptr->SgrReverse) strcat (sptr, ""); scrptr->SgrBlink = scrptr->SgrBold = scrptr->SgrReverse = scrptr->SgrUnder = 0; return (sptr); } if (value & SGR_BOLD) { if (!scrptr->SgrBold) strcat (sptr, ""); scrptr->SgrBold = 1; } else { if (scrptr->SgrBold) strcat (sptr, ""); scrptr->SgrBold = 0; } if (value & SGR_UNDER) { if (!scrptr->SgrUnder) strcat (sptr, ""); scrptr->SgrUnder = 1; } else { if (scrptr->SgrUnder) strcat (sptr, ""); scrptr->SgrUnder = 0; } if (value & SGR_BLINK) { if (!scrptr->SgrBlink) strcat (sptr, ""); scrptr->SgrBlink = 1; } else { if (scrptr->SgrBlink) strcat (sptr, ""); scrptr->SgrBlink = 0; } if (value & SGR_REVER) { if (!scrptr->SgrReverse) strcat (sptr, ""); scrptr->SgrReverse = 1; } else { if (scrptr->SgrReverse) strcat (sptr, ""); scrptr->SgrReverse = 0; } ch = value & CHAR_MASK; if (ch > 127) cptr = CharTable_128_255[(uchar)ch-128]; else if (ch < 32) // cptr = CharTable_0_31[(uchar)ch]; cptr = "▾"; else if (ch < 95) cptr = CharTable_32_94[(uchar)ch-32]; else if (value & SGR_G1) cptr = CharTable_SG_95_127[(uchar)ch-95]; else cptr = CharTable_95_127[(uchar)ch-95]; strcat (sptr, cptr); return (sptr); } /*****************************************************************************/ /* Reset the screen content. */ static void ScreenReset (SCREPER_STRUCT *scrptr) { int ccnt, page, rcnt, width; ulong *screen; /*********/ /* begin */ /*********/ page = scrptr->PtdPageLength; width = scrptr->PtdPageWidth; if (screen = scrptr->ScreenBuffer) for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; scrptr->ScreenRow = scrptr->ScreenCol = scrptr->EtxDetected = scrptr->StxDetected = 0; } /*****************************************************************************/ /* Get a quoted string. A character beginning ^ needs two hex digits following to define the character. For example, ^1a is a control-z, ^3e a hat, ^22 a literal quotation character. Return a pointer to the next character. */ static char* ConfigString (char *string, char *buf, int size, int *lenptr) { char ch; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (size <= 0 || !buf || !string) return (string); if (buf) *buf = '\0'; if (*string != '\"') return (string); zptr = (sptr = buf) + size-1; for (cptr = string+1; *cptr && *cptr != '\"' && sptr < zptr; cptr++) { if (*cptr == '^') { cptr++; if (*cptr && *(cptr+1)) { ch = *(cptr+2); *sptr++ = (uchar)strtol (cptr, NULL, 16); *(cptr+2) = ch; cptr++; } else if (*cptr) cptr++; } else *sptr++ = *cptr; } *sptr = '\0'; if (lenptr) *lenptr = sptr - buf; while (*cptr && *cptr != '\"') cptr++; if (*cptr) cptr++; return (cptr); } /*****************************************************************************/ /* Look for the client-specified (-sentinal=..) output sentinal in the record. Return true or false. */ static int ScreenSentinal (SCREPER_STRUCT *scrptr, char* record, int length) { char *cptr, *czptr, *sptr; /*********/ /* begin */ /*********/ czptr = (cptr = record) + length; sptr = scrptr->SnapshotSentinal; length = scrptr->SnapshotSentinalLength; while (cptr + scrptr->SnapshotSentinalLength < czptr) { if (*cptr == *sptr) if (!(memcmp (cptr, sptr, length))) return (1); cptr++; } if (cptr < czptr) return (1); return (0); } /*****************************************************************************/ /* This function is called every 100mS (i.e. ten times a second). If these specific number of these ticks has expired (i.e. the specified number of 100mS) then output the scraped screen. If not just return. Each time screen data arrives the ticker is reset and must tick down to zero before being output as HTML. */ static void ScreenSnapshot (SCREPER_STRUCT *scrptr) { int status; /*********/ /* begin */ /*********/ if (!scrptr->SnapshotTickCount) { if (scrptr->InspectStream) fprintf (scrptr->ScrOut, "ScreenSnapshot() %d\n", scrptr->SnapshotDataCount); /* output the page as HTML */ if (scrptr->SnapshotDataCount) ScreenOutput (scrptr); scrptr->SnapshotDataCount = 0; scrptr->SnapshotTickCount = scrptr->SnapshotTicks; } else scrptr->SnapshotTickCount--; status = sys$setimr (0, &refreshTimer64, &ScreenSnapshot, scrptr, 0); if (!(status & 1)) exit (status); } /*****************************************************************************/ /* */ static void ScreenTimeStamp (SCREPER_STRUCT *scrptr) { int status; short slen; char *cptr, *sptr; char ch; char tbuf [255]; time_t utime; struct tm *tmptr; $DESCRIPTOR (faoDsc, ""); $DESCRIPTOR (tbufDsc, tbuf); /*********/ /* begin */ /*********/ cptr = scrptr->TimeStamp; for (sptr = cptr; *cptr && *cptr != '!' && *cptr != '%'; cptr++); fprintf (scrptr->ScrOut, "%*.*s", cptr-sptr, cptr-sptr, sptr); if (*cptr == '!') { /* VMS time format */ faoDsc.dsc$a_pointer = sptr = cptr; while (*cptr && *cptr != '<') cptr++; faoDsc.dsc$w_length = cptr - sptr; status = sys$fao (&faoDsc, &slen, &tbufDsc, 0); tbuf[slen] = '\0'; } else if (*cptr == '%') { /* strftime() time format */ time (&utime); tmptr = localtime (&utime); for (sptr = cptr; *cptr && *cptr != '<'; cptr++); ch = *cptr; slen = strftime (tbuf, sizeof(tbuf), sptr, tmptr); if (!slen) strcpy (tbuf, "strftime() ERROR"); *cptr = ch; } fprintf (scrptr->ScrOut, "%*.*s", slen, slen, tbuf); for (sptr = cptr; *cptr; cptr++); fprintf (scrptr->ScrOut, "%*.*s", cptr-sptr, cptr-sptr, sptr); } /*****************************************************************************/ /* If the /fmt/ parameter is NULL output and then empty the report buffer. If not NULL then append a formatted string to the report buffer. */ static void ScreenReport (SCREPER_STRUCT *scrptr, char *fmt, ...) { #define BUF_SIZE 1024 static char buf [BUF_SIZE+128]; static char *bptr = buf; int argcnt; va_list argptr; /*********/ /* begin */ /*********/ if (!fmt) { if (!buf[0]) return; fprintf (scrptr->ScrOut, "\n%s", buf); *(bptr = buf) = '\0'; return; } if (bptr - buf > BUF_SIZE) return; va_count (argcnt); va_start (argptr, fmt); if (vsprintf (bptr, fmt, argptr) < 0) sprintf (bptr, "ERROR: %s\n", fmt); while (*bptr) bptr++; } /*****************************************************************************/ /* Returns a pointer to the HTML, et.al. required by the parent browser app to display the virtual screen. */ char* ScreperScreen () { static char screen [] = "

\n\
\
\n\
\n\
\n";

   char  *sptr;

   /*********/
   /* begin */
   /*********/

   return (screen);
}

/*****************************************************************************/
/*
Return a pointer to an allocated string containing encoded HTML-forbidden
characters.  The string can be free()ed after use, as required.
*/

char* ScreperHtmlEncode (char *string)

{
   int  cnt = 0;
   char  *aptr, *cptr, *eptr, *sptr;

   /*********/
   /* begin */
   /*********/

   for (cptr = string; *cptr; cptr++)
      if (*cptr && (*cptr == '<' || *cptr == '>' || *cptr != '&' ||
                    *cptr == '\"' || *cptr != '\'')) cnt++;
   aptr = sptr = calloc (1, cptr - string + (cnt * 6));
   for (cptr = string; *cptr; cptr++)
   {
      if (*cptr != '<' && *cptr != '>' && *cptr != '&' &&
          *cptr != '\"' && *cptr != '\"')
         *sptr++ = *cptr;
      else
      {
         if (*cptr == '<')
            eptr = "<";
         else
         if (*cptr == '>')
            eptr = ">";
         else
         if (*cptr == '\"')
            eptr = """;
         else
         if (*cptr == '\'')
            eptr = "'";
         else
            eptr = "&";
         while (*eptr) *sptr++ = *eptr++;
      }
   }
   return (aptr);
}

/****************************************************************************/
/*
$FAO formatted print statement to OPCOM.  A fixed-size, internal buffer of 986
bytes maximum is used and the result output as an OPCOM message.  If the format
string begins with "S/" it is sent to SOFTWARE, otherwise CENTRAL.
*/

void ScreperOpcom
(
char *FormatString,
...
)
{
   static $DESCRIPTOR (FaoDsc, "");
   static $DESCRIPTOR (OpcomDsc, "");
   static $DESCRIPTOR (OpcomMsgDsc, "");

   int  status, argcnt, target;
   unsigned short  ShortLength;
   unsigned long  *vecptr;
   unsigned long  FaoVector [31+1];
   va_list  argptr;
   struct
   {
      unsigned long  TargetType;
      unsigned long  RequestId;
      char  MsgText [986+1];
   } OpcomMsg;

   /*********/
   /* begin */
   /*********/

   va_count (argcnt);

   if (argcnt > 31+1) exit (SS$_OVRMAXARG);

   vecptr = FaoVector;
   va_start (argptr, FormatString);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);
   *vecptr = 0;

   if (*(USHORTPTR)FormatString == 'S/')
   {
      target = OPC$M_NM_SOFTWARE;
      FormatString += 2;
   }
   else
      target = OPC$M_NM_CENTRL;
   FaoDsc.dsc$a_pointer = FormatString;
   FaoDsc.dsc$w_length = strlen(FormatString);

   OpcomMsgDsc.dsc$a_pointer = (char*)&OpcomMsg.MsgText;
   OpcomMsgDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1;

   status = sys$faol (&FaoDsc, &ShortLength, &OpcomMsgDsc, &FaoVector);
   if (!(status & 1)) exit (status);

   OpcomMsg.MsgText[ShortLength] = '\0';
   OpcomMsg.TargetType = OPC$_RQ_RQST + ((target & 0xffffff) << 8);
   OpcomMsg.RequestId = 0;

   OpcomDsc.dsc$a_pointer = (char*)&OpcomMsg;
   OpcomDsc.dsc$w_length = ShortLength + 8;

   status = sys$sndopr (&OpcomDsc, 0);
   if (!(status & 1)) exit (status);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreenDump (SCREPER_STRUCT *scrptr, char *fi, int li)

{
   int  ccnt, page, rcnt, width;
   ulong  value;
   ulong  *screen;

   /*********/
   /* begin */
   /*********/

   if (!scrptr->InspectStream) return;

   page =scrptr->PtdPageLength;
   width = scrptr->PtdPageWidth;
   screen = scrptr->ScreenBuffer;

   fflush (scrptr->ScrOut);
   fprintf (scrptr->ScrOut, "%s:%d\n    ", fi, li);
   for (ccnt = 0; ccnt < width; ccnt++)
      fprintf (scrptr->ScrOut, "%02d         ",ccnt);
   fputs ("\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);

   for (rcnt = 0; rcnt < page; rcnt++)
   {
      fprintf (scrptr->ScrOut, "%02d  ",rcnt);
      for (ccnt = 0; ccnt < width; ccnt++)
      {
         value = screen[(rcnt*width)+ccnt];
         fprintf (scrptr->ScrOut, "%08.08x %s ",
                  value, ScreenChar (scrptr, value & ~SGR_MASK));
      }
      fputs ("\n", scrptr->ScrOut);
      fflush (scrptr->ScrOut);
   }
   fputs ("\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreenCursor (SCREPER_STRUCT *scrptr, char *fmt, ...)

{
   int  argcnt;
   va_list  argptr;

   /*********/
   /* begin */
   /*********/

   va_count (argcnt);
   va_start (argptr, fmt);

   fprintf (scrptr->ScrOut, "");
   vfprintf (scrptr->ScrOut, fmt, argptr);
   fprintf (scrptr->ScrOut, "\n");
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperData (SCREPER_STRUCT *scrptr, char *cptr, int length)

{
   char  *czptr;

   fputs ("", scrptr->ScrOut);
   czptr = cptr + length;
   while (cptr < czptr)
   {
      fprintf (scrptr->ScrOut, "%02.02x%c ",
               *(uchar*)cptr, *cptr >= 32 ? *cptr : '.');
      if (*cptr == '\n') fputs ("\n", scrptr->ScrOut);
      cptr++;
   }
   fputs ("\n", scrptr->ScrOut);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperDump (SCREPER_STRUCT *scrptr, char *cptr, int length)

{
   char  *czptr;

   czptr = cptr + length;
   fputs ("", scrptr->ScrOut);
   while (cptr < czptr)
      fputs (ScreenChar (scrptr, *cptr++ & ~SGR_MASK), scrptr->ScrOut);
   fprintf (scrptr->ScrOut, " %d\n", length);
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperKnown (SCREPER_STRUCT *scrptr, char* what,
                          char *cptr, char *czptr)

{
   fprintf (scrptr->ScrOut, "%s ", what);
   ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
   fputs ("\n", scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperUnknown (SCREPER_STRUCT *scrptr, char* what,
                            char *cptr, char *czptr)

{
   fprintf (scrptr->ScrOut, "%s ", what);
   ScreperMore (scrptr, cptr, czptr - cptr > 10 ? 10 : czptr - cptr);
   fputs ("\n", scrptr->ScrOut);
}

/*****************************************************************************/
/*
Development tool.
*/

static void ScreperMore (SCREPER_STRUCT *scrptr, char *aptr, int count)

{
   if (count <= 0) count = 10;
   while (count)
   {
      fprintf (scrptr->ScrOut, "%02.02x%c ",
               *(uchar*)aptr, *aptr > 31 && *aptr < 127 ? *aptr : '.');
      aptr++;
      count--;
   }
   fflush (scrptr->ScrOut);
}

/*****************************************************************************/