/*****************************************************************************/ /* WOTSUP.c The WASD Over-The-Shoulder Uptime Picket is designed to monitor WASD in a production environment for the purpose of alerting operations staff to conditions which might cause that production to be adversely impacted. What follows could be considered the WOTSUP doc (thanks to Ben Burke for this obvious - but not originally to me - pun). As a matter of fact it seems to fit so well that this section should have a mascot ... /\ /\ | \ \ `\ | \ \ `\ \ \ \ | \ \| | \ \ | \ |\ | .-' .--. | /^\ ' .-. ( /\ / \ \ | | | | | .'`|O|/ |__O|.'--. { '/___, \ `. ';(_Y_) `-'/ .' \ \\\_| .' _.-' `'.`._.'.''` jgs `-..' http://www.geocities.com/joan_stark/textcartoon.htm Back to the serious stuff ... Alert triggers include: o server image exit and/or startup (default) o server process non-existent or suspended (default) o percentage thresholds on process quotas (optional) o rates of HTTP status counter change (optional) o maximum period without request processing (optional) Alert reports can be delivered via any combination of: o OPCOM message o MAIL (VMS and RFC822) o site-specific DCL command executed in a spawned subprocess o log file entry The utility monitors the server environment by periodically polling various server data. The default interval is 30 seconds but can specified between 1 and 60 using the /INTERVAL qualifier. As the utility requires access to global memory accounting a per-system WOTSUP is required for each node to be monitored. The following (somewhat contrived) example illustrates the format and content of a WOTSUP report delivered via OPCOM. Reports delivered via other mechanisms have the same content and similar format. %%%%%%%%%% OPCOM 7-MAY-2005 22:27:18.28 %%%%%%%%%%% Message from user SYSTEM on KLAATU Over-The-Shoulder (WASD_WOTSUP) reports: 1. server STARTUP after exit of %X00000001 (%SYSTEM-S-NORMAL) 2. pagfilcnt:395432 pgflquota:500000 79% <= 80% Multiple items are consolidated and provided in the one report delivery if noted during the same poll. An item is not re-reported unless it has subsequently returned to the non-alert threshold and then exceeded it again. NOTE ON MONIKER --------------- You may think justifying the use of the cute acronym WOTSUP from a (somewhat meaningful) functional description required no little effort, and you'd be right! The 'WASD' is obvious. 'Over-The-Shoulder' refers to the approach the utility takes; standing outside the server proper and looking over its shoulder (so to speak) at process data and accounting global memory. 'Uptime' should also be obvious; the utility is intended to assist in maximising server uptime. 'Picket' refers to it's role as lookout in the sense of this (partial) dictionary definition: picket n 1: a person employed to watch for something to happen [syn: lookout, lookout man, sentinel, sentry, watch, spotter, scout] 2: a detachment of troops guarding an army from surprise attack ... 4: a vehicle performing sentinel duty ... There is also a very oblique reference intended to that famous signature line belonging to the cartoon character Bugs Bunny, "Nyahh, what's up, Doc?", and in this environment implies, "what's happening?" Of course WASD should be up but we want to know if it's not, or if there's any likelihood it soon might not be. And, yes, I know WOTSDOWN might be a more accurate moniker but apart from the slightly negative connotation it's almost impossible to generate such an acronym without really torturing the intended meaning :-) So, WOTSUP? MULTIPLE INSTANCES ------------------ WOTSUP will monitor the quotas and status of all server processes associated with multi-instance sites. It will also report on process exit (e.g. STOP/IMAGE/ID=, and of course error exits) and process disappearance (e.g. STOP/ID=). With multi-instance restarts, exit data in the global section is shared between multiple server processes, and so this will always reflect the most recent process exit. Generally WOTSUP reports on multi-instance restarts and single instance failures in a relatively consistent and easily understandable manner. It will provide a more complex sequence of reports and report items where the number of instances is being administratively increased or decreased, and this can require more careful interpretation. PROCESS QUOTAS -------------- Quotas are checked by comparing the $GETJPI count against the limit as a percentage. The following quotas are checked: ASTlm BIOlm BYTlm DIOlm Enqlm Fillm Pgflquo Tqlm Should any one of more of these have reached the minimum available percentage as specified by the /QUOTA qualifier a report is issued. The default is 30%. Individual process quotas may be set using the appropriate keyword with the /QUOTA qualifier specifying the individual quota percentage, though this granularity of control should seldom be necessary. For example /QUOTA=(20,BYTLM:20,PGFLQUO:50) sets all quotas to a minimum of 20% but specifically sets BYTlm to 20% and Pgflquo to 50%. HTTP RESPONSE STATUS -------------------- The command-line /HTTP qualifier allows specified HTTP status groups and individual status codes to be monitored. Status *groups* available are 2xx, 3xx, 4xx and 5xx, although the only meaningful ones for such attention are the 4xx (not found, forbidden, etc.) and 5xx (server errors). Special categories are 0xx, those requests that fail before any significant processing can be accomplished, and 403, forbidden responses. Individual status codes might include 409, 500, 502, etc. NOTE: setting an individual status code in any groups resets that group's monitoring so to monitor any individual status code in a group requires that all codes of interest in that group be specified. There is one exception to this (for backwards compatibility) 403 (forbidden). The specified groups/codes are monitored every sixty seconds and any increase beyond the specified threshold is reported. Groups and thresholds are provided to the qualifier via a single digit and using the following syntax. : An example would be 5:10 which would intrepreted as; for status group 5xx (500, 501, 502, etc.) any increase of ten or more over any one minute should be reported. The qualifier allows multiple values to be specified as in the following example /HTTP=(4:20,403:10,5:10) where a threshold of 20 is set for the 4xx group, 10 specifically for the 403 status (forbidden), and 10 for the 5xx group. To report on individual status codes specify a full three digits and the threshold using the same syntax as for status groups. : This example monitors forbidden access (50 per minute), server internal errors (any 1 in a minute), bad gateway (10 in the minute) and service unavailable (2 per minute). /HTTP=(403:50,500:1,502:10,503:2) SERVER GOING QUIET ------------------ The /QUIET qualifier allows a maximum expected period with no requests to be specified in seconds. (Somewhat obviously) if no requests are received for the period a report is issued. This period may be exceeded by up to /INTERVAL seconds. SPAWNED DCL REPORTING --------------------- The command provided by the /SPAWN qualifier may be anything the subprocess can execute. It would most commonly be the execution of a DCL procedure that would deliver the reporting in some site-specific manner. For example: /SPAWN=@PRODUCTION-DISK:[SUPPORT]WOTSUP_REPORT.COM The spawned command accesses the lines of the report for delivery via the multi-valued system-table logical name WASD_WOTSUP_REPORT. The following DCL commands provide an example of how to retrieve the values from such a logical. $ IDX = 0 $ REPORT_LOOP: $ LINE = F$TRNLNM ("WASD_WOTSUP_REPORT", "LNM$SYSTEM", IDX) $ IF LINE .EQS. "" THEN GOTO END_REPORT_LOOP $ WRITE SYS$OUTPUT LINE $ IDX = IDX + 1 $ GOTO REPORT_LOOP $ END_REPORT_LOOP: $!(all WOTSUP reports should contain at least four lines) $ IF IDX .LT. 4 THEN GOTO REPORT_LOGICAL_NAME_ERROR DETACHED PROCESS ---------------- The WOTSUP utility is intended to be run as a detached process. The DCL procedure WOTSUP.COM provides such an infrastructure. WOTSUP can also be run at the command line to provide some control over such a detached process. $ WOTSUP /DO=RESTART $ WOTSUP /DO=EXIT The first causes the WOTSUP image to exit and the WOTSUP.COM procedure to restart it. In this way a changed WASD_WOTSUP_PARAM logical name value can be reread by the utility changing it's run-time behaviour. The second causes the image to exit and the detached process to delete itself. USAGE EXAMPLES -------------- When the server is noticed to have exited, started or restarted, or the server process no longer exists (this set of events are always reported), and the first reporting to OPCOM CENTRAL and CLUSTER, the second to OPER1. $ WOTSUP /OPCOM $ WOTSUP /OPCOM=OPER1 Report server exit, startup, etc., any server process quota reaching 30% (the default) or less of maximum (the second example down to 15%), and reporting to OPCOM CLUSTER only as well as mailing a message to OPERATOR account. $ WOTSUP /QUOTA /OPCOM=CLUSTER /MAIL=OPERATOR $ WOTSUP /QUOTA=15 /OPCOM=CLUSTER /MAIL=OPERATOR These examples add HTTP status reporting, the first any one per minute 5xx (server error) status, the second any one internal server error (500), ten bad gateways (502), any server unavailbilities (503), along with any 403 (forbidden) increase, and logging the reports to a site-specific file in addition to those destinations in the above examples. $ WOTSUP /QUOTA=15 /HTTP=5:1 /OPCOM=CLUSTER /MAIL=OPERATOR - /LOG=PRODUCTION_LOGS:WOTSUP_REPORTS.LOG $ WOTSUP /QUOTA=15 /HTTP=(403:1,500:1,502:10,503:2) /OPCOM=CLUSTER - /MAIL=OPERATOR /LOG=PRODUCTION_LOGS:WOTSUP_REPORTS.LOG This example expands the one above, adding a server quiet period of three minutes, reporting to two mail destinations (one via RFC822) and spawning a custom DCL command to report using a site-specific mechanism. $ WOTSUP /QUOTA=15 /HTTP=(5:10,403:1) /QUIET=180 /OPCOM=CLUSTER - /MAIL="OPERATOR,prod-mgr@home.net" - /LOG=PRODUCTION_LOGS:WOTSUP_REPORTS.LOG - /SPAWN=@PRODUCTION_DCL:WOTSUP_REPORTS.COM This final example reports to all of the specified destinations as a pre-production, or in-production change, integrity check of the reporting. $ WOTSUP /CHECK - /MAIL="OPERATOR,prod-mgr@home.net" - /LOG=PRODUCTION_LOGS:WOTSUP_REPORTS.LOG - /SPAWN=@PRODUCTION_DCL:WOTSUP_REPORTS.COM LOGICAL NAMES ------------- These are read each time they are required, and therefore can be modified without restarting the utility, in contrast to the command-line qualifiers that are only read at utility startup. If present they override anything supplied at the command-line. These are accessed using LNM$FILE_DEV and so can be defined in process, job or system tables (etc.) Using the system-level table allows the value to be modified without restarting the utility. WASD_WOTSUP_LOG same as /LOG qualifier WASD_WOTSUP_MAIL same as /MAIL qualifier WASD_WOTSUP_OPCOM same as /OPCOM qualifier WASD_WOTSUP_SHUSH if defined reports only to log and OPCOM WASD_WOTSUP_SPAWN same as /SPAWN qualifier The following logical name is used by non-interactive WOTSUP in place of any parameters supplied by the command-line. Defining this as a system-level logical allows the logical value to be modified and the utility restarted (obviating the need to modify WOTSUP wrapper procedures). WASD_WOTSUP_PARAM if present used instead of command-line parameters The following system-level logical names are created and used by the utility. WASD_WOTSUP_PID the PID of an executing, non-interactive WOTSUP WASD_WOTSUP_REPORT this is a multi-value logical created by WOTSUP in the SYSTEM table containing the lines of report text available to a spawned reporting DCL command QUALIFIERS ---------- /CHECK interactive check of /MAIL, /OPCOM and /SPAWN reporting /DBUG turns on all "if (Debug)" statements /DO= controls a detached process; DELETE, EXIT, RESTART /EXIT= seconds wait after exit without restart before alert /HTTP=: HTTP status group/code counter threshold (per minute) /HELP display brief usage information /INTERVAL= seconds between checking (default 15) /LOG= write alerts to this file as well /MAIL[=] one or more comma-separated email addresses (default is SYSTEM, can be RFC822 addresses) /OPCOM[=] target is CENTRAL, PRINTER, ... OPER12 (default is CENTRAL) /QUIET[=] maximum seconds without any request being processed (default is 300 - 5 minutes) /QUOTA= alert when a process quota gets below this percentage /SPAWN= when a report is required execute this command (there is a parameter as P1 containing a string) REQUIRED PRIVILEGES ------------------- WORLD for access to the server process' JPI data SYSLCK for access to system locks for multi-instance support SYSPRV for access to the global section containing the server data to create logical names in the LNM$SYSTEM table BUILD DETAILS ------------- See BUILD_WOTSUP.COM COPYRIGHT --------- */ char CopyrightInfo [] = "Copyright (C) 2005-2021 Mark G.Daniel.\n\ \n\ Licensed under the Apache License, Version 2.0 (the \"License\");\n\ you may not use this file except in compliance with the License.\n\ You may obtain a copy of the License at\n\ \n\ http://www.apache.org/licenses/LICENSE-2.0\n\ \n\ Unless required by applicable law or agreed to in writing, software\n\ distributed under the License is distributed on an \"AS IS\" BASIS,\n\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\ See the License for the specific language governing permissions and\n\ limitations under the License.\n"; /* VERSION HISTORY (update SOFTWAREVN as well!) --------------- 18-DEC-2020 MGD v1.1.6, VAX no longer implemented use native 64bit data (rather than ulong[2]) 07-FEB-2016 MGD v1.1.5, WASD v11 |LastExitBinTime| becomes |LastExitTime64| 20-AUG-2013 MGD v1.1.4, SysTrnLnm() becomes SysTrnLnm2() to avoid WASD.H function prototype collision (differing arguments) 02-JAN-2013 MGD v1.1.3, bugfix; ReportLogFile() log file name pointer 30-AUG-2009 MGD v1.1.2, minor mod for WASD v10.0 WASD_WOTSUP_SHUSH logical name 10-FEB-2008 MGD v1.1.1, OverTheShoulder() check for GBLSEC mismatch 24-SEP-2006 MGD v1.1.0, improve granularity of HTTP status code monitoring enhances /HTTP= parameters and reporting available monitor all of multiple instance processes modify email subject line to indicate report item(s) increase default interval from 15 to 30 seconds (all require support available with WASD v9.2.0) 24-APR-2006 MGD v1.0.1, bugfix; GetParameters() /QUIET parameter test 07-MAY-2005 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "v1.1.6" #define SOFTWARENM "WOTSUP" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # error VAX no longer implemented #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* this header file contains the accounting structure definitions */ #include "../httpd/wasd.h" /* only with 7.3-2 and later? */ #ifndef SS$_FORCEX # define SS$_FORCEX 11228 #endif /* these make it easier to have both VAX and Alpha declarations */ #define OPC$_RQ_RQST 3 #define OPC$M_OPR_CENTRAL 0x1 #define OPC$M_OPR_PRINTER 0x2 #define OPC$M_OPR_TAPES 0x4 #define OPC$M_OPR_DISKS 0x8 #define OPC$M_OPR_DEVICES 0x10 #define OPC$M_OPR_CARDS 0x20 #define OPC$M_OPR_NETWORK 0x40 #define OPC$M_OPR_CLUSTER 0x80 #define OPC$M_OPR_SECURITY 0x100 #define OPC$M_OPR_REPLY 0x200 #define OPC$M_OPR_SOFTWARE 0x400 #define OPC$M_OPR_LICENSE 0x800 #define OPC$M_OPR_USER1 0x1000 #define OPC$M_OPR_USER2 0x2000 #define OPC$M_OPR_USER3 0x4000 #define OPC$M_OPR_USER4 0x8000 #define OPC$M_OPR_USER5 0x10000 #define OPC$M_OPR_USER6 0x20000 #define OPC$M_OPR_USER7 0x40000 #define OPC$M_OPR_USER8 0x80000 #define OPC$M_OPR_USER9 0x100000 #define OPC$M_OPR_USER10 0x200000 #define OPC$M_OPR_USER11 0x400000 #define OPC$M_OPR_USER12 0x800000 #define BOOL int #define TRUE 1 #define FALSE 0 #define FI_LI "WOTSUP", __LINE__ #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define DEFAULT_INTERVAL_SECONDS 30 #define DEFAULT_QUIET_SECONDS 300 #define DEFAULT_QUOTA_MIN_PERCENT 30 #define INSTANCE_INTERVAL_SECONDS 75 #define SUSPENDED_RETRY_SECONDS 30 #define WASD_WOTSUP_LOG "WASD_WOTSUP_LOG" #define WASD_WOTSUP_MAIL "WASD_WOTSUP_MAIL" #define WASD_WOTSUP_OPCOM "WASD_WOTSUP_OPCOM" #define WASD_WOTSUP_SHUSH "WASD_WOTSUP_SHUSH" #define WASD_WOTSUP_SPAWN "WASD_WOTSUP_SPAWN" #define WASD_WOTSUP_PID "WASD_WOTSUP_PID" #define WASD_WOTSUP_REPORT "WASD_WOTSUP_REPORT" #define INSTANCE_MAX 8 char ErrorSanityCheck [] = "sanity check", Utility [] = "WOTSUP"; BOOL DoCheck, Debug; int CliStatusGroup403Delta, CliOpcomTarget, CliReportExitType, DefaultOpcomTarget = OPC$M_OPR_CENTRAL | OPC$M_OPR_CLUSTER, ExitStatus, InstanceCount = -1, InstanceCountChange, InstanceGroupNumber = 1, /* default group number */ IntervalSeconds = DEFAULT_INTERVAL_SECONDS, JpiMode, JpiPid, PercentMinAst = DEFAULT_QUOTA_MIN_PERCENT, PercentMinBio = DEFAULT_QUOTA_MIN_PERCENT, PercentMinByt = DEFAULT_QUOTA_MIN_PERCENT, PercentMinDio = DEFAULT_QUOTA_MIN_PERCENT, PercentMinEnq = DEFAULT_QUOTA_MIN_PERCENT, PercentMinFil = DEFAULT_QUOTA_MIN_PERCENT, PercentMinPg = DEFAULT_QUOTA_MIN_PERCENT, PercentMinTq = DEFAULT_QUOTA_MIN_PERCENT, PreviousInstanceCount, PreviousStartupCount, QuietSeconds, SecondsAfterExit; int CliStatusCodeNumber [RESPONSE_STATUS_CODE_MAX], CliStatusCountDelta [RESPONSE_STATUS_CODE_MAX], CliStatusGroupDelta [1+5], InstancePid [INSTANCE_MAX]; int64 PreviousLastExitTime64; unsigned long SetPrvMask [2] = { PRV$M_SYSLCK | PRV$M_SYSPRV | PRV$M_WORLD, 0 }; unsigned short SyiClusterNodes, SyiNodeLength; char *JpiModeName, *CliLogPtr, *CliMailToPtr, *CliSpawnCommandPtr, *CommandLinePtr; char CommandLine [256], JpiPrcNam [16], JpiUserName [13], MailPersonal [64], SyiNodeName [16]; int HttpdGblSecLength; HTTPD_GBLSEC *HttpdGblSecPtr; ACCOUNTING_STRUCT *AccountingPtr; struct AnExitHandler ExitHandler; /* required prototypes */ GetInstancePid (); LookBack (int*); ReportLine (char*, ...); ReportServerPidName (); char* ReportSubj (char*, ...); BOOL SetHttpStatusCode (int, int); char* SysGetMsg (int); char* SysTrnLnm2 (char*, char*); /*****************************************************************************/ /* */ int main () { static short PrcNamLength; static struct { unsigned short buf_len; unsigned short item; unsigned char *buf_addr; void *ret_len; } JpiItems [] = { { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 }, { sizeof(JpiMode), JPI$_MODE, &JpiMode, 0 }, { sizeof(JpiUserName)-1, JPI$_USERNAME, &JpiUserName, 0 }, { sizeof(JpiPrcNam)-1, JPI$_PRCNAM, &JpiPrcNam, &PrcNamLength }, { 0,0,0,0 } }, SyiItems [] = { { sizeof(SyiClusterNodes), SYI$_CLUSTER_NODES, &SyiClusterNodes, 0 }, { sizeof(SyiNodeName)-1, SYI$_NODENAME, &SyiNodeName, &SyiNodeLength }, { 0,0,0,0 } }; int status; char *cptr; /*********/ /* begin */ /*********/ status = sys$setprv (1, &SetPrvMask, 0, 0); if (VMSnok (status) || status == SS$_NOTALLPRIV) ErrorExit (status, "$SETPRV()", FI_LI); status = sys$getsyi (0, 0, 0, &SyiItems, 0, 0, 0); if (VMSnok (status)) ErrorExit (status, "$GETSYI()", FI_LI); SyiNodeName[SyiNodeLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", SyiNodeName); status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0); if (VMSnok (status)) ErrorExit (status, "$GETJPIW()", FI_LI); switch (JpiMode) { case JPI$K_INTERACTIVE : JpiModeName = "INTERACTIVE"; break; case JPI$K_NETWORK : JpiModeName = "NETWORK"; break; case JPI$K_OTHER : JpiModeName = "DETACHED"; break; case JPI$K_BATCH : JpiModeName = "BATCH"; break; default : JpiModeName = "?"; } JpiPrcNam[PrcNamLength] = '\0'; for (cptr = JpiUserName; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; if (Debug) fprintf (stdout, "%08.08X %d |%s|%s|%s|\n", JpiPid, JpiMode, JpiModeName, JpiUserName, JpiPrcNam); GetParameters (); if (JpiMode == JPI$K_INTERACTIVE) fprintf (stdout, "%%%s-I-INTERACTIVE, %s as %s\n%s", Utility, JpiUserName, JpiPrcNam, CopyrightInfo); /* build a personal name in case it's needed when mailing */ sprintf (MailPersonal, "WOTSUP on %s", SyiNodeName); if (DoCheck) { ReportSubj ("/CHECK only, please ignore"); ReportLine ("!AZ /CHECK only, please ignore!!", SOFTWAREID); ReportLine (NULL); exit (SS$_NORMAL); } if (JpiMode != JPI$K_INTERACTIVE) { $DESCRIPTOR (LogFaoDsc, "%%%%%%%%%% WOTSUP !%D %%%%%%%%%%%\n\ Message from user !AZ on !AZ\n\ Over-The-Shoulder (!AZ) !AZ mode startup\n\ !AZ\0"); char Buffer [512]; $DESCRIPTOR (BufferDsc, Buffer); fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n\n", Utility, SOFTWAREID); status = sys$fao (&LogFaoDsc, 0, &BufferDsc, 0, JpiUserName, SyiNodeName, JpiPrcNam, JpiModeName, CommandLinePtr); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorExit (status, "$FAO()", FI_LI); fprintf (stdout, "%s\n\n", Buffer); ReportLogFile (Buffer); ReportOpcom (DefaultOpcomTarget, "Over-The-Shoulder (!AZ) !AZ mode startup\r\n\!AZ", JpiPrcNam, JpiModeName, CommandLinePtr); sprintf (Buffer, "%08.08X", JpiPid); SysCreLnm (WASD_WOTSUP_PID, Buffer); /* set up and declare the exit handler */ ExitHandler.HandlerAddress = &LookBack; ExitHandler.ArgCount = 1; ExitHandler.ExitStatusPtr = &ExitStatus; if (VMSnok (status = sys$dclexh (&ExitHandler))) ErrorExit (status, "$DCLEXH()", FI_LI); } MapGlobalSection (); AccountingPtr = &HttpdGblSecPtr->Accounting; LookAtHttp (1); OverTheShoulder (); } /*****************************************************************************/ /* Declared exit handler. */ int LookBack (int *ExitStatusPtr) { int status; char *cptr; $DESCRIPTOR (LogFaoDsc, "%%%%%%%%%% WOTSUP !%D %%%%%%%%%%%\n\ Message from user !AZ on !AZ\n\ Over-The-Shoulder (!AZ) !AZ mode !AZ\0"); char Buffer [512]; $DESCRIPTOR (BufferDsc, Buffer); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LookBack() %%X%08.08X\n", *ExitStatusPtr); status = *ExitStatusPtr; if (status == SS$_NORMAL) cptr = "REQUESTED RESTART"; else if (status == SS$_FORCEX) cptr = "REQUESTED EXIT"; else cptr = "ERROR EXIT"; sys$fao (&LogFaoDsc, 0, &BufferDsc, 0, JpiUserName, SyiNodeName, JpiPrcNam, JpiModeName, cptr); fprintf (stdout, "%s\n\n", Buffer); } /*****************************************************************************/ /* Loop, checking on the server, sleeping 'interval' seconds between checks. */ int OverTheShoulder () { BOOL LookNow; int cnt; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "OverTheShoulder()\n"); for (;;) { if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER || HttpdGblSecPtr->GblSecLength != sizeof(HTTPD_GBLSEC)) { ReportSubj ("GBLSEC mismatch"); ReportLine ("Global section mismatch, rebuild WOTSUP?"); /* report it */ ReportLine (NULL); exit (SS$_ABORT); } GetInstancePid (); if (InstanceCount != PreviousInstanceCount) { /* If the number of instances changed don't report it immediately. This also has the desirable side-effect of delaying any initial report by this interval allowing things to settle down a bit first (whatever this means exactly). */ if (InstanceCountChange >= INSTANCE_INTERVAL_SECONDS) { if (PreviousInstanceCount) { ReportSubj ("INSTANCES:!UL", InstanceCount); ReportLine ("number of instances has changed from !UL to !UL", PreviousInstanceCount, InstanceCount); } InstanceCountChange = 0; PreviousInstanceCount = InstanceCount; LookNow = TRUE; } else { InstanceCountChange += IntervalSeconds; LookNow = FALSE; } } else LookNow = TRUE; if (LookNow) { LookAtServer (); for (cnt = 0; cnt < INSTANCE_MAX; cnt++) LookAtProcess (cnt); LookAtHttp (0); /* if there is anything to report then this will do it */ ReportLine (NULL); } sleep (IntervalSeconds); } } /*****************************************************************************/ /* Check that the server still exists, has not exited and/or restarted. */ int LookAtServer () { static BOOL QuietReported; static unsigned long PrevRequestCount, RequestSeconds; BOOL IncludeServerList = FALSE; int idx, status, ReportExitType; unsigned long CurrentSeconds, RequestCount; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LookAtServer()\n"); if (AccountingPtr->LastExitTime64) { if (PreviousLastExitTime64 && AccountingPtr->LastExitTime64 != PreviousLastExitTime64) { /* startup count has changed since the utility initialized */ ReportSubj ("EXIT:%X!8XL", AccountingPtr->LastExitStatus); ReportLine ("server PID !8XL exit %X!8XL (!AZ)", AccountingPtr->LastExitPid, AccountingPtr->LastExitStatus, SysGetMsg(AccountingPtr->LastExitStatus)); IncludeServerList = TRUE; } PreviousLastExitTime64 = AccountingPtr->LastExitTime64; } if (AccountingPtr->StartupCount && PreviousStartupCount != AccountingPtr->StartupCount) { if (PreviousStartupCount) { /* startup count has changed since the utility initialized */ ReportSubj ("STARTUP:!UL", AccountingPtr->StartupCount); ReportLine ("server STARTUP (!UL)", AccountingPtr->StartupCount); IncludeServerList = TRUE; } PreviousStartupCount = AccountingPtr->StartupCount; } if (IncludeServerList) ReportServerPidName(); if (QuietSeconds) { RequestCount = AccountingPtr->ProcessingTotalCount[HTTP12]; if (PrevRequestCount && RequestCount == PrevRequestCount) { CurrentSeconds = time(NULL); if (CurrentSeconds - RequestSeconds > QuietSeconds) { if (!QuietReported) { ReportSubj ("QUIET"); ReportLine ("no requests processed for !UL seconds", CurrentSeconds - RequestSeconds); QuietReported = TRUE; } } } else { QuietReported = FALSE; PrevRequestCount = RequestCount; RequestSeconds = time(NULL); } } } /*****************************************************************************/ /* Check that the server process still exists, and that each of the relevant process quotas has not reached it's minimum remaining quotas. As a process is normally on occasion in a SUSPENDED state allow for this by retrying a limited number of times. */ int LookAtProcess (int InstanceIndex) { static BOOL AstReported [INSTANCE_MAX], BioReported [INSTANCE_MAX], BytReported [INSTANCE_MAX], DioReported [INSTANCE_MAX], EnqReported [INSTANCE_MAX], FilReported [INSTANCE_MAX], NonExprReported [INSTANCE_MAX], PgReported [INSTANCE_MAX], TqReported [INSTANCE_MAX]; static unsigned long HttpdPid [INSTANCE_MAX]; static int NonExprSeconds [INSTANCE_MAX]; static unsigned long JpiAstCnt, JpiAstLm, JpiBioCnt, JpiBytLm, JpiBytCnt, JpiBioLm, JpiDioCnt, JpiDioLm, JpiEnqCnt, JpiEnqLm, JpiFilCnt, JpiFilLm, JpiPagFilCnt, JpiPgFlQuota, JpiPid, JpiPrcCnt, JpiPrcLm, JpiTqCnt, JpiTqLm; static int64 ConnectTime64, CurrentTime64, JpiLoginTime64; static char JpiPrcNam [16]; static struct { unsigned short buf_len; unsigned short item; unsigned char *buf_addr; void *ret_len; } JpiItems [] = { { sizeof(JpiAstCnt), JPI$_ASTCNT, &JpiAstCnt, 0 }, { sizeof(JpiAstLm), JPI$_ASTLM, &JpiAstLm, 0 }, { sizeof(JpiBioCnt), JPI$_BIOCNT, &JpiBioCnt, 0 }, { sizeof(JpiBioLm), JPI$_BIOLM, &JpiBioLm, 0 }, { sizeof(JpiBytCnt), JPI$_BYTCNT, &JpiBytCnt, 0 }, { sizeof(JpiBytLm), JPI$_BYTLM, &JpiBytLm, 0 }, { sizeof(JpiDioCnt), JPI$_DIOCNT, &JpiDioCnt, 0 }, { sizeof(JpiDioLm), JPI$_DIOLM, &JpiDioLm, 0 }, { sizeof(JpiEnqCnt), JPI$_ENQCNT, &JpiEnqCnt, 0 }, { sizeof(JpiEnqLm), JPI$_ENQLM, &JpiEnqLm, 0 }, { sizeof(JpiFilCnt), JPI$_FILCNT, &JpiFilCnt, 0 }, { sizeof(JpiFilLm), JPI$_FILLM, &JpiFilLm, 0 }, { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 }, { sizeof(JpiPgFlQuota), JPI$_PGFLQUOTA, &JpiPgFlQuota, 0 }, { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 }, { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 }, { sizeof(JpiTqCnt), JPI$_TQCNT, &JpiTqCnt, 0 }, { sizeof(JpiTqLm), JPI$_TQLM, &JpiTqLm, 0 }, {0,0,0,0} }; int attempt, idx, status, CurrentSeconds; unsigned short Length; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LookAtProcess()\n"); if (HttpdPid[InstanceIndex] != InstancePid [InstanceIndex]) { AstReported[InstanceIndex] = BioReported[InstanceIndex] = BytReported[InstanceIndex] = DioReported[InstanceIndex] = EnqReported[InstanceIndex] = FilReported[InstanceIndex] = NonExprReported[InstanceIndex] = PgReported[InstanceIndex] = TqReported[InstanceIndex] = FALSE; NonExprSeconds[InstanceIndex] = 0; } HttpdPid[InstanceIndex] = InstancePid[InstanceIndex]; /* if just reseting the PID-related info */ if (!HttpdPid[InstanceIndex]) return (SS$_NORMAL); if (Debug) fprintf (stdout, "%08.08X\n", HttpdPid[InstanceIndex]); /* try once per second for however many */ attempt = SUSPENDED_RETRY_SECONDS; for (;;) { status = sys$getjpiw (0, &HttpdPid[InstanceIndex], 0, &JpiItems, 0, 0, 0); if (Debug) fprintf (stdout, "sys$getjpiw() %%X%08.08X\n", status); if (VMSok (status)) break; if (status == SS$_SUSPENDED) { sleep (1); if (attempt--) continue; /* after retrying for SUSPENDED_RETRY_SECONDS it's still suspended */ ReportSubj ("SERVER:%X!8XL", status); ReportLine ("server PID !8XL is %X!8XL (!AZ)", HttpdPid[InstanceIndex], status, SysGetMsg(status)); return (SS$_NORMAL); } if (status == SS$_NONEXPR) { ReportSubj ("SERVER:%X!8XL", status); ReportLine ("server PID !8XL is %X!8XL (!AZ)", HttpdPid[InstanceIndex], status, SysGetMsg(status)); /* "Make it so, Number One" */ InstancePid[InstanceIndex] = 0; return (SS$_NORMAL); } ErrorExit (status, "$GETJPIW()", FI_LI); } JpiPrcNam[15] = '\0'; for (cptr = JpiPrcNam; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; if (Debug) fprintf (stdout, "JpiPrcNam |%s|\n", JpiPrcNam); sys$gettim (&CurrentTime64); lib$sub_times (&CurrentTime64, &JpiLoginTime64, &ConnectTime64); if (JpiAstLm) { if (JpiAstCnt * 100 / JpiAstLm <= PercentMinAst) { if (!AstReported[InstanceIndex]) { ReportSubj ("QUOTA:astlm"); ReportLine ("!8XL !AZ astcnt:!UL astlm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiAstCnt, JpiAstLm, JpiAstCnt * 100 / JpiAstLm, PercentMinAst); AstReported[InstanceIndex] = TRUE; } } else AstReported[InstanceIndex] = FALSE; } if (JpiBioLm) { if (JpiBioCnt * 100 / JpiBioLm <= PercentMinBio) { if (!BioReported[InstanceIndex]) { ReportSubj ("QUOTA:biolm"); ReportLine ("!8XL !AZ biocnt:!UL biolm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiBioCnt, JpiBioLm, JpiBioCnt * 100 / JpiBioLm, PercentMinBio); BioReported[InstanceIndex] = TRUE; } } else BioReported[InstanceIndex] = FALSE; } if (JpiBytLm) { if (JpiBytCnt * 100 / JpiBytLm <= PercentMinByt) { if (!BytReported[InstanceIndex]) { ReportSubj ("QUOTA:bytlm"); ReportLine ("!8XL !AZ bytcnt:!UL bytlm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiBytCnt, JpiBytLm, JpiBytCnt * 100 / JpiBytLm, PercentMinByt); BytReported[InstanceIndex] = TRUE; } } else BytReported[InstanceIndex] = FALSE; } if (JpiDioLm) { if (JpiDioCnt * 100 / JpiDioLm <= PercentMinDio) { if (!DioReported[InstanceIndex]) { ReportSubj ("QUOTA:diolm"); ReportLine ("!8XL !AZ diocnt:!UL diolm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiDioCnt, JpiDioLm, JpiDioCnt * 100 / JpiDioLm, PercentMinDio); DioReported[InstanceIndex] = TRUE; } } else DioReported[InstanceIndex] = FALSE; } if (JpiEnqLm) { if (JpiEnqCnt * 100 / JpiEnqLm <= PercentMinEnq) { if (!EnqReported[InstanceIndex]) { ReportSubj ("QUOTA:enqlm"); ReportLine ("!8XL !AZ enqcnt:!UL enqlm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiEnqCnt, JpiEnqLm, JpiEnqCnt * 100 / JpiEnqLm, PercentMinEnq); EnqReported[InstanceIndex] = TRUE; } } else EnqReported[InstanceIndex] = FALSE; } if (JpiFilLm) { if (JpiFilCnt * 100 / JpiFilLm <= PercentMinFil) { if (!FilReported[InstanceIndex]) { ReportSubj ("QUOTA:fillm"); ReportLine ("!8XL !AZ filcnt:!UL fillm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiFilCnt, JpiFilLm, JpiFilCnt * 100 / JpiFilLm, PercentMinFil); FilReported[InstanceIndex] = TRUE; } } else FilReported[InstanceIndex] = FALSE; } if (JpiTqLm) { if (JpiTqCnt * 100 / JpiTqLm <= PercentMinTq) { if (!TqReported[InstanceIndex]) { ReportSubj ("QUOTA:tqlm"); ReportLine ("!8XL !AZ tqcnt:!UL tqlm:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiTqCnt, JpiTqLm, JpiTqCnt * 100 / JpiTqLm, PercentMinTq); TqReported[InstanceIndex] = TRUE; } } else TqReported[InstanceIndex] = FALSE; } if (JpiPgFlQuota) { if (JpiPagFilCnt * 100 / JpiPgFlQuota <= PercentMinPg) { if (!PgReported[InstanceIndex]) { ReportSubj ("QUOTA:pgflquota"); ReportLine ("!8XL !AZ pagfilcnt:!UL pgflquota:!UL !UL% <= !UL%", JpiPid, JpiPrcNam, JpiPagFilCnt, JpiPgFlQuota, JpiPagFilCnt * 100 / JpiPgFlQuota, PercentMinPg); PgReported[InstanceIndex] = TRUE; } } else PgReported[InstanceIndex] = FALSE; } return (SS$_NORMAL); } /*****************************************************************************/ /* Check the HTTP response status code counters once per minute to ascertain whether any have increased greater than the allowed threshold. Operates in two modes. After the initial call, with 'DoCheck' true, it is called using a timer every sixty seconds to check the server HTTP counters against the thresholds. If any has exceeded it has it's corresponding alert boolean set. When called with 'DoCheck' false it checks these booleans and reports any that are set. */ int LookAtHttp (BOOL DoCheck) { static BOOL StatusCode403Alert; static BOOL StatusCodeCountAlert [RESPONSE_STATUS_CODE_MAX], StatusCodeGroupAlert [1+5]; static int PrevStatusCode403Count, StatusCode403Count; static int PrevStatusCodeGroup [1+5], StatusCodeGroup [1+5]; /* RESPONSE_STATUS_CODE_MAX is defined in [SRC.HTTPD]WASD.H */ static unsigned long PrevStatusCodeCount [RESPONSE_STATUS_CODE_MAX], StatusCodeCount [RESPONSE_STATUS_CODE_MAX]; static unsigned long OneMinuteDelta [2] = { -600000000, -1 }; int idx, status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LookAtHttp() %d\n", DoCheck); if (DoCheck) { /* check the code groups (1xx, 2xx, 3xx, 4xx, 5xx) */ for (idx = 0; idx <= 5; idx++) { if (idx == 1 || !CliStatusGroupDelta[idx]) continue; StatusCodeGroup[idx] = AccountingPtr->ResponseStatusCodeGroup[idx]; if (PrevStatusCodeGroup[idx]) if (StatusCodeGroup[idx] >= PrevStatusCodeGroup[idx] + CliStatusGroupDelta[idx]) StatusCodeGroupAlert[idx] = TRUE; else StatusCodeGroupAlert[idx] = FALSE; else { StatusCodeGroupAlert[idx] = FALSE; PrevStatusCodeGroup[idx] = StatusCodeGroup[idx]; } } /* check the individual code counts (100, 101, 200, 201 ... 505) */ for (idx = 1; idx < RESPONSE_STATUS_CODE_MAX; idx++) { if (!CliStatusCountDelta[idx]) continue; StatusCodeCount[idx] = AccountingPtr->ResponseStatusCodeCount[idx]; if (PrevStatusCodeCount[idx]) if (StatusCodeCount[idx] >= PrevStatusCodeCount[idx] + CliStatusCountDelta[idx]) StatusCodeCountAlert[idx] = TRUE; else StatusCodeCountAlert[idx] = FALSE; else { StatusCodeCountAlert[idx] = FALSE; PrevStatusCodeCount[idx] = StatusCodeCount[idx]; } } StatusCode403Count = AccountingPtr->RequestForbiddenCount; if (CliStatusGroup403Delta && PrevStatusCode403Count) if (StatusCode403Count > PrevStatusCode403Count + CliStatusGroup403Delta) StatusCode403Alert = TRUE; else StatusCode403Alert = FALSE; else { StatusCode403Alert = FALSE; PrevStatusCode403Count = StatusCode403Count; } status = sys$setimr (0, &OneMinuteDelta, &LookAtHttp, 1, 0); if (VMSnok (status)) ErrorExit (status, "$SETIMR()", FI_LI); } else { /* report code groups */ for (idx = 0; idx <= 5; idx++) { if (StatusCodeGroupAlert[idx]) { ReportSubj ("HTTP:!ULxx", idx); ReportLine ("HTTP !ULxx curr:!UL > prev:!UL + !UL", idx, StatusCodeGroup[idx], PrevStatusCodeGroup[idx], CliStatusGroupDelta[idx]); StatusCodeGroupAlert[idx] = FALSE; PrevStatusCodeGroup[idx] = StatusCodeGroup[idx]; } } /* report individual codes */ for (idx = 1; idx < RESPONSE_STATUS_CODE_MAX; idx++) { if (StatusCodeCountAlert[idx]) { ReportSubj ("HTTP:!UL", CliStatusCodeNumber[idx]); ReportLine ("HTTP !UL curr:!UL > prev:!UL + !UL", CliStatusCodeNumber[idx], StatusCodeCount[idx], PrevStatusCodeCount[idx], CliStatusCountDelta[idx]); StatusCodeCountAlert[idx] = FALSE; PrevStatusCodeCount[idx] = StatusCodeCount[idx]; } } if (StatusCode403Alert) { ReportSubj ("HTTP:403"); ReportLine ("HTTP 403 curr:!UL > prev:!UL + !UL", StatusCode403Count, PrevStatusCode403Count, CliStatusGroup403Delta); StatusCode403Alert = FALSE; PrevStatusCode403Count = StatusCode403Count; } } } /*****************************************************************************/ /* Create a report line containing the PID and process name of each process in the InstancePid[] array. */ ReportServerPidName () { static unsigned long JpiPid; static char JpiPrcNam [16]; static struct { unsigned short buf_len; unsigned short item; unsigned char *buf_addr; void *ret_len; } JpiItems [] = { { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 }, {0,0,0,0} }; int cnt = 0, idx, status; char *cptr, *sptr; char ListBuffer [21+(INSTANCE_MAX*28)+1]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReportServerPidName()\n"); sptr = ListBuffer; for (idx = 0; idx < INSTANCE_MAX; idx++) { if (!InstancePid[idx]) continue; status = sys$getjpiw (0, &InstancePid[idx], 0, &JpiItems, 0, 0, 0); if (Debug) fprintf (stdout, "sys$getjpiw() %%X%08.08X\n", status); /* only time it continues is when the process does not exist */ if (VMSnok (status) && status == SS$_NONEXPR) continue; if (cnt++) sptr += sprintf( sptr, ", "); else sptr += sprintf (sptr, "server PID%s ", InstanceCount > 1 ? "s are" : " is"); if (VMSok (status)) { JpiPrcNam[15] = '\0'; for (cptr = JpiPrcNam; *cptr && *cptr != ' '; cptr++); *cptr = '\0'; sptr += sprintf (sptr, "%08.08X (%s)", InstancePid[idx], JpiPrcNam); } else sptr += sprintf (sptr, "%08.08X (%%X%08.08X)", InstancePid[idx], status); } if (!cnt) sprintf (sptr, "no server process%s found", InstanceCount > 1 ? "es" : ""); ReportLine (ListBuffer); } /*****************************************************************************/ /* See [SRC.HTTPD]CONTROL.C for other information. Uses the VMS Distributed Lock Manager to keep track of how many servers are currently executing on the node/cluster. NL locks indicate interest (used by this utility), CR locks indicate a server waiting for a group directive. This function enqueues a single NL lock, then periodically get all the locks associated with that resource and counts up the number of CR locks - giving the number of servers! */ GetInstancePid () { static char NodeLockName [32], PrevInstanceString [32]; static $DESCRIPTOR (NodeLockNameDsc, NodeLockName); static LKIDEF LkiLocks [32]; static struct lksb NodeLockLksb; static struct { unsigned short TotalLength, /* bits 0..15 */ LockLength; /* bits 16..30 */ } ReturnLength; static struct { unsigned short buf_len; unsigned short item; unsigned char *buf_addr; void *ret_len; } LkiItems [] = { { sizeof(LkiLocks), LKI$_LOCKS, &LkiLocks, &ReturnLength }, {0,0,0,0} }; int cnt, idx, status, LockCount, LockNameMagic; char *cptr, *sptr, *zptr; unsigned long LockPid [INSTANCE_MAX]; IO_SB IOsb; LKIDEF *lkiptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetInstancePid()\n"); if (!NodeLockName[0]) { /************************/ /* build and queue lock */ /************************/ if (InstanceGroupNumber > INSTANCE_ENV_NUMBER_MAX) ErrorExit (SS$_BUGCHECK, "Group number range.", FI_LI); /* a byte comprising two 4 bit fields, version and server group number */ LockNameMagic = ((HTTPD_LOCK_VERSION & 0xf) << 4) | (InstanceGroupNumber & 0xf); /* build the (binary) resource name for the node lock */ zptr = (sptr = NodeLockName) + sizeof(NodeLockName)-1; for (cptr = HTTPD_NAME; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = (char)LockNameMagic; for (cptr = SyiNodeName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = (char)0x05; NodeLockNameDsc.dsc$w_length = sptr - NodeLockName; /* enqueue a just-interested NL lock */ status = sys$enqw (0, LCK$K_NLMODE, &NodeLockLksb, LCK$M_EXPEDITE | LCK$M_SYSTEM, &NodeLockNameDsc, 0, 0, 0, 0, 0, 2, 0); if (VMSok (status)) status = NodeLockLksb.lksb$w_status; if (VMSnok (status)) ErrorExit (status, "$ENQW()", FI_LI); } /*****************************************/ /* get and count the node instance locks */ /*****************************************/ status = sys$getlkiw (0, &NodeLockLksb.lksb$l_lkid, &LkiItems, &IOsb, 0, 0, 0); if (VMSok (status)) status = NodeLockLksb.lksb$w_status; if (VMSnok (status)) ErrorExit (status, "$GETLKIW()", FI_LI);; if (ReturnLength.LockLength) { /* if insufficient buffer space */ if (ReturnLength.LockLength & 0x8000) ErrorExit (SS$_BADPARAM, "$GETLKIW()", FI_LI);; LockCount = ReturnLength.TotalLength / ReturnLength.LockLength; } else LockCount = 0; if (Debug) fprintf (stdout, "%d\n", LockCount); InstanceCount = 0; lkiptr = &LkiLocks; for (cnt = LockCount; cnt; cnt--) { if (Debug) fprintf (stdout, "lki$b_grmode: %d\n", lkiptr->lki$b_grmode); if (lkiptr->lki$b_grmode != LCK$K_NLMODE) { if (Debug) fprintf (stdout, "%d %08x\n", InstanceCount, lkiptr->lki$l_pid); if (InstanceCount >= INSTANCE_MAX) ErrorExit (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); LockPid[InstanceCount++] = lkiptr->lki$l_pid; } lkiptr++; } /**************************************************/ /* reorganise PID array so each element is stable */ /**************************************************/ /* correlate existing PIDs in the instance array with the lock array */ for (idx = 0; idx < INSTANCE_MAX; idx++) { if (!InstancePid[idx]) continue; for (cnt = 0; cnt < InstanceCount; cnt++) { if (InstancePid[idx] == LockPid[cnt]) { /* this lock PID is already in the instance array */ LockPid[cnt] = 0; break; } } } /* now place any new PIDs from the lock to the instance array */ for (cnt = 0; cnt < InstanceCount; cnt++) { if (!LockPid[cnt]) continue; /* find an empty instance array element and use it */ for (idx = 0; idx < INSTANCE_MAX; idx++) if (!InstancePid[idx]) break; if (idx >= INSTANCE_MAX) ErrorExit (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); InstancePid[idx] = LockPid[cnt]; } if (Debug) for (idx = 0; idx < INSTANCE_MAX; idx++) fprintf (stdout, "%d %08x\n", idx, InstancePid[idx]); } /*****************************************************************************/ /* If 'FormatString' is non-NULL it is expected to contain a $FAO formatting string and the call any required parameters. The resultant string has required carriage-control added and is stored as a series of lines in a static buffer. The resulting string will be used as a subject line in the alerting email. */ char* ReportSubj ( char *FormatString, ... ) { static int BufferLength, ItemCount; static char Buffer [256]; static $DESCRIPTOR (BufferDsc, ""); static $DESCRIPTOR (CommaFaoDsc, ", "); static $DESCRIPTOR (FaoDsc, ""); int status, argcnt; unsigned short ShortLength; unsigned long *vecptr; unsigned long FaoVector [31+2]; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (argcnt > 31) ErrorExit (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (Debug) fprintf (stdout, "ReportSubj() %d |%s|\n", argcnt, FormatString); if (FormatString) { if (!BufferLength) { /*********************/ /* prepend some info */ /*********************/ BufferDsc.dsc$a_pointer = Buffer; BufferDsc.dsc$w_length = sizeof(Buffer)-1; FaoDsc.dsc$a_pointer = "WOTSUP on !AZ reports "; FaoDsc.dsc$w_length = strlen(FaoDsc.dsc$a_pointer); vecptr = FaoVector; *vecptr++ = SyiNodeName; *vecptr = 0; status = sys$faol (&FaoDsc, &ShortLength, &BufferDsc, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorExit (status, "$FAOL()", FI_LI); BufferLength += ShortLength; Buffer[BufferLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", Buffer); } /*************************/ /* now append the report */ /*************************/ BufferDsc.dsc$a_pointer = Buffer + BufferLength; BufferDsc.dsc$w_length = sizeof(Buffer)-1 - BufferLength; if (ItemCount++) { status = sys$faol (&CommaFaoDsc, &ShortLength, &BufferDsc); /* not worried about buffer overflow with the email subject line */ if (VMSnok (status)) ErrorExit (status, "$FAOL()", FI_LI); BufferLength += ShortLength; Buffer[BufferLength] = '\0'; } vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 1; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); *vecptr = 0; BufferDsc.dsc$a_pointer = Buffer + BufferLength; BufferDsc.dsc$w_length = sizeof(Buffer)-1 - BufferLength; FaoDsc.dsc$a_pointer = FormatString; FaoDsc.dsc$w_length = strlen(FormatString); status = sys$faol (&FaoDsc, &ShortLength, &BufferDsc, &FaoVector); /* not worried about buffer overflow with the email subject line */ if (VMSnok (status)) ErrorExit (status, "$FAOL()", FI_LI); BufferLength += ShortLength; Buffer[BufferLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", Buffer); return; } /************************************/ /* return the built-up subject line */ /************************************/ /* if there is nothing to report then report that! */ if (!BufferLength) "BUGCHECK?"; /* reset subj line (without emptying the to-be returned string) */ BufferLength = 0; ItemCount = 0; return (Buffer); } /*****************************************************************************/ /* If 'FormatString' is non-NULL it is expected to contain a $FAO formatting string and the call any required parameters. The resultant string has required carriage-control added and is stored as a series of lines in a static buffer. When 'FormatString' is NULL the buffered text is sent to the OPCOM, Mail and/or spawn targets. Check the logical names containing the OPCOM, Mail and spawn targets for the most recent settings (can be changed dynamically without restarting the utility). If these logical names do not exist fall back to anything specified at the original command-line. */ int ReportLine ( char *FormatString, ... ) { static int BufferLength, ItemCount; static char Buffer [2048]; static char *OpcomTextPtr; static $DESCRIPTOR (BufferDsc, ""); static $DESCRIPTOR (FaoDsc, ""); int status, argcnt, OpcomTarget; unsigned short ShortLength; unsigned long *vecptr; unsigned long FaoVector [32]; char *cptr, *MailToPtr, *SpawnCommandPtr; char MailTo [256], SpawnCommand [256]; va_list argptr; FILE *LogFilePtr; /*********/ /* begin */ /*********/ va_count (argcnt); if (argcnt > 31) ErrorExit (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (Debug) fprintf (stdout, "ReportLine() %d |%s|\n", argcnt, FormatString); if (FormatString) { if (!BufferLength) { /****************************************/ /* prepend a leading informational line */ /****************************************/ BufferDsc.dsc$a_pointer = Buffer; BufferDsc.dsc$w_length = sizeof(Buffer)-1; FaoDsc.dsc$a_pointer = "%%%%%%%%%% WOTSUP !%D %%%%%%%%%%%\r\n\ Message from user !AZ on !AZ\r\n\ Over-The-Shoulder (!AZ) reports:"; FaoDsc.dsc$w_length = strlen(FaoDsc.dsc$a_pointer); vecptr = FaoVector; *vecptr++ = 0; *vecptr++ = JpiUserName; *vecptr++ = SyiNodeName; *vecptr++ = JpiPrcNam; *vecptr = 0; status = sys$faol (&FaoDsc, &ShortLength, &BufferDsc, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorExit (status, "$FAOL()", FI_LI); BufferLength += ShortLength; Buffer[BufferLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", Buffer); /* for OPCOM purposes find the start of the third line */ OpcomTextPtr = Buffer + BufferLength; while (OpcomTextPtr > Buffer && *OpcomTextPtr != '\n') OpcomTextPtr--; if (*OpcomTextPtr == '\n') OpcomTextPtr++; } /*******************************/ /* now append the line of text */ /*******************************/ BufferDsc.dsc$a_pointer = Buffer + BufferLength; BufferDsc.dsc$w_length = sizeof(Buffer)-1 - BufferLength; FaoDsc.dsc$a_pointer = "\r\n!UL. "; FaoDsc.dsc$w_length = 7; status = sys$fao (&FaoDsc, &ShortLength, &BufferDsc, ++ItemCount); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorExit (status, "$FAO()", FI_LI); BufferLength += ShortLength; vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 1; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); *vecptr++ = 0; BufferDsc.dsc$a_pointer = Buffer + BufferLength; BufferDsc.dsc$w_length = sizeof(Buffer)-1 - BufferLength; FaoDsc.dsc$a_pointer = FormatString; FaoDsc.dsc$w_length = strlen(FormatString); status = sys$faol (&FaoDsc, &ShortLength, &BufferDsc, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorExit (status, "$FAOL()", FI_LI); BufferLength += ShortLength; Buffer[BufferLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", Buffer); return; } /* if there is nothing to report then just return */ if (!BufferLength) return; /**********************/ /* get latest targets */ /**********************/ if (cptr = SysTrnLnm2 (WASD_WOTSUP_OPCOM, NULL)) { OpcomTarget = OpcomTargetOf(cptr); if (!OpcomTarget) ErrorExit (SS$_BADPARAM, WASD_WOTSUP_OPCOM, FI_LI); } else OpcomTarget = CliOpcomTarget; if (cptr = SysTrnLnm2 (WASD_WOTSUP_MAIL, MailTo)) { if (*cptr) MailToPtr = MailTo; else ErrorExit (SS$_BADPARAM, WASD_WOTSUP_MAIL, FI_LI); } else MailToPtr = CliMailToPtr; if (cptr = SysTrnLnm2 (WASD_WOTSUP_SPAWN, SpawnCommand)) { while (*cptr && isspace(*cptr)) cptr++; if (!*cptr) ErrorExit (SS$_BADPARAM, WASD_WOTSUP_SPAWN, FI_LI); SpawnCommandPtr = SpawnCommand; } else SpawnCommandPtr = CliSpawnCommandPtr; /*******************/ /* send to targets */ /*******************/ if (OpcomTarget) ReportOpcom (OpcomTarget, "!AZ", OpcomTextPtr); if (!SysTrnLnm2 (WASD_WOTSUP_SHUSH, NULL)) { if (MailToPtr) MailMessage (MailPersonal, MailToPtr, ReportSubj(NULL), Buffer); /* ensure email subject line is reset regardless of mailing message */ ReportSubj (NULL); if (SpawnCommandPtr) ReportSpawn (SpawnCommandPtr, Buffer); } /* should be called last as it modifies the buffer content */ ReportLogFile (Buffer); /****************/ /* reset buffer */ /****************/ Buffer[BufferLength=0] = '\0'; ItemCount = 0; OpcomTextPtr = NULL; } /*****************************************************************************/ /* Write the specified text to any log file. Modifies the text! */ int ReportLogFile (char *Buffer) { int status; char *cptr, *sptr, *LogFileNamePtr; char LogFileName [256]; FILE *LogFilePtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReportLogFile()\n"); if (SysTrnLnm2 (WASD_WOTSUP_LOG, LogFileName)) LogFileNamePtr = LogFileName; else LogFileNamePtr = CliLogPtr; if (!LogFileNamePtr) return; LogFilePtr = fopen (LogFileNamePtr, "a", "shr=put"); if (LogFilePtr) { /* remove the from s before writing to the log file */ for (cptr = sptr = Buffer; *cptr; *sptr++ = *cptr++) if (*(unsigned short*)cptr == '\r\n') cptr++; *sptr = '\0'; fprintf (LogFilePtr, "%s\n\n", Buffer); fclose (LogFilePtr); } else { status = vaxc$errno; ReportOpcom (DefaultOpcomTarget, "Over-The-Shoulder (!AZ) reports:\r\n\ failed to open log file !AZ, %X!8XL (!AZ)", JpiPrcNam, LogFileNamePtr, status, SysGetMsg(status)); } } /*****************************************************************************/ /* Get the HTTPd server data from its global section. Data is written by UpdateGlobalSection() in [SRC.HTTPD]SUPPORT.C module. */ int MapGlobalSection () { /* system global section, map into first available virtual address */ static int MapFlags = SEC$M_SYSGBL | SEC$M_EXPREG; /* it is recommended to map into any virtual address in the region (P0) */ static unsigned long InAddr [2] = { 0x200, 0x200 }; static char HttpdGblSecName [32]; static $DESCRIPTOR (HttpdGblSecNameDsc, HttpdGblSecName); static $DESCRIPTOR (HttpdGblSecNameFaoDsc, GBLSEC_NAME_FAO); int status, ByteSize, PageSize; unsigned short ShortLength; unsigned long RetAddr [2]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MapGlobalSection()\n"); if (HttpdGblSecPtr) return (SS$_NORMAL); /* only need to map it the one time */ sys$fao (&HttpdGblSecNameFaoDsc, &ShortLength, &HttpdGblSecNameDsc, HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER, InstanceGroupNumber, "HTTPD"); HttpdGblSecNameDsc.dsc$w_length = ShortLength; if (Debug) fprintf (stdout, "|%s|\n", HttpdGblSecName); /* map the specified global section */ status = sys$mgblsc (&InAddr, &RetAddr, 0, MapFlags, &HttpdGblSecNameDsc, 0, 0); if (Debug) fprintf (stdout, "sys$mgblsc() %%X%08.08X begin:%d end:%d\n", status, RetAddr[0], RetAddr[1]); if (VMSok (status)) { ByteSize = (RetAddr[1]+1) - RetAddr[0]; PageSize = (RetAddr[1]+1) - RetAddr[0] >> 9; HttpdGblSecPtr = (HTTPD_GBLSEC*)RetAddr[0]; HttpdGblSecLength = ByteSize; } else { HttpdGblSecPtr = NULL; HttpdGblSecLength = ByteSize = PageSize = 0; if (status == SS$_NOSUCHSEC) { fprintf (stdout, "%%%s-E-SERVER, no such server!\n\ -SYSTEM-E-NOSUCHSEC, no such (global) section\n", Utility); exit (SS$_NOSUCHSEC | STS$M_INHIB_MSG); } else exit (status); } if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER || HttpdGblSecPtr->GblSecLength != sizeof(HTTPD_GBLSEC)) { fprintf (stdout, "%%%s-E-GBLSEC_MISMATCH, global section mismatch, rebuild WOTSUP?\n", Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } return (SS$_NORMAL); } /*****************************************************************************/ /* Use the VMS callable mail interface to create and send a VMS mail message. 'To' can be a list of comma-separated addresses. 'Subject' is a null-terminated string. 'Body' is a null-terminated string of '\n'-separated lines of plain text. Just truncates anything longer than 255 characters (body excluded, body records included)! */ int MailMessage ( char *PersonalName, char *To, char *Subject, char *Body ) { int status; unsigned long SendContext = 0; char *cptr, *sptr; struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } BodyPartItem [] = { { 0, MAIL$_SEND_RECORD, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, PersonalNameItem [] = { { 0, MAIL$_SEND_PERS_NAME, PersonalName, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendUserNameItem [] = { { 0, MAIL$_SEND_USERNAME, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SubjectItem [] = { { 0, MAIL$_SEND_SUBJECT, Subject, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, NoSignalItem [] = { { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, NullItem = {0,0,0,0}; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MailMessage() |%s|\n|%s|\n|%s|\n", PersonalName, To, Subject); if (PersonalName != NULL && PersonalName[0]) { PersonalNameItem[0].buf_len = strlen(PersonalName); if (PersonalNameItem[0].buf_len > 255) PersonalNameItem[0].buf_len = 255; status = mail$send_begin (&SendContext, &PersonalNameItem, &NullItem); } else status = mail$send_begin (&SendContext, &NoSignalItem, &NullItem); if (VMSnok (status)) ErrorExit (status, "MAIL$SEND_BEGIN", FI_LI); /* a single, or multiple comma-separated addresses */ cptr = To; while (*cptr) { if (!*cptr) break; sptr = cptr; while (*cptr && *cptr != ',') cptr++; if (*cptr) *cptr++ = '\0'; SendUserNameItem[0].buf_addr = sptr; SendUserNameItem[0].buf_len = strlen(sptr); if (SendUserNameItem[0].buf_len > 255) SendUserNameItem[0].buf_len = 255; if (Debug) fprintf (stdout, "address |%s|\n", (char*)SendUserNameItem[0].buf_addr); status = mail$send_add_address (&SendContext, &SendUserNameItem, &NullItem); if (VMSnok (status)) ErrorExit (status, sptr, FI_LI); } SubjectItem[0].buf_len = strlen(Subject); if (SubjectItem[0].buf_len > 255) SubjectItem[0].buf_len = 255; status = mail$send_add_attribute (&SendContext, &SubjectItem, &NullItem); if (VMSnok (status)) ErrorExit (status, "MAIL$SEND_ADD_ATTRIBUTE", FI_LI); cptr = Body; while (*cptr) { BodyPartItem[0].buf_addr = cptr; while (*cptr && *cptr != '\n') cptr++; BodyPartItem[0].buf_len = cptr - (char*)BodyPartItem[0].buf_addr; if (BodyPartItem[0].buf_len > 255) BodyPartItem[0].buf_len = 255; if (*cptr) cptr++; status = mail$send_add_bodypart (&SendContext, &BodyPartItem, &NullItem); if (VMSnok (status)) ErrorExit (status, "MAIL$SEND_ADD_BODYPART", FI_LI); } status = mail$send_message (&SendContext, &NoSignalItem, &NoSignalItem); if (VMSnok (status)) ErrorExit (status, "MAIL$SEND_MESSAGE", FI_LI); mail$send_end (&SendContext, &NullItem, &NullItem); return (SS$_NORMAL); } /****************************************************************************/ /* $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. */ int ReportOpcom ( int OpcomTarget, char *FormatString, ... ) { static $DESCRIPTOR (FaoDsc, ""); static $DESCRIPTOR (OpcomDsc, ""); static $DESCRIPTOR (OpcomMsgDsc, ""); int status, argcnt; 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 (Debug) fprintf (stdout, "ReportOpcom() |%s| %d\n", FormatString, argcnt); if (argcnt > 31+2) exit (SS$_OVRMAXARG); vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 2; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); *vecptr = 0; FaoDsc.dsc$a_pointer = FormatString; FaoDsc.dsc$w_length = strlen(FormatString); OpcomMsgDsc.dsc$a_pointer = &OpcomMsg.MsgText; OpcomMsgDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1; status = sys$faol (&FaoDsc, &ShortLength, &OpcomMsgDsc, &FaoVector); if (VMSnok (status)) ErrorExit (status, "$FAOL()", FI_LI); OpcomMsg.MsgText[ShortLength] = '\0'; if (Debug) fprintf (stdout, "%d |%s|\n", ShortLength, OpcomMsg.MsgText); OpcomMsg.TargetType = OPC$_RQ_RQST + ((OpcomTarget & 0xffffff) << 8); OpcomMsg.RequestId = 0; OpcomDsc.dsc$a_pointer = &OpcomMsg; OpcomDsc.dsc$w_length = ShortLength + 8; status = sys$sndopr (&OpcomDsc, 0); if (VMSnok (status)) ErrorExit (status, "$SNDOPR()", FI_LI); return (status); } /*****************************************************************************/ /* Return an integer value representing the OPCOM target string. Zero indicates an error. */ int OpcomTargetOf (char *cptr) { int OpcomTarget; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "OpcomTargetOf() |%s|\n", cptr); OpcomTarget = 0; while (*cptr) { while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++; if (!*cptr) break; if (strsame (cptr, "ALL", 3)) OpcomTarget = 0xffffffff; else if (strsame (cptr, "CENTRAL", 7)) OpcomTarget |= OPC$M_OPR_CENTRAL; else if (strsame (cptr, "PRINTER", 7)) OpcomTarget |= OPC$M_OPR_PRINTER; else if (strsame (cptr, "TAPES", 5)) OpcomTarget |= OPC$M_OPR_TAPES; else if (strsame (cptr, "DISKS", 5)) OpcomTarget |= OPC$M_OPR_DISKS; else if (strsame (cptr, "DEVICES", 7)) OpcomTarget |= OPC$M_OPR_DEVICES; else if (strsame (cptr, "CARDS", 5)) OpcomTarget |= OPC$M_OPR_CARDS; else if (strsame (cptr, "NETWORK", 7)) OpcomTarget |= OPC$M_OPR_NETWORK; else if (strsame (cptr, "CLUSTER", 7)) OpcomTarget |= OPC$M_OPR_CLUSTER; else if (strsame (cptr, "SECURITY", 8)) OpcomTarget |= OPC$M_OPR_SECURITY; else if (strsame (cptr, "REPLY", 5)) OpcomTarget |= OPC$M_OPR_REPLY; else if (strsame (cptr, "SOFTWARE", 8)) OpcomTarget |= OPC$M_OPR_SOFTWARE; else if (strsame (cptr, "LICENSE", 7)) OpcomTarget |= OPC$M_OPR_LICENSE; else if (strsame (cptr, "OPER2", 5)) OpcomTarget |= OPC$M_OPR_USER2; else if (strsame (cptr, "OPER3", 5)) OpcomTarget |= OPC$M_OPR_USER3; else if (strsame (cptr, "OPER4", 5)) OpcomTarget |= OPC$M_OPR_USER4; else if (strsame (cptr, "OPER5", 5)) OpcomTarget |= OPC$M_OPR_USER5; else if (strsame (cptr, "OPER6", 5)) OpcomTarget |= OPC$M_OPR_USER6; else if (strsame (cptr, "OPER7", 5)) OpcomTarget |= OPC$M_OPR_USER7; else if (strsame (cptr, "OPER8", 5)) OpcomTarget |= OPC$M_OPR_USER8; else if (strsame (cptr, "OPER9", 5)) OpcomTarget |= OPC$M_OPR_USER9; else if (strsame (cptr, "OPER10", 6)) OpcomTarget |= OPC$M_OPR_USER10; else if (strsame (cptr, "OPER11", 6)) OpcomTarget |= OPC$M_OPR_USER11; else if (strsame (cptr, "OPER12", 6)) OpcomTarget |= OPC$M_OPR_USER12; else /* must be here after OPER1n for the obvious reason */ if (strsame (cptr, "OPER1", 5)) OpcomTarget |= OPC$M_OPR_USER1; else return (0); while (isalnum(*cptr)) cptr++; } if (Debug) fprintf (stdout, "%08.08X\n", OpcomTarget); return (OpcomTarget); } /*****************************************************************************/ /* Parse and set the /QUOTA=.. qualifier from the command-line. It can be followed by a single integer, and/or one or more keywords for ASTLM, BIOLM, etc, followed by an integer value for the invidual quota. For example: /QUOTA=(30,BYTLM:20,PGFLQUO:50). */ ProcessQuotaOf (char *cptr) { int status, percent; int *QuotaPtr; char *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessQuotaOf() |%s|\n", cptr); while (*cptr) { while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++; if (!*cptr) break; if (isdigit(*cptr)) QuotaPtr = NULL; else if (strsame (cptr, "ASTLM:", 6)) QuotaPtr = &PercentMinAst; else if (strsame (cptr, "BIOLM:", 6)) QuotaPtr = &PercentMinBio; else if (strsame (cptr, "BYTLM:", 6)) QuotaPtr = &PercentMinByt; else if (strsame (cptr, "DIOLM:", 6)) QuotaPtr = &PercentMinDio; else if (strsame (cptr, "ENQLM:", 6)) QuotaPtr = &PercentMinEnq; else if (strsame (cptr, "FILLM:", 6)) QuotaPtr = &PercentMinFil; else if (strsame (cptr, "PGFLQUO:", 8)) QuotaPtr = &PercentMinPg; else if (strsame (cptr, "TQLM:", 5)) QuotaPtr = &PercentMinTq; else { for (sptr = cptr; *sptr && *sptr != ',' && *sptr != ')'; sptr++); *sptr = '\0'; fprintf (stdout, "%%%s-E-UNRKEY, \"%s\" is an unrecognised keyword\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } while (*cptr && !isdigit(*cptr)) cptr++; if (*cptr == ':') cptr++; percent = atoi(cptr); if (percent <= 0 || percent > 99) { for (sptr = cptr; *sptr && *sptr != ',' && *sptr != ')'; sptr++); *sptr = '\0'; fprintf (stdout, "%%%s-E-QUOTA, \"%s\" must be >= 1 percent <= 99\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (QuotaPtr) *QuotaPtr = percent; else { PercentMinAst = PercentMinBio = PercentMinByt = PercentMinDio = PercentMinEnq = PercentMinFil = PercentMinTq = PercentMinPg = percent; } while (isdigit(*cptr)) cptr++; } } /*****************************************************************************/ /* Using the CRTL system() call spawn a subprocess to perform the DCL command provided. A double-quote delimited parameter is provided on the command-line that supplies the report string. */ int ReportSpawn ( char *DclCommand, char *ReportText ) { int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReportSpawn() |%s|%s|\n", DclCommand, ReportText); SysCreLnm (WASD_WOTSUP_REPORT, ReportText); status = system (DclCommand); if (VMSnok (status)) ErrorExit (status, "system()", FI_LI); } /*****************************************************************************/ /* Translate a logical name using LNM$FILE_DEV. Return a pointer to the value string, or NULL if the name does not exist. If 'LogValue' is supplied the logical name is translated into that (assumed to be large enough), otherwise it's translated into an internal static buffer. */ char* SysTrnLnm2 ( char *LogName, char *LogValue ) { static unsigned short ShortLength; static char StaticLogValue [256]; static $DESCRIPTOR (LogNameDsc, ""); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { 255, LNM$_STRING, 0, &ShortLength }, { 0,0,0,0 } }; int status; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SysTrnLnm2() |%s|\n", LogName); LogNameDsc.dsc$a_pointer = LogName; LogNameDsc.dsc$w_length = strlen(LogName); if (LogValue) cptr = LnmItems[0].buf_addr = LogValue; else cptr = LnmItems[0].buf_addr = StaticLogValue; status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (!(status & 1)) { if (Debug) fprintf (stdout, "|(null)|\n"); return (NULL); } cptr[ShortLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", cptr); return (cptr); } /*****************************************************************************/ /* Create a logical name in the system table. If that name contains delimtted lines the create a translation index for each (0..127). Used to pass multi-line values to a spawned reporting agent. */ SysCreLnm ( char *LogName, char *LogValue ) { #define LNM_INDEX_MAX 127 static $DESCRIPTOR (LogTableDsc, "LNM$SYSTEM"); static $DESCRIPTOR (LogNameDsc, ""); int idx, status; char *cptr, *sptr; struct { short int buf_len; short int item; unsigned char *buf_addr; unsigned short *ret_len; } CreLnmItem [LNM_INDEX_MAX+1]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SysCreLnm() |%s|\n", LogName, LogValue); idx = 0; cptr = LogValue; while (*cptr && idx < LNM_INDEX_MAX) { for (sptr = cptr; *sptr && *(unsigned short*)sptr != '\r\n'; sptr++); CreLnmItem[idx].item = LNM$_STRING; CreLnmItem[idx].buf_addr = (unsigned char*)cptr; CreLnmItem[idx++].buf_len = sptr - cptr; if (Debug) fprintf (stdout, "|%.*s|\n", sptr-cptr, cptr); cptr = sptr; if (*(unsigned short*)cptr == '\r\n') cptr += 2; } memset (&CreLnmItem[idx], 0, sizeof(CreLnmItem[idx])); LogNameDsc.dsc$a_pointer = LogName; LogNameDsc.dsc$w_length = strlen(LogName); status = sys$crelnm (0, &LogTableDsc, &LogNameDsc, 0, &CreLnmItem); if (VMSnok (status)) ErrorExit (status, "$CRELNM()", FI_LI); } /*****************************************************************************/ /* Provide some extra information (particularly source module and line) when the image exits. Needs to be explicitly called (not an exit handler). */ ErrorExit ( int StatusValue, char *Explanation, char *SourceCodeModule, int SourceCodeLine ) { /*********/ /* begin */ /*********/ fprintf (stdout, "%%%s-F-EXIT, %s (%%X%08.08X)\n\ -%s-I-WHERE, module:%s line:%d\n", Utility, Explanation, StatusValue, Utility, SourceCodeModule, SourceCodeLine); _exit (StatusValue & 0x7fffffff); } /*****************************************************************************/ /* Return a pointer to the message string corresponding to the supplied VMS status value. */ char* SysGetMsg (int VmsStatus) { static char Message [256]; int status; short int Length; $DESCRIPTOR (MessageDsc, Message); /*********/ /* begin */ /*********/ Message[0] = '\0'; if (VmsStatus) { /* text component only */ sys$getmsg (VmsStatus, &Length, &MessageDsc, 0xe, 0); Message[Length] = '\0'; } if (Message[0]) return (Message); else return ("(internal error)"); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration logical containing the equivalent. */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; int status; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr, *zptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if (JpiMode == JPI$K_INTERACTIVE) clptr = NULL; else clptr = getenv ("WASD_WOTSUP_PARAM"); if (!clptr) { /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; } CommandLinePtr = calloc (1, strlen(clptr)+1); strcpy (CommandLinePtr, clptr); aptr = NULL; ch = *clptr; for (;;) { if (aptr && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (strsame (aptr, "/CHECK", -1)) { DoCheck = TRUE; continue; } if (strsame (aptr, "/DBUG", -1)) { Debug = TRUE; continue; } if (strsame (aptr, "/DO=", 4)) { unsigned long Pid; cptr = SysTrnLnm2 (WASD_WOTSUP_PID, NULL); if (!cptr) exit (SS$_NONEXPR); Pid = strtoul (cptr, NULL, 16); if (!Pid) exit (SS$_NONEXPR); for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (strsame (cptr, "DELETE", -1)) exit (sys$delprc (&Pid, 0)); if (strsame (cptr, "EXIT", -1)) exit (sys$forcex (&Pid, 0, SS$_FORCEX)); if (strsame (cptr, "RESTART", -1)) exit (sys$forcex (&Pid, 0, SS$_NORMAL)); fprintf (stdout, "%%%s-E-UNRKEY, \"%s\" is an unrecognised keyword\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); continue; } if (strsame (aptr, "/EXIT=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; SecondsAfterExit = atoi(cptr); if (SecondsAfterExit <= 0 || SecondsAfterExit > 300) { fprintf (stdout, "%%%s-E-EXIT, must be >= 1 seconds <= 300\n", Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } continue; } if (strsame (aptr, "/HELP", 4)) { ShowHelp (); exit (SS$_NORMAL); } if (strsame (aptr, "/HTTP=", 4)) { int CodeDelta, CodeNumber; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; while (*cptr) { while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++; if (!*cptr) break; CodeNumber = atoi(cptr); while (isdigit(*cptr)) cptr++; if (*cptr) cptr++; CodeDelta = atoi(cptr); if (CodeDelta < 0) { fprintf (stdout, "%%%s-E-HTTP, delta must be > 0\n", Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } while (isdigit(*cptr)) cptr++; if ((CodeNumber >= 0 && CodeNumber <= 5) || CodeNumber == 403) { /* HTTP status code group */ if (CodeNumber == 403) CliStatusGroup403Delta = CodeDelta; else CliStatusGroupDelta[CodeNumber] = CodeDelta; } else if (CodeNumber >= 100 && CodeNumber <= 505) { /* individual HTTP status code */ if (!SetHttpStatusCode (CodeNumber, CodeDelta)) { fprintf (stdout, "%%%s-E-HTTP, code %d unknown\n", CodeNumber, Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } /* get the code group from the number and reset the group */ CodeNumber /= 100; CliStatusGroupDelta[CodeNumber] = 0; } else { fprintf (stdout, "%%%s-E-HTTP, must be >= 0 and <= 5 or >= 100 and <= 505\n", Utility); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } continue; } if (strsame (aptr, "/INTERVAL=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; IntervalSeconds = atoi(cptr); if (IntervalSeconds <= 0 || IntervalSeconds > 60) { fprintf (stdout, "%%%s-E-INTERVAL, must be >= 1 seconds <= 60\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } continue; } if (strsame (aptr, "/LOG=", 4)) { CliLogPtr = "SYS$OUTPUT"; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; cptr++; CliLogPtr = cptr; continue; } if (strsame (aptr, "/MAIL=", 4)) { CliMailToPtr = "SYSTEM"; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; cptr++; if (*cptr == '(') cptr++; CliMailToPtr = cptr; while (*cptr) cptr++; if (*(cptr-1) == ')') *(cptr-1) = '\0'; continue; } if (strsame (aptr, "/OPCOM=", 4)) { CliOpcomTarget = DefaultOpcomTarget; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; cptr++; CliOpcomTarget = OpcomTargetOf(cptr); if (!CliOpcomTarget) { fprintf (stdout, "%%%s-E-UNRKEY, \"%s\" is an unrecognised keyword\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } continue; } if (strsame (aptr, "/QUIET=", 4)) { QuietSeconds = DEFAULT_QUIET_SECONDS; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; cptr++; QuietSeconds = atoi(cptr); if (QuietSeconds <= 0 || QuietSeconds < IntervalSeconds) { fprintf (stdout, "%%%s-E-QUIET, must be >= /INTERVAL\n", Utility, cptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } continue; } if (strsame (aptr, "/QUOTA=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; ProcessQuotaOf (cptr); continue; } if (strsame (aptr, "/SPAWN=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (!*cptr) { fprintf (stdout, "%%%s-W-VALREQ, missing qualifier or keyword value\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } CliSpawnCommandPtr = cptr; continue; } if (*aptr != '/') { fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } else { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } } /*****************************************************************************/ /* Set the delta against the HTTP status code number. Based on array elements from [SRC.HTTPD]REQUEST.C RequestHttpStatusCode(). */ BOOL SetHttpStatusCode ( int CodeNumber, int CodeDelta ) { int idx; /*********/ /* begin */ /*********/ switch (CodeNumber) { case 100 : idx = 1; break; case 101 : idx = 2; break; case 200 : idx = 3; break; case 201 : idx = 4; break; case 202 : idx = 5; break; case 203 : idx = 6; break; case 204 : idx = 7; break; case 205 : idx = 8; break; case 206 : idx = 9; break; case 300 : idx = 10; break; case 301 : idx = 11; break; case 302 : idx = 12; break; case 303 : idx = 13; break; case 304 : idx = 14; break; case 305 : idx = 15; break; case 306 : idx = 16; break; case 307 : idx = 17; break; case 400 : idx = 18; break; case 401 : idx = 19; break; case 402 : idx = 20; break; case 403 : idx = 21; break; case 404 : idx = 22; break; case 405 : idx = 23; break; case 406 : idx = 24; break; case 407 : idx = 25; break; case 408 : idx = 26; break; case 409 : idx = 27; break; case 410 : idx = 28; break; case 411 : idx = 29; break; case 412 : idx = 30; break; case 413 : idx = 31; break; case 414 : idx = 32; break; case 415 : idx = 33; break; case 416 : idx = 34; break; case 417 : idx = 35; break; case 500 : idx = 36; break; case 501 : idx = 37; break; case 502 : idx = 38; break; case 503 : idx = 39; break; case 504 : idx = 40; break; case 505 : idx = 41; break; default : return (FALSE); } CliStatusCodeNumber[idx] = CodeNumber; CliStatusCountDelta[idx] = CodeDelta; return (TRUE); } /****************************************************************************/ /* */ int ShowHelp () { fprintf (stdout, "%%%s-I-HELP, usage for the WASD Over-The-Shoulder Uptime Picket (%s)\n\ \n\ The WOTSUP utility is designed to look at WASD in a production environment,\n\ over it's shoulder so-to-speak, to alert operations staff of conditions which\n\ might cause that production to be adversely impacted. Triggers include server\n\ image exit and/or startup, percentage thresholds on process quotas, rates of\n\ HTTP status counter change, maximum period without request processing. Alert\n\ reports are delivered via OPCOM message and/or email and/or a custom DCL\n\ command executed in a spawned subprocess, and/or log file.\n\ \n\ $ WOTSUP [qualifiers..] [parameters..]\n\ \n\ /CHECK /DO= /EXIT[=] /HELP /HTTP=(:[,..])\n\ /INTERVAL= /LOG= /MAIL[=
(es)] /OPCOM[=]\n\ /QUIET[=] /QUOTA[=] /SPAWN=\n\ \n\ Usage examples:\n\ \n\ $ WOTSUP /DO=RESTART\n\ $ WOTSUP /OPCOM=OPER12 /MAIL=OPERATOR /CHECK\n\ $ WOTSUP /QUOTA=20 /HTTP=5:20 /OPCOM /MAIL=OPERATOR /SPAWN=@SUPPORT:WOTSUP.COM\n\ \n", Utility, SOFTWAREID); return (SS$_NORMAL); } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ BOOL strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (FALSE); if (count) if (!--count) return (TRUE); } if (*sptr1 || *sptr2) return (FALSE); else return (TRUE); } /*****************************************************************************/