/**
* \file log.c
*
* \brief Logging for PennMUSH.
*
*
*/
#include "copyrite.h"
#include "config.h"
#include <stdio.h>
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#endif
#include <time.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#include "conf.h"
#include "externs.h"
#include "flags.h"
#include "dbdefs.h"
#include "htab.h"
#include "bufferq.h"
#include "log.h"
#include "confmagic.h"
static char *quick_unparse(dbref object);
static void start_log(FILE ** fp, const char *filename);
static void end_log(const char *filename);
BUFFERQ *activity_bq = NULL;
HASHTAB htab_logfiles; /**< Hash table of logfile names and descriptors */
/* log file pointers */
FILE *connlog_fp; /**< Connect log */
FILE *checklog_fp; /**< Checkpoint log */
FILE *wizlog_fp; /**< Wizard log */
FILE *tracelog_fp; /**< Trace log */
FILE *cmdlog_fp; /**< Command log */
static char *
quick_unparse(dbref object)
{
static char buff[BUFFER_LEN], *bp;
switch (object) {
case NOTHING:
strcpy(buff, T("*NOTHING*"));
break;
case AMBIGUOUS:
strcpy(buff, T("*VARIABLE*"));
break;
case HOME:
strcpy(buff, T("*HOME*"));
break;
default:
bp = buff;
safe_format(buff, &bp, "%s(#%d%s)",
Name(object), object, unparse_flags(object, GOD));
*bp = '\0';
}
return buff;
}
static void
start_log(FILE ** fp, const char *filename)
{
char newfilename[256] = "\0";
static int ht_initialized = 0;
FILE *f;
if (!filename || !*filename) {
*fp = stderr;
} else {
if (!ht_initialized) {
hashinit(&htab_logfiles, 8, sizeof(FILE *));
ht_initialized = 1;
}
if ((f = (FILE *) hashfind(strupper(filename), &htab_logfiles))) {
/* We've already opened this file, so just use that pointer */
*fp = f;
} else {
/* Must use a buffer for MacOS file path conversion */
strncpy(newfilename, filename, 256);
*fp = fopen(newfilename, "a");
if (*fp == NULL) {
fprintf(stderr, T("WARNING: cannot open log %s\n"), newfilename);
*fp = stderr;
} else {
hashadd(strupper(filename), (void *) *fp, &htab_logfiles);
fprintf(*fp, "START OF LOG.\n");
fflush(*fp);
}
}
}
}
/** Open all logfiles.
*/
void
start_all_logs(void)
{
start_log(&connlog_fp, CONNLOG);
start_log(&checklog_fp, CHECKLOG);
start_log(&wizlog_fp, WIZLOG);
start_log(&tracelog_fp, TRACELOG);
start_log(&cmdlog_fp, CMDLOG);
}
/** Redirect stderr to a error log file.
* Should be called after start_all_logs().
* \param log name of logfile to redirect stderr to.
*/
void
redirect_stderr(void)
{
FILE *errlog_fp;
fprintf(stderr, T("Redirecting stderr to %s\n"), ERRLOG);
errlog_fp = fopen(ERRLOG, "a");
if (!errlog_fp) {
fprintf(stderr, T("Unable to open %s. Error output to stderr.\n"), ERRLOG);
} else {
if (!freopen(ERRLOG, "a", stderr)) {
printf(T("Ack! Failed reopening stderr!"));
exit(1);
}
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
fclose(errlog_fp);
}
}
static void
end_log(const char *filename)
{
FILE *fp;
if (!filename || !*filename)
return;
if ((fp = (FILE *) hashfind(strupper(filename), &htab_logfiles))) {
fprintf(fp, "END OF LOG.\n");
fflush(fp);
fclose(fp);
hashdelete(strupper(filename), &htab_logfiles);
}
}
/** Close all logfiles.
*/
void
end_all_logs(void)
{
char *name, *next;
name = hash_firstentry_key(&htab_logfiles);
while (name) {
next = hash_nextentry_key(&htab_logfiles);
end_log(name);
name = next;
}
}
/** Log a raw message.
* take a log type and format list and args, write to appropriate logfile.
* log types are defined in log.h
* \param logtype type of log to print message to.
* \param fmt format string for message.
*/
void WIN32_CDECL
do_rawlog(int logtype, const char *fmt, ...)
{
struct tm *ttm;
char timebuf[18];
char tbuf1[BUFFER_LEN + 50];
va_list args;
FILE *f = NULL;
va_start(args, fmt);
#ifdef HAS_VSNPRINTF
(void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
#else
(void) vsprintf(tbuf1, fmt, args);
#endif
tbuf1[BUFFER_LEN - 1] = '\0';
va_end(args);
ttm = localtime(&mudtime);
strftime(timebuf, sizeof timebuf, "[%m/%d %H:%M:%S]", ttm);
switch (logtype) {
case LT_ERR:
f = stderr;
break;
case LT_HUH:
case LT_CMD:
start_log(&cmdlog_fp, CMDLOG);
f = cmdlog_fp;
break;
case LT_WIZ:
start_log(&wizlog_fp, WIZLOG);
f = wizlog_fp;
break;
case LT_CONN:
start_log(&connlog_fp, CONNLOG);
f = connlog_fp;
break;
case LT_TRACE:
start_log(&tracelog_fp, TRACELOG);
f = tracelog_fp;
break;
case LT_CHECK:
start_log(&checklog_fp, CHECKLOG);
f = checklog_fp;
break;
default:
f = stderr;
break;
}
fprintf(f, "%s %s\n", timebuf, tbuf1);
fflush(f);
}
/** Log a message, with useful information.
* take a log type and format list and args, write to appropriate logfile.
* log types are defined in log.h. Unlike do_rawlog, this version
* tags messages with prefixes, and uses dbref information passed to it.
* \param logtype type of log to print message to.
* \param player dbref that generated the log message.
* \param object second dbref involved in log message (e.g. force logs)
* \param fmt mesage format string.
*/
void WIN32_CDECL
do_log(int logtype, dbref player, dbref object, const char *fmt, ...)
{
/* tbuf1 had 50 extra chars because we might pass this function
* both a label string and a command which could be up to BUFFER_LEN
* in length - for example, when logging @forces
*/
char tbuf1[BUFFER_LEN + 50];
va_list args;
char unp1[BUFFER_LEN], unp2[BUFFER_LEN];
va_start(args, fmt);
#ifdef HAS_VSNPRINTF
(void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
#else
(void) vsprintf(tbuf1, fmt, args);
#endif
va_end(args);
switch (logtype) {
case LT_ERR:
do_rawlog(logtype, "RPT: %s", tbuf1);
break;
case LT_CMD:
strcpy(unp1, quick_unparse(player));
if (GoodObject(object)) {
strcpy(unp2, quick_unparse(object));
do_rawlog(logtype, T("CMD: %s %s / %s: %s"),
(Suspect(player) ? "SUSPECT" : ""), unp1, unp2, tbuf1);
} else {
strcpy(unp2, quick_unparse(Location(player)));
do_rawlog(logtype, T("CMD: %s %s in %s: %s"),
(Suspect(player) ? "SUSPECT" : ""), unp1, unp2, tbuf1);
}
break;
case LT_WIZ:
strcpy(unp1, quick_unparse(player));
if (GoodObject(object)) {
strcpy(unp2, quick_unparse(object));
do_rawlog(logtype, "WIZ: %s --> %s: %s", unp1, unp2, tbuf1);
} else {
do_rawlog(logtype, "WIZ: %s: %s", unp1, tbuf1);
}
break;
case LT_CONN:
do_rawlog(logtype, "NET: %s", tbuf1);
break;
case LT_TRACE:
do_rawlog(logtype, "TRC: %s", tbuf1);
break;
case LT_CHECK:
do_rawlog(logtype, "%s", tbuf1);
break;
case LT_HUH:
if (!controls(player, Location(player))) {
strcpy(unp1, quick_unparse(player));
strcpy(unp2, quick_unparse(Location(player)));
do_rawlog(logtype, T("HUH: %s in %s [%s]: %s"),
unp1, unp2,
(GoodObject(Location(player))) ?
Name(Owner(Location(player))) : T("bad object"), tbuf1);
}
break;
default:
do_rawlog(LT_ERR, "ERR: %s", tbuf1);
}
}
/** Wipe out a game log. This is intended for those emergencies where
* the log has grown out of bounds, overflowing the disk quota, etc.
* Because someone with the god password can use this command to wipe
* out 'intrusion' traces, we also require the log_wipe_passwd given
* in mush.cnf
* \param player the enactor.
* \param logtype type of log to wipe.
* \param str password for wiping logs.
*/
void
do_logwipe(dbref player, int logtype, char *str)
{
if (strcmp(str, LOG_WIPE_PASSWD)) {
const char *lname;
switch (logtype) {
case LT_CONN:
lname = "connection";
break;
case LT_CHECK:
lname = "checkpoint";
break;
case LT_CMD:
lname = "command";
break;
case LT_TRACE:
lname = "trace";
break;
case LT_WIZ:
lname = "wizard";
break;
default:
lname = "unspecified";
}
notify(player, T("Wrong password."));
do_log(LT_WIZ, player, NOTHING,
T("Invalid attempt to wipe the %s log, password %s"), lname, str);
return;
}
switch (logtype) {
case LT_CONN:
end_log(CONNLOG);
unlink(CONNLOG);
start_log(&connlog_fp, CONNLOG);
do_log(LT_ERR, player, NOTHING, T("Connect log wiped."));
break;
case LT_CHECK:
end_log(CHECKLOG);
unlink(CHECKLOG);
start_log(&checklog_fp, CHECKLOG);
do_log(LT_ERR, player, NOTHING, T("Checkpoint log wiped."));
break;
case LT_CMD:
end_log(CMDLOG);
unlink(CMDLOG);
start_log(&cmdlog_fp, CMDLOG);
do_log(LT_ERR, player, NOTHING, T("Command log wiped."));
break;
case LT_TRACE:
end_log(TRACELOG);
unlink(TRACELOG);
start_log(&tracelog_fp, TRACELOG);
do_log(LT_ERR, player, NOTHING, T("Trace log wiped."));
break;
case LT_WIZ:
end_log(WIZLOG);
unlink(WIZLOG);
start_log(&wizlog_fp, WIZLOG);
do_log(LT_ERR, player, NOTHING, T("Wizard log wiped."));
break;
default:
notify(player, T("That is not a valid log."));
return;
}
notify(player, T("Log wiped."));
}
/** Log a message to the activity log.
* \param type message type (an LA_* constant)
* \param player object responsible for the message.
* \param action message to log.
*/
void
log_activity(int type, dbref player, const char *action)
{
if (!activity_bq)
activity_bq = allocate_bufferq(ACTIVITY_LOG_SIZE);
add_to_bufferq(activity_bq, type, player, action);
}
/** Retrieve the last logged message from the activity log.
* \return last logged message or an empty string.
*/
const char *
last_activity(void)
{
if (!activity_bq)
return "";
else
return BufferQLast(activity_bq);
}
/** Retrieve the type of the last logged message from the activity log.
* \return last type of last logged message or -1.
*/
int
last_activity_type(void)
{
if (!activity_bq)
return -1;
else
return BufferQLastType(activity_bq);
}
/** Dump out (to a player or the error log) the activity buffer queue.
* \param player player to receive notification, if notifying.
* \param num_lines number of lines of buffer to dump (0 = all).
* \param dump if 1, dump to error log; if 0, notify player.
*/
void
notify_activity(dbref player, int num_lines, int dump)
{
int type;
dbref plr;
time_t timestamp;
char *buf;
char *p = NULL;
char *stamp;
int skip;
const char *typestr;
if (!activity_bq)
return;
if (dump || !num_lines)
num_lines = BufferQNum(activity_bq);
skip = BufferQNum(activity_bq) - num_lines;
if (dump)
do_rawlog(LT_ERR, "Dumping recent activity:");
else
notify(player, T("GAME: Recall from activity log"));
do {
buf = iter_bufferq(activity_bq, &p, &plr, &type, ×tamp);
if (skip <= 0) {
if (buf) {
stamp = show_time(timestamp, 0);
switch (type) {
case LA_CMD:
typestr = "CMD";
break;
case LA_PE:
typestr = "EXP";
break;
case LA_LOCK:
typestr = "LCK";
break;
default:
typestr = "???";
break;
}
if (dump)
do_rawlog(LT_ERR, "[%s/#%d/%s] %s", stamp, plr, typestr, buf);
else
notify_format(player, "[%s/#%d/%s] %s", stamp, plr, typestr, buf);
}
}
skip--;
} while (buf);
if (!dump)
notify(player, T("GAME: End recall"));
}