651 lines
17 KiB
C
651 lines
17 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
|
|
|
|
Added command history logging. Due to problems this is currently disabled by
|
|
writeHistory = 0 and code in SetWriteHistory
|
|
|
|
Mark Koennecke, July 2010
|
|
--------------------------------------------------------------------------*/
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <tcl.h>
|
|
#include "splitter.h"
|
|
#include "sics.h"
|
|
#include "ifile.h"
|
|
#include "sicsvar.h"
|
|
#include "scaldate.h"
|
|
#include "network.h"
|
|
#include "circular.h"
|
|
#include "stptok.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];
|
|
/*------------------------- command history logging --------------------*/
|
|
static char pHistoryFilter[1024] = "";
|
|
static int writeHistory = 0;
|
|
/*-------------------- 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); /* strlcpy is not correct here */
|
|
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;
|
|
}
|
|
}
|
|
|
|
now = time(NULL);
|
|
|
|
/* create tail buffer as needed */
|
|
if (!pTail) {
|
|
pTail = createCircular(MAXTAIL, free);
|
|
}
|
|
|
|
doStamp = 0;
|
|
doStampId = 0;
|
|
|
|
if (iCompact == 1) {
|
|
nowTm = localtime(&now);
|
|
strftime(stamp1, sizeof stamp1, "%H:%M:%S", nowTm);
|
|
if (prompt == NULL)
|
|
prompt = "";
|
|
if (id == NOID) {
|
|
if (!prompt)
|
|
prompt = " ";
|
|
snprintf(buffer, sizeof buffer, "%s %s ", stamp1, prompt);
|
|
} else {
|
|
snprintf(buffer, sizeof buffer, "%s %2.0d| ", stamp1, id);
|
|
}
|
|
prompt = buffer;
|
|
} else 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 (iCompact > 1) {
|
|
if (id != lastId) {
|
|
lastId = id;
|
|
doStampId = 1;
|
|
}
|
|
if (!prompt) {
|
|
prompt = "";
|
|
} else {
|
|
snprintf(buffer, sizeof buffer, "%s ", prompt);
|
|
prompt = buffer;
|
|
}
|
|
}
|
|
|
|
if (iCompact > 1) { /* 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, strdup(pCopy));
|
|
nextCircular(pTail);
|
|
}
|
|
lastId = id;
|
|
if(pCopy != NULL){
|
|
free(pCopy);
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
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 (pPtr != NULL) {
|
|
SCPureSockWrite(pCon, pPtr, eWarning);
|
|
}
|
|
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 */
|
|
snprintf(pBueffel,1024, "%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) {
|
|
snprintf(pBueffel,1024, "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);
|
|
snprintf(pBueffel,1024, "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;
|
|
}
|
|
/**
|
|
* handle write
|
|
*/
|
|
|
|
if (strcmp(argv[1], "write") == 0) {
|
|
Arg2Text(argc-2, &argv[2],pBueffel,sizeof(pBueffel));
|
|
WriteToCommandLogId(NULL, pCon->sockHandle,pBueffel);
|
|
SCSendOK(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);
|
|
snprintf(pBueffel,1023, "%s", argv[2]);
|
|
|
|
} else {
|
|
snprintf(pBueffel,1023, "%s/%s", pPtr, argv[2]);
|
|
}
|
|
fd = fopen(pBueffel, "w");
|
|
if (!fd) {
|
|
snprintf(pBueffel,1023, "ERROR: cannot open %s/%s for writing", pPtr,
|
|
argv[2]);
|
|
SCWrite(pCon, pBueffel, eError);
|
|
return 0;
|
|
}
|
|
strlcpy(pFile, argv[2],255);
|
|
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], "history") == 0) {
|
|
if (argc > 2) {
|
|
iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iVal);
|
|
if (iRet != TCL_OK) {
|
|
SCWrite(pCon, "ERROR: failed to convert new history to number",
|
|
eError);
|
|
return 0;
|
|
}
|
|
writeHistory = iVal;
|
|
}
|
|
SCPrintf(pCon, eValue, "%s.writeHistory = %d", argv[0], writeHistory);
|
|
return 1;
|
|
} else if (strcmp(argv[1], "historyfilter") == 0) {
|
|
if (argc > 2) {
|
|
if(strlen(argv[2]) < 1024){
|
|
strcpy(pHistoryFilter,argv[2]);
|
|
} else {
|
|
SCWrite(pCon,"ERROR: history filter to long", eError);
|
|
return 0;
|
|
}
|
|
SCSendOK(pCon);
|
|
return 1;
|
|
}
|
|
SCPrintf(pCon, eValue, "%s.historyFilter = %s", argv[0], pHistoryFilter);
|
|
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], (int)iCompact);
|
|
return 1;
|
|
} else if (strcmp(argv[1], "close") == 0) { /* close command */
|
|
fclose(fd);
|
|
fd = NULL;
|
|
SCSendOK(pCon);
|
|
return 1;
|
|
}
|
|
snprintf(pBueffel, 1024,"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);
|
|
}
|
|
/*-------------------------------------------------------------------------*/
|
|
void CommandLogInit(void)
|
|
{
|
|
AddCommand(pServ->pSics, "commandlog", CommandLog, CommandLogClose, NULL);
|
|
}
|
|
/*---------------------- History -----------------------------------------*/
|
|
static FILE *comHistory = NULL;
|
|
/*-----------------------------------------------------------------------*/
|
|
static int openHistoryLog()
|
|
{
|
|
char *fileName = NULL;
|
|
char fileBuffer[1024];
|
|
time_t iDate;
|
|
struct tm *psTime;
|
|
|
|
|
|
if (comHistory == NULL) {
|
|
fileName = IFindOption(pSICSOptions, "historylog");
|
|
if (fileName != NULL) {
|
|
strlcpy(fileBuffer, fileName,1024);
|
|
} else {
|
|
iDate = time(NULL);
|
|
psTime = localtime(&iDate);
|
|
fileBuffer[0] = '\0';
|
|
fileName = getenv("HOME");
|
|
if (fileName != NULL) {
|
|
snprintf(fileBuffer, 1023, "%s/log/comhistory%4.4d.log",
|
|
fileName, psTime->tm_year + 1900);
|
|
}
|
|
}
|
|
comHistory = fopen(fileBuffer, "a+");
|
|
}
|
|
if (comHistory == NULL) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------
|
|
* This is to suppress certain stuff from the history log in order to stop it
|
|
* from filling with garbage
|
|
-----------------------------------------------------------------------------*/
|
|
static int historyFilter(char *command)
|
|
{
|
|
static char *toRemove[] = {"sct", "hupdate","contextdo","transact", NULL};
|
|
char token[50];
|
|
char *pPtr = NULL;
|
|
int i = 0;
|
|
|
|
while(toRemove[i] != NULL){
|
|
if(strstr(command,toRemove[i]) != NULL){
|
|
return 0;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
pPtr = pHistoryFilter;
|
|
while((pPtr = stptok(pPtr,token,50,":")) != NULL){
|
|
if(strstr(command,token) != NULL){
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
void WriteCommandHistory(char *txt)
|
|
{
|
|
if(writeHistory == 0){
|
|
return;
|
|
}
|
|
if(comHistory == NULL){
|
|
openHistoryLog();
|
|
}
|
|
if(comHistory != NULL){
|
|
if(historyFilter(txt)){
|
|
fprintf(comHistory,"%s\n", txt);
|
|
}
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
void SetWriteHistory(int i)
|
|
{
|
|
/* writeHistory = i;*/
|
|
writeHistory = 0;
|
|
}
|