/*****************************************************************************/ /* WAStee.c WAStee is a utility to generate time-stamped log files containing intervals of a long-lived WASD server process, and/or to consolidate all process log files generated during the defined period. It is the tee in a PIPE sequence. And yes, an intended (somewhat lame) homophone. The log files are generated as WASD_SERVER_LOGS:node_yyyymmdd.LOG and reflect a shortened file name of the default, primary server log file. Alternate location for the log files may be specified using the logical name WASTEE_LOCATION. SYSPRV is used to create the file so the account must possess that privilege, or the image INSTALLed with it, and the location permit SYSPRV write access. This utility is UNSUITABLE for sites using multiple instances and/or environments on a node. Only the first of multiple server processes will have the log teed. Subsequent processes see a message of the ilk: %WASTEE-E-OPEN, 14-MAR-2021 01:28:11 WASD_SERVER_LOGS:KLAATU_20210308.LOG -WASTEE-E-ERROR, file currently locked by another user The teed logs may be identified (and distinguished from primary logs) using WASD_SERVER_LOGS:*_%%%%%%%%.LOG Both the teed log file and primary log file have a %WASTEE-I-OPEN dd-mmm-yyyy hh:mm:ss timestamp inserted each time a log file is opened (i.e. on interval rollover and server startup), and a complementary %WASTEE-I-CLOSE at exit. Intervals supported; DAILY, WEEKLY (default), MONTHLY. Interval can be changed from default by logical name WASTEE_INTERVAL. Weekly days are 1..7 (Mon..Sun) defaulting to Mon. The WASTEE_INTERVAL logical name will accept a digit representing the weekday on which to rollover the log. For example, to rollover on Saturday $ DEFINE /SYSTEM WASTEE_INTERVAL WEEKLY6 The log file location may be specified using logical name WASTEE_LOCATION. $ DEFINE /SYSTEM WASTEE_LOCATION WASD_ROOT:[LOG_SERVER] NOTE: just to emphasise; unlike the regular process logs where a new file is created each time the [STARTUP]STARTUP.COM procedure is executed, these teed logs accumulate any and all startups within the interval being logged. The regular server process log is also populated. The PIPE introduces some latency to this that the Server Statistics process Output report cannot control. COMMAND-LINE ------------ When outside of a PIPE environment the utility merely exits without a qualifier. All but the /ppf qualifier are intended as general command-line operation. /next display next interval log file name /ppf defines job-level logicals containing process-permanent file names /this display current interval log file name /version display utility version information PIPED WASD LOGS --------------- To WAStee WASD server process logs prepend the following TEN lines to WASD_ROOT:[STARTUP]STARTUP_SERVER.COM (despite the "DO NOT MODIFY THIS PROCEDURE!" :-) so it looks like this: $! ~~~~ see WASD_ROOT:[SRC.UTILS]WASTEE.C ~~~ $ if f$trnlnm("SYS$PIPE") .eqs. "" $ then $ arch_name = f$edit(f$getsyi("arch_name"),"upcase") $ if arch_name .eqs. "ALPHA" then arch_name = "axp" $ wastee = "$wasd_root:[startup]wastee_''arch_name'.exe" $ wastee /ppf $ pipe @WASD_ROOT:[STARTUP]STARTUP_SERVER.COM | wastee $ exit $ endif $!----------------------------------------------------------------------------- $! STARTUP_SERVER.COM 8< snip 8< BUILD DETAILS ------------- $ @BUILD_WASTEE BUILD !compile+link $ @BUILD_WASTEE LINK !link-only NOTE: links executable as WASD_ROOT:[STARTUP]WASTEE_.EXE 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 HISTORY --------------- 20-MAR-2021 MGD v1.1.0, refinements based on field-test 10-MAR-2021 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.1.0" #define SOFTWARENM "WASTEE" #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 #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include #include #include int louterrno; char interval[] = "weekly1"; FILE *lout; void WeekDay (int64*, ushort[], int, int); void InaMinute (); char* LogName (ushort[]); void LogTime (int64*, ushort[], int); void SetProcessName (); void WASteeExit (); void WASteeLogFileName (char*); int WASteePPF(); void WASteeStamp (char*); /*****************************************************************************/ /* */ main (int argc, char** argv) { static char record [2048]; if (argc > 1) { if (!strcasecmp(argv[1],"/ppf")) { /* set process name only if NOT interactive */ if (WASteePPF ()) SetProcessName ("WASD:pipe"); } else if (!strcasecmp(argv[1],"/this")) { ushort time7 [7]; LogTime (0, time7, 0); if (interval[0] == '?') exit (SS$_IVTIME); fprintf (stdout, "%%%s-I-%s, %s\n", SOFTWARENM, interval, LogName(time7)); } else if (!strcasecmp(argv[1],"/next")) { ushort time7 [7]; LogTime (0, time7, 1); if (interval[0] == '?') exit (SS$_IVTIME); fprintf (stdout, "%%%s-I-%s, %s\n", SOFTWARENM, interval, LogName(time7)); } else if (!strcasecmp(argv[1],"/version")) puts (SOFTWAREID); exit (SS$_NORMAL); } else if (!getenv ("SYS$PIPE")) exit (SS$_NORMAL); atexit (WASteeExit); SetProcessName ("WAStee"); InaMinute (); if (interval[0] == '?') exit (SS$_IVTIME); for (;;) { if (!fgets (record, sizeof(record), stdin)) break; if (fputs (record, stdout) == EOF) break; fsync (fileno(stdout)); if (lout) { if (fputs (record, lout) == EOF) break; fsync (fileno(lout)); } } exit (vaxc$errno); } /*****************************************************************************/ /* On image exit put a timestamp into the log(s). */ void WASteeExit () { WASteeStamp ("CLOSE"); if (lout) fclose (lout); } /*****************************************************************************/ /* Put a timestamp into both primary and teed logs. */ void WASteeStamp (char *cptr) { static char stamp [48+256]; static $DESCRIPTOR (StampDsc, stamp); static $DESCRIPTOR (StampFaoDsc, "%!AZ-!AZ-!AZ, !20%D !AZ\n\0"); sys$fao (&StampFaoDsc, 0, &StampDsc, SOFTWARENM, louterrno ? "E" : "I", cptr, 0, LogName(NULL)); fputs (stamp, stdout); fsync (fileno(stdout)); fputs (stamp, lout); fsync (fileno(lout)); } /*****************************************************************************/ /* Called once a minute. */ void InaMinute () { static ulong SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; static int64 d1min64 = -600000000; /* one minute delta */ static int newlog, status, phour = -1, pday = -1, pmonth = -1; static int64 time64; static ushort time7 [7], roll7 [7]; static char *lname; sys$gettim (&time64); sys$numtim (&time7, &time64); if (time7[3] != phour) { phour = time7[3]; LogTime (&time64, roll7, 0); newlog = ((pmonth < 0 && pday < 0) || (pmonth != roll7[1] || pday != roll7[2])); if (newlog) { pmonth = roll7[1]; pday = roll7[2]; lname = LogName (roll7); sys$setprv (1, &SysPrvMask, 0, 0); if (lout) { WASteeStamp ("CLOSE"); fclose (lout); } if (!(lout = fopen (lname, "a", "shr=get"))) louterrno = vaxc$errno; sys$setprv (0, &SysPrvMask, 0, 0); WASteeStamp ("OPEN"); if (louterrno) fprintf (stdout, "-%s-E-ERROR, %s\n", SOFTWARENM, strerror(EVMSERR, louterrno)); else WASteeLogFileName (lname); } } status = sys$setimr (0, &d1min64, InaMinute, 0, 0); if (!(status & 1)) exit (status); } /*****************************************************************************/ /* Generate and return a pointer to a log file name. */ char* LogName (ushort time7ptr[]) { static ushort SyiNodeLength; static int SyiNodeNameItem = SYI$_NODENAME; static char SyiNodeName [16]; static $DESCRIPTOR (SyiNodeNameDsc, SyiNodeName); static char logname [256]; int status; char *location; ushort slen; if (!SyiNodeName[0]) { status = lib$getsyi (&SyiNodeNameItem, 0, &SyiNodeNameDsc, &slen, 0, 0); if (!(status & 1)) exit (status); SyiNodeName[slen] = '\0'; } if (!time7ptr) return (logname); if (!(location = getenv ("WASTEE_LOCATION"))) location = "WASD_SERVER_LOGS:"; sprintf (logname, "%s%s_%d%02d%02d.LOG", location, SyiNodeName, time7ptr[0], time7ptr[1], time7ptr[2]); return (logname); } /****************************************************************************/ /* Calculate the data/time of the current/next log and populate the vector time. */ void LogTime (int64 *time64ptr, ushort time7ptr[], int next) { static int64 d1day64 = (int64)-600000000 * 60 * 24; /* one day delta */ int64 time64; char *cptr; /*********/ /* begin */ /*********/ if (time64ptr) time64 = *time64ptr; else sys$gettim (&time64); sys$numtim (time7ptr, &time64); if (!(cptr = getenv ("WASTEE_INTERVAL"))) cptr = "W1"; if (*cptr == 'D') { strcpy (interval, "DAILY"); if (next) time64 -= d1day64; sys$numtim (time7ptr, &time64); } else if (*cptr == 'W') { /* does the weekday also specify a day of the week integer */ int dayof = 1; while (*cptr && !isdigit(*cptr)) cptr++; if (*cptr >= '1' && *cptr <= '7') dayof = *cptr - '0'; strcpy (interval, "WEEKLY"); interval[6] = dayof + '0'; WeekDay (&time64, time7ptr, dayof, next); } else if (*cptr == 'M') { int pmonth; strcpy (interval, "MONTHLY"); /* add a day until the month changes (q&d) */ pmonth = time7ptr[1]; for (;;) { if (next) time64 -= d1day64; else time64 += d1day64; sys$numtim (time7ptr, &time64); if (time7ptr[1] != pmonth) break; } if (!next) time64 -= d1day64; } else strcpy (interval, "?"); } /*****************************************************************************/ /* Get the day of the week (1..7) and calculate the date (year/month/day) adjusted for that day of the week. Adjust the numeric time structure provided. Return the day of the month, as calculated by the specified weekday. */ void WeekDay (int64 *time64ptr, ushort time7ptr[], int dayof, int next) { static int LibDayOfWeek = LIB$K_DAY_OF_WEEK; static int64 d1day64 = (int64)-600000000 * 60 * 24; /* one day delta */ static int64 dweek64; static int aday, wday; static int64 ptime64; if (ptime64 == *time64ptr) { /* same time as previous call, no need to recalculate */ sys$numtim (time7ptr, &dweek64); return; } ptime64 = *time64ptr; /* return 1 for Mon, 2 for Tue .. 7 for Sun */ lib$cvt_from_internal_time (&LibDayOfWeek, &wday, time64ptr); if (wday < dayof) { aday = dayof - wday - 7; if (next) aday += 7; dweek64 = *time64ptr - (d1day64 * aday); } else { aday = wday - dayof; if (next) aday -= 7; dweek64 = *time64ptr + (d1day64 * aday); } sys$numtim (time7ptr, &dweek64); } /****************************************************************************/ /* Set the process name so it's unique. Must be a maximum of ten characters. */ void SetProcessName (char *name) { static unsigned long JpiPidItem = JPI$_PID; static char PrcNam [16]; static $DESCRIPTOR (PrcNamDsc, PrcNam); int status; unsigned long ScriptPid; struct dsc$descriptor_s *dscptr; /*********/ /* begin */ /*********/ lib$getjpi (&JpiPidItem, 0, 0, &ScriptPid, 0, 0); dscptr = &PrcNamDsc; dscptr->dsc$w_length = sprintf (PrcNam, "%s_%4.4X", name, ScriptPid & 0xffff); status = sys$setprn (dscptr); if (!(status & 1)) exit (status); } /*****************************************************************************/ /* Get the values of the SYS$INPUT and SYS$OUTPUT process-permanent files (PPFs) and create job-level logical names containing those values that can be used by the HTTPd server for admin purposes. Derived from WASD_ROOT:[SRC.HTTPD]HTTPD.C HttpdProcessInfo(). */ int WASteePPF () { static $DESCRIPTOR (InpNameDsc, "WASTEE_SYS$INPUT"); static $DESCRIPTOR (OutNameDsc, "WASTEE_SYS$OUTPUT"); static $DESCRIPTOR (LnmJobDsc, "LNM$JOB"); static $DESCRIPTOR (LnmProcessDsc, "LNM$PROCESS"); static $DESCRIPTOR (SysOutputDsc, "SYS$OUTPUT"); static $DESCRIPTOR (SysInputDsc, "SYS$INPUT"); static int JpiModeItem = JPI$_MODE; static ulong JpiMode; static ushort InpLen, OutLen, ValueLen; static char LogValue [64], SysInput [256], SysOutput [256]; static struct _ile3 TrnLnmItem [] = { { sizeof(LogValue)-1, LNM$_STRING, LogValue, &ValueLen }, { 0,0,0,0 } }; static struct _ile3 CreLnmInpItem [] = { { 0, LNM$_STRING, SysInput, 0 }, { 0,0,0,0 } }; static struct _ile3 CreLnmOutItem [] = { { 0, LNM$_STRING, SysOutput, 0 }, { 0,0,0,0 } }; int status, inplen, outlen; struct FAB aFab; struct NAM aNam; /*********/ /* begin */ /*********/ /* check process mode */ status = lib$getjpi (&JpiModeItem, 0, 0, &JpiMode, 0, 0); if (!(status & 1)) exit (status); /* if interactive then do not proceed */ if (JpiMode == 3) return (0); status = sys$trnlnm (0, &LnmProcessDsc, &SysInputDsc, 0, &TrnLnmItem); if (!(status & 1)) exit (status); if (*(ushort*)LogValue == 0x001b) { aFab = cc$rms_fab; aFab.fab$l_nam = &aNam; aFab.fab$b_fac = FAB$M_GET; aFab.fab$w_ifi = *(ushort*)(LogValue+2) | FAB$M_PPF_IND; aNam = cc$rms_nam; aNam.nam$l_rsa = SysInput; aNam.nam$b_rss = sizeof(SysInput)-1; status = sys$display (&aFab, 0, 0); SysInput[inplen = aNam.nam$b_rsl] = '\0'; if (!(status & 1)) exit (status); } else strcpy (SysInput, "PPF?"); status = sys$trnlnm (0, &LnmProcessDsc, &SysOutputDsc, 0, &TrnLnmItem); if (!(status & 1)) exit (status); if (*(ushort*)LogValue == 0x001b) { aFab = cc$rms_fab; aFab.fab$l_nam = &aNam; aFab.fab$b_fac = FAB$M_GET; aFab.fab$w_ifi = *(ushort*)(LogValue+2) | FAB$M_PPF_IND; aNam = cc$rms_nam; aNam.nam$l_rsa = SysOutput; aNam.nam$b_rss = sizeof(SysOutput)-1; status = sys$display (&aFab, 0, 0); SysOutput[outlen = aNam.nam$b_rsl] = '\0'; if (!(status & 1)) exit (status); } else strcpy (SysOutput, "PPF?"); CreLnmInpItem[0].ile3$w_length = inplen; status = sys$crelnm (0, &LnmJobDsc, &InpNameDsc, 0, &CreLnmInpItem); if (!(status & 1)) exit (status); CreLnmOutItem[0].ile3$w_length = outlen; status = sys$crelnm (0, &LnmJobDsc, &OutNameDsc, 0, &CreLnmOutItem); if (!(status & 1)) exit (status); return (1); } /*****************************************************************************/ /* Define a job-level logical name containing the teed log file name. Can be used by the HTTPd server admin report. */ void WASteeLogFileName (char *lname) { static $DESCRIPTOR (LnmJobDsc, "LNM$JOB"); static $DESCRIPTOR (LogNameDsc, "WASTEE_LOGFILE"); static struct _ile3 CreLnmItem [] = { { 0, LNM$_STRING, 0, 0 }, { 0,0,0,0 } }; int status; /*********/ /* begin */ /*********/ CreLnmItem[0].ile3$w_length = strlen(lname); CreLnmItem[0].ile3$ps_bufaddr = lname; status = sys$crelnm (0, &LnmJobDsc, &LogNameDsc, 0, &CreLnmItem); if (!(status & 1)) exit (status); } /*****************************************************************************/