546 lines
15 KiB
C
546 lines
15 KiB
C
/*--------------------------------------------------------------------------
|
|
C O M M A N D L O G
|
|
|
|
A much requested facility for writing only user and manager level commands
|
|
in a transcript file. This is it.
|
|
|
|
Mark Koennecke, June 1998
|
|
|
|
Extended to support Heinz Heers autolog-file
|
|
Mark Koennecke, April-May 1999
|
|
|
|
Added a tail facility
|
|
Mark Koennecke, October 1999
|
|
|
|
Added compact mode:
|
|
- timestamps look different and are omitted if no other text is written
|
|
- socket number information is written on the timestamp line
|
|
|
|
--------------------------------------------------------------------------*/
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <tcl.h>
|
|
#include "sics.h"
|
|
#include "ifile.h"
|
|
#include "sicsvar.h"
|
|
#include "scaldate.h"
|
|
#include "network.h"
|
|
#include "circular.h"
|
|
|
|
|
|
/* in conman.c */
|
|
int TelnetWrite(mkChannel *pSock, char *pText);
|
|
/*-------------------- the command log file pointer ---------------------*/
|
|
static FILE *fd = NULL;
|
|
static FILE *fauto = NULL;
|
|
static char pFile[256];
|
|
/*-------------------- the tail buffer ---------------------------------*/
|
|
static pCircular pTail = NULL;
|
|
#define MAXTAIL 1000
|
|
#define NOID -1964
|
|
static time_t lastStamp = 0;
|
|
static time_t iCompact = 0;
|
|
static time_t tLastWrite = 0;
|
|
char *cmdPrompt=">";
|
|
static int lastId=NOID;
|
|
|
|
static time_t tLogfile = 0;
|
|
static time_t tStamp = 0;
|
|
static int iEnd = 1;
|
|
static int iAutoActive = 0;
|
|
static int iIntervall = 60;
|
|
/*----------------------------------------------------------------------*/
|
|
void WriteToCommandLogId(char *prompt, int id, char *text)
|
|
{
|
|
int l, iPos;
|
|
char *pPtr = NULL, *pCopy = NULL, *strippedText = text;
|
|
struct tm *nowTm;
|
|
time_t now;
|
|
char stamp1[32], stamp2[32], buffer[80];
|
|
int doStamp, doStampId;
|
|
|
|
/* suppress status messages */
|
|
if (strstr(text,"status =") != NULL) {
|
|
return;
|
|
}
|
|
|
|
/* suppress TRANSACTIONFINISHED as well in order to make the WWW
|
|
commandlog work and TRANSACTIONSTART in order to make the logfiles
|
|
shorter
|
|
*/
|
|
if (strstr(text,"TRANSACTIONSTART") != NULL) {
|
|
return;
|
|
}
|
|
if (strstr(text,"TRANSACTIONFINISHED") != NULL) {
|
|
return;
|
|
}
|
|
|
|
/* we make a local copy, stripping off the newline at the
|
|
end. We anyway need a copy later for the circular buffer */
|
|
l = strlen(text);
|
|
pPtr = strrchr(text,'\n');
|
|
if (pPtr != NULL && (pPtr[1]=='\0' || pPtr[2] == '\0')) {
|
|
l = pPtr - text;
|
|
}
|
|
pCopy = malloc(l+1);
|
|
if (pCopy == NULL) return;
|
|
strncpy(pCopy, text, l);
|
|
pCopy[l]='\0';
|
|
|
|
if (prompt == cmdPrompt && iCompact) {
|
|
pPtr = strstr(pCopy, "fulltransact ");
|
|
if (pPtr && pPtr < pCopy+3) {
|
|
strippedText = pPtr + 13;
|
|
}
|
|
pPtr = strstr(pCopy, "transact ");
|
|
if (pPtr && pPtr < pCopy+3) {
|
|
strippedText = pPtr + 9;
|
|
}
|
|
}
|
|
|
|
/* create tail buffer as needed */
|
|
if (!pTail) {
|
|
pTail = createCircular(MAXTAIL,free);
|
|
}
|
|
|
|
now = time(NULL);
|
|
|
|
doStamp = 0;
|
|
doStampId = 0;
|
|
|
|
if (id == NOID) {
|
|
if (!prompt) {
|
|
prompt="";
|
|
} else {
|
|
snprintf(buffer, sizeof buffer, "%s ", prompt);
|
|
prompt = buffer;
|
|
}
|
|
} else if (iCompact == 0) {
|
|
if (!prompt) {
|
|
snprintf(buffer, sizeof buffer, "To sock %d : ", id);
|
|
} else {
|
|
snprintf(buffer, sizeof buffer, "sock %d>%s ", id, prompt);
|
|
}
|
|
prompt = buffer;
|
|
} else {
|
|
if (id != lastId) {
|
|
lastId = id;
|
|
doStampId = 1;
|
|
}
|
|
if (!prompt) {
|
|
prompt="";
|
|
} else {
|
|
snprintf(buffer, sizeof buffer, "%s ", prompt);
|
|
prompt = buffer;
|
|
}
|
|
}
|
|
|
|
if (iCompact > 0) { /* write time stamp */
|
|
if (now/iCompact != lastStamp/iCompact) {
|
|
doStamp = 1;
|
|
doStampId = 1;
|
|
}
|
|
if (doStampId) {
|
|
lastStamp = now;
|
|
nowTm = localtime(&now);
|
|
strftime(stamp1, sizeof stamp1, "=== %H:%M:%S ===", nowTm);
|
|
if (id != NOID) {
|
|
snprintf(stamp2, sizeof stamp2, " socket %d ===", id);
|
|
} else {
|
|
stamp2[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* user file */
|
|
if (fd != NULL) {
|
|
if (doStampId) {
|
|
fprintf(fd,"%s %s\n", stamp1, stamp2);
|
|
}
|
|
fprintf(fd,"%s%s\n", prompt, pCopy);
|
|
}
|
|
|
|
/* automatic file */
|
|
if (fauto != NULL) {
|
|
tLastWrite = now;
|
|
if (doStampId) {
|
|
fprintf(fauto,"%s%s\n", stamp1, stamp2);
|
|
}
|
|
fprintf(fauto,"%s%s\n", prompt, strippedText);
|
|
}
|
|
|
|
/* to all listening sockets. The check is necessary to resolve a shutdown problem */
|
|
if (pServ->pTasker != NULL) {
|
|
if (doStamp) {
|
|
TaskSignal(pServ->pTasker,COMLOG,stamp1);
|
|
}
|
|
TaskSignal(pServ->pTasker,COMLOG,pCopy);
|
|
}
|
|
|
|
/* tail buffer */
|
|
if (pTail != NULL) {
|
|
if (doStamp) {
|
|
setCircular(pTail,strdup(stamp1));
|
|
nextCircular(pTail);
|
|
}
|
|
setCircular(pTail,pCopy);
|
|
nextCircular(pTail);
|
|
}
|
|
lastId = id;
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
void WriteToCommandLog(char *prompt, char *text)
|
|
{
|
|
WriteToCommandLogId(prompt, NOID, text);
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
void WriteToCommandLogCmd(int id, char *text)
|
|
{
|
|
WriteToCommandLogId(cmdPrompt, id, text);
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
int CompactCommandLog(void) {
|
|
return iCompact > 0;
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
static void PrintTail(int iNum, SConnection *pCon)
|
|
{
|
|
char *pPtr = NULL;
|
|
int i;
|
|
|
|
if(pTail == NULL)
|
|
{
|
|
SCWrite(pCon,"Nothing to print",eError);
|
|
return;
|
|
}
|
|
|
|
/* step back */
|
|
for(i = 0; i < iNum; i++)
|
|
{
|
|
previousCircular(pTail);
|
|
}
|
|
|
|
/* now step ahead and print. I have to use a trick here: I do not
|
|
want the tail stuff to show up in log files. Thus I write it
|
|
directly to the connection socket.
|
|
*/
|
|
for(i = 0; i < iNum; i++)
|
|
{
|
|
pPtr = (char *)getCircular(pTail);
|
|
if(pCon->pSock && pPtr != NULL)
|
|
{
|
|
TelnetWrite(pCon->pSock, pPtr);
|
|
}
|
|
nextCircular(pTail);
|
|
}
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
void CLFormatTime(char *pBuffer, int iBufLen)
|
|
{
|
|
time_t iDate;
|
|
struct tm *psTime;
|
|
|
|
/* make time string */
|
|
iDate = time(NULL);
|
|
psTime = localtime(&iDate);
|
|
memset(pBuffer,0,iBufLen);
|
|
strftime(pBuffer,iBufLen,"%Y-%m-%d@%H-%M-%S",psTime);
|
|
}
|
|
/*----------------------------------------------------------------------
|
|
Build an automatically generated log file name and open it.
|
|
*/
|
|
static void AutoLog(void)
|
|
{
|
|
char pBueffel[1024];
|
|
char pTime[80];
|
|
pSicsVariable pInst = NULL;
|
|
char *pPtr = NULL;
|
|
SConnection *pIntern = NULL;
|
|
|
|
if(fauto)
|
|
{
|
|
fclose(fauto);
|
|
fauto = NULL;
|
|
}
|
|
|
|
/* find path */
|
|
pPtr = IFindOption(pSICSOptions,"LogFileDir");
|
|
if(!pPtr)
|
|
{
|
|
pPtr = strdup("~/log");
|
|
printf("WARNING: Required SICS option LogFileDir not found");
|
|
}
|
|
|
|
/* get time */
|
|
CLFormatTime(pTime,79);
|
|
|
|
/* build file name */
|
|
sprintf(pBueffel,"%s/auto%s.log",pPtr,pTime);
|
|
|
|
/* open file */
|
|
fauto = fopen(pBueffel,"w");
|
|
if(!fauto)
|
|
{
|
|
ServerWriteGlobal("ERROR: failed to open autolog file",eError);
|
|
}
|
|
|
|
/* write the instrument name to it for identification */
|
|
pInst = FindVariable(pServ->pSics,"instrument");
|
|
if(pInst)
|
|
{
|
|
sprintf(pBueffel,"Logfile started at instrument %s at %s",
|
|
pInst->text,pTime);
|
|
WriteToCommandLog("SYS>>", pBueffel);
|
|
}
|
|
|
|
/* if a file to execute is configured, execute it */
|
|
pPtr = NULL;
|
|
pPtr = IFindOption(pSICSOptions,"logstartfile");
|
|
if(pPtr != NULL)
|
|
{
|
|
pIntern = SCCreateDummyConnection(pServ->pSics);
|
|
if(!pIntern)
|
|
{
|
|
return;
|
|
}
|
|
SCnoSock(pIntern);
|
|
SCSetRights(pIntern,usUser);
|
|
sprintf(pBueffel,"fileeval %s",pPtr);
|
|
InterpExecute(pServ->pSics,pIntern,pBueffel);
|
|
SCDeleteConnection(pIntern);
|
|
}
|
|
}
|
|
/*----------------------------------------------------------------------
|
|
AutoTask puts a time stamp into the auto log file any hour and
|
|
creates a new log file any 24 hours
|
|
*/
|
|
static int AutoTask(void *pData)
|
|
{
|
|
time_t tNow;
|
|
char pTime[80];
|
|
struct tm *sTime;
|
|
long julian;
|
|
unsigned yr, mo, dd;
|
|
|
|
tNow = time(NULL);
|
|
if(tNow > tLogfile)
|
|
{
|
|
AutoLog();
|
|
sTime = localtime(&tNow);
|
|
/* find next day, do so by converting to julian Date, add one
|
|
and calculate back. The (stolen) julian calculations will
|
|
take care of all the leaps and month and year etc.
|
|
*/
|
|
julian = ymd_to_scalar(sTime->tm_year+1900, sTime->tm_mon+1,
|
|
sTime->tm_mday);
|
|
julian++;
|
|
scalar_to_ymd(julian, &yr, &mo, &dd);
|
|
sTime->tm_sec = 0;
|
|
sTime->tm_min = 1;
|
|
sTime->tm_hour = 0;
|
|
sTime->tm_mday = dd;
|
|
sTime->tm_mon = mo - 1;
|
|
sTime->tm_year = yr - 1900;
|
|
tLogfile = mktime(sTime);
|
|
if(tLogfile < 0)
|
|
tLogfile = tNow + 60*60*24;
|
|
}
|
|
if(tNow > tStamp && iIntervall > 0)
|
|
{
|
|
CLFormatTime(pTime,79);
|
|
WriteToCommandLog("TIMESTAMP>> ",pTime);
|
|
sTime = localtime(&tNow);
|
|
sTime->tm_sec = 0;
|
|
sTime->tm_min += iIntervall;
|
|
if(sTime->tm_min >= 60)
|
|
{
|
|
sTime->tm_min = 0;
|
|
sTime->tm_hour++;
|
|
}
|
|
if(sTime->tm_hour >= 24)
|
|
sTime->tm_hour = 0;
|
|
tStamp = mktime(sTime);
|
|
if((tStamp < 0) || ( (tStamp-tNow) < 100) )
|
|
{
|
|
tStamp = tNow + iIntervall*60;
|
|
}
|
|
if(fauto)
|
|
fflush(fauto);
|
|
}
|
|
|
|
if (fauto && tLastWrite > 0 && tNow > tLastWrite) {
|
|
fflush(fauto);
|
|
tLastWrite = 0;
|
|
}
|
|
return iEnd;
|
|
}
|
|
/*----------- a command to configure the log --------------------------*/
|
|
int CommandLog(SConnection *pCon, SicsInterp *pSics, void *pData,
|
|
int argc, char *argv[])
|
|
{
|
|
char *pPtr = NULL;
|
|
char pBueffel[1024];
|
|
int iVal, iRet;
|
|
|
|
if(argc == 1)
|
|
{
|
|
if(fd)
|
|
{
|
|
sprintf(pBueffel,"Command log ACTIVE at %s",pFile);
|
|
SCWrite(pCon,pBueffel,eValue);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
SCWrite(pCon,"Command logging DISABLED",eValue);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* handle tail */
|
|
strtolower(argv[1]);
|
|
if(strcmp(argv[1],"tail") == 0)
|
|
{
|
|
/* check for optional number of lines argument */
|
|
iVal = 20;
|
|
if(argc >= 3)
|
|
{
|
|
iRet = Tcl_GetInt(pSics->pTcl,argv[2],&iVal);
|
|
if(iRet != TCL_OK)
|
|
iVal = 20;
|
|
}
|
|
PrintTail(iVal,pCon);
|
|
return 1;
|
|
}
|
|
|
|
/* check rights */
|
|
if(!SCMatchRights(pCon,usMugger))
|
|
{
|
|
SCWrite(pCon,"ERROR: only managers may configure the logfile",
|
|
eError);
|
|
SCWrite(pCon,"ERROR: Request refused",eError);
|
|
return 0;
|
|
}
|
|
|
|
/* check no of args */
|
|
if(argc < 2)
|
|
{
|
|
SCWrite(pCon,
|
|
"ERROR: Insufficient number or arguments to commandlog",
|
|
eError);
|
|
return 0;
|
|
}
|
|
|
|
if(strcmp(argv[1],"new") == 0) /* new command */
|
|
{
|
|
if(argc < 3)
|
|
{
|
|
SCWrite(pCon,
|
|
"ERROR: Insufficient number or arguments to commandlog new",
|
|
eError);
|
|
return 0;
|
|
}
|
|
if(fd)
|
|
{
|
|
fclose(fd);
|
|
fd = NULL;
|
|
}
|
|
/* make the filename */
|
|
pPtr = IFindOption(pSICSOptions,"LogFileDir");
|
|
if(!pPtr)
|
|
{
|
|
SCWrite(pCon,"WARNING: no log file directory specified",eWarning);
|
|
sprintf(pBueffel,"%s",argv[2]);
|
|
|
|
}
|
|
else
|
|
{
|
|
sprintf(pBueffel,"%s/%s",pPtr,argv[2]);
|
|
}
|
|
fd = fopen(pBueffel,"w");
|
|
if(!fd)
|
|
{
|
|
sprintf(pBueffel,"ERROR: cannot open %s/%s for writing",pPtr,
|
|
argv[2]);
|
|
SCWrite(pCon,pBueffel,eError);
|
|
return 0;
|
|
}
|
|
strcpy(pFile,argv[2]);
|
|
SCSendOK(pCon);
|
|
return 1;
|
|
}
|
|
else if(strcmp(argv[1],"auto") == 0)
|
|
{
|
|
if(iAutoActive)
|
|
{
|
|
SCWrite(pCon,"ERROR: autologging is already active",eError);
|
|
return 0;
|
|
}
|
|
TaskRegister(pServ->pTasker,
|
|
AutoTask,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
1);
|
|
SCSendOK(pCon);
|
|
iAutoActive = 1;
|
|
return 1;
|
|
}
|
|
else if(strcmp(argv[1],"intervall") == 0)
|
|
{
|
|
if(argc > 2)
|
|
{
|
|
iRet = Tcl_GetInt(pSics->pTcl,argv[2],&iVal);
|
|
if(iRet != TCL_OK)
|
|
{
|
|
SCWrite(pCon,"ERROR: failed to convert new intervall to number",
|
|
eError);
|
|
return 0;
|
|
}
|
|
iIntervall = iVal;
|
|
}
|
|
SCPrintf(pCon,eValue,"%s.intervall [min] = %d", argv[0], iIntervall);
|
|
return 1;
|
|
}
|
|
else if(strcmp(argv[1],"compact") == 0)
|
|
{
|
|
if(argc > 2)
|
|
{
|
|
iCompact = atoi(argv[2]);
|
|
if (iCompact > 0) iIntervall = 0;
|
|
}
|
|
SCPrintf(pCon,eValue,"%s.compact [sec] = %d", argv[0], iCompact);
|
|
return 1;
|
|
}
|
|
else if(strcmp(argv[1],"close") == 0) /* close command */
|
|
{
|
|
fclose(fd);
|
|
fd = NULL;
|
|
SCSendOK(pCon);
|
|
return 1;
|
|
}
|
|
sprintf(pBueffel,"ERROR: subcommand %s to commandlog unknown",
|
|
argv[1]);
|
|
SCWrite(pCon,pBueffel,eError);
|
|
return 0;
|
|
}
|
|
/*-------------------------------------------------------------------------*/
|
|
void CommandLogClose(void *pData)
|
|
{
|
|
if(fd)
|
|
{
|
|
fclose(fd);
|
|
}
|
|
if(fauto)
|
|
fclose(fauto);
|
|
if(pData)
|
|
KillDummy(pData);
|
|
if(pTail)
|
|
deleteCircular(pTail);
|
|
}
|
|
|
|
|
|
|