forked from bip/bip
1
0
Fork 0
bip/src/log.c

1222 lines
27 KiB
C

/*
* $Id: log.c,v 1.56 2005/04/21 06:58:50 nohar Exp $
*
* This file is part of the bip project
* Copyright (C) 2004 Arnaud Cornet and Loïc Gomez
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* See the file "COPYING" for the exact licensing terms.
*/
#include "config.h"
#include "log.h"
#include "irc.h"
#include "util.h"
extern int errno;
extern int log_level;
extern char *conf_log_root;
extern char *conf_log_format;
extern int conf_log;
extern FILE *conf_global_log_file;
int log_set_backlog_offset(log_t *logdata, char *dest);
static int _log_write(log_t *logdata, logfilegroup_t *lf, char *d, char *str);
void logfile_free(logfile_t *lf);
static char *_log_wrap(char *dest, char *line);
#define BOLD_CHAR 0x02
#define LAMESTRING "!bip@bip.bip.bip PRIVMSG "
#define PMSG_ARROW " \002->\002 "
/* TODO: change fatal("out of memory") to cleanup & return NULL */
int check_dir(char *filename, int is_fatal)
{
int err;
struct stat statbuf;
err = stat(filename, &statbuf);
if (err && errno == ENOENT) {
err = mkdir(filename, 0750);
if (err) {
if (is_fatal)
fatal("mkdir(%s) %s", filename,
strerror(errno));
mylog(LOG_ERROR, "mkdir(%s) %s", filename,
strerror(errno));
return 1;
}
} else if (err) {
if (is_fatal)
fatal("stat(%s) %s", filename, strerror(errno));
mylog(LOG_ERROR, "stat(%s) %s", filename,
strerror(errno));
return 1;
} else if (!(statbuf.st_mode & S_IFDIR)) {
if (is_fatal)
fatal("%s is not a directory", filename);
mylog(LOG_ERROR, "%s is not a directory", filename);
return 1;
}
return 0;
}
int check_dir_r(char *dirname)
{
int pos, count = 0;
char *dir, *tmp;
int len = strlen(dirname);
mylog(LOG_DEBUGVERB, "Recursive check of %s engaged", dirname);
tmp = dirname;
dir = (char *)malloc(sizeof(char) * (len + 1));
while (*tmp) {
int slash_ok = 1;
while (*tmp == '/') {
if (slash_ok) {
strncpy(dir + count, "/", 1);
count++;
slash_ok = 0;
}
tmp++;
}
pos = strcspn(tmp, "/");
strncpy(dir + count, tmp, pos);
tmp += pos;
count += pos;
*(dir + count) = '\0';
mylog(LOG_DEBUGVERB,"check_dir_r: %s", dir);
if (check_dir(dir, 0)) {
free(dir);
return 1;
}
}
free(dir);
return 0;
}
void strtolower(char *str)
{
char *c;
for (c = str; *c != '\0'; c++)
*c = tolower(*c);
}
/*
* Replace all occurences of var in str by value.
* This function modifies its first argument!
* Truncate the string after max characters.
*/
void replace_var(char *str, char *var, char *value, unsigned int max)
{
char *pos;
unsigned int lenvar = strlen(var);
unsigned int lenval = strlen(value);
while((pos = strstr(str, var))) {
/* Make room */
if (strlen(str) + (lenval - lenvar) >= max)
return;
memmove(pos + lenval, pos + lenvar,
(strlen(pos + lenvar) + 1)*sizeof(char));
memcpy(pos, value, lenval*sizeof(char));
}
}
char *log_build_filename(log_t *logdata, char *destination)
{
char *logfile, year[5], day[3], month[3], *tmp, *logdir;
int log_format_len;
struct tm *now;
time_t s;
char *dest = strdup(destination);
strtolower(dest);
log_format_len = strlen(conf_log_format);
logfile = (char*)malloc((MAX_PATH_LEN + 1)*sizeof(char));
if (!logfile)
fatal("out of memory");
time(&s);
now = localtime(&s);
snprintf(year, 5, "%04d", now->tm_year + 1900);
snprintf(day, 3, "%02d", now->tm_mday);
snprintf(month, 3, "%02d", now->tm_mon + 1);
snprintf(logfile, MAX_PATH_LEN, "%s/%s", conf_log_root,
conf_log_format);
replace_var(logfile, "%u", logdata->user->name, MAX_PATH_LEN);
replace_var(logfile, "%n", logdata->network, MAX_PATH_LEN);
replace_var(logfile, "%c", dest, MAX_PATH_LEN);
replace_var(logfile, "%Y", year, MAX_PATH_LEN);
replace_var(logfile, "%d", day, MAX_PATH_LEN);
replace_var(logfile, "%m", month, MAX_PATH_LEN);
logdir = strdup(logfile);
if (!logdir)
fatal("out of memory");
/* strrchr works on bytes, not on char (if sizeof(char) != 1) */
tmp = strrchr(logdir, '/');
if (tmp)
*tmp = '\0';
free(dest);
if (check_dir_r(logdir)) {
free(logfile);
free(logdir);
return NULL;
}
free(logdir);
return logfile;
}
void log_updatelast(logfile_t *lf)
{
time_t t;
time(&t);
localtime_r(&t, &lf->last_log);
}
void log_reset(logfilegroup_t *lfg)
{
logfile_t *olf;
lfg->skip_advance = 0;
if (lfg->memlog) {
while (!list_is_empty(lfg->memlog))
free(list_remove_first(lfg->memlog));
return;
}
while ((olf = list_get_first(&lfg->file_group)) !=
list_get_last(&lfg->file_group)) {
logfile_free(olf);
list_remove_first(&lfg->file_group);
}
if (!olf)
return;
if (!olf->file)
fatal("internal, (NULL logfile)");
fseek(olf->file, 0, SEEK_END);
olf->len = ftell(olf->file);
olf->backlog_offset = olf->len;
}
void log_reinit(logfilegroup_t *lfg)
{
mylog(LOG_ERROR, "%s is inconsistant, droping backlog info",
lfg->name);
log_reset(lfg);
}
static int log_add_file(log_t *logdata, char *destination, char *filename)
{
FILE *f;
logfile_t *lf = NULL;
logfilegroup_t *lfg;
if (conf_log) {
f = fopen(filename, "a+");
if (!f) {
mylog(LOG_ERROR, "fopen(%s) %s", filename,
strerror(errno));
return 0;
}
lf = malloc(sizeof(logfile_t));
if (!lf)
fatal("out of memory");
lf->file = f;
lf->filename = strdup(filename);
if (!lf->filename)
fatal("out of memory");
fseek(lf->file, 0, SEEK_END);
if (ftell(f) < 0)
fatal("ftell");
lf->len = ftell(f);
lf->backlog_offset = lf->len;
log_updatelast(lf);
}
lfg = hash_get(&logdata->logfgs, destination);
if (!lfg) {
lfg = calloc(sizeof(logfilegroup_t), 1);
if (!lfg)
fatal("out of memory");
list_init(&lfg->file_group, NULL);
lfg->name = strdup(destination);
if (!lfg->name)
fatal("out of memory");
lfg->skip_advance = 0;
hash_insert(&logdata->logfgs, destination, lfg);
}
if (!conf_log && logdata->user->backlog) {
if (!lfg->memlog)
lfg->memlog = list_new(NULL);
}
if (lf)
list_add_last(&lfg->file_group, lf);
return 1;
}
/*
* XXX: must not free file_group
*/
void logfile_free(logfile_t *lf)
{
if (!lf)
return;
if (lf->file)
fclose(lf->file);
if (lf->filename)
free(lf->filename);
free(lf);
}
logfilegroup_t *log_find_file(log_t *logdata, char *destination)
{
logfile_t *lf;
logfilegroup_t *lfg;
char *filename = NULL;
time_t t;
struct tm *ltime;
struct link *l;
if (!ischannel(*destination))
destination = "privates";
lfg = hash_get(&logdata->logfgs, destination);
if (lfg && !conf_log)
return lfg;
if (!lfg) {
if (conf_log) {
filename = log_build_filename(logdata, destination);
if (!filename)
return NULL;
mylog(LOG_DEBUG, "Creating new logfile for %s: %s",
destination, filename);
}
if (!log_add_file(logdata, destination, filename)) {
free(filename);
return NULL;
}
lfg = hash_get(&logdata->logfgs, destination);
if (!lfg)
fatal("internal log_find_file");
/* ok we are allocating a new lfg now, let's set it up for
* backlogging if applicable */
if (!logdata->user)
fatal("log_find_file: no user associated to logdata");
if (!logdata->network)
fatal("log_find_file: no network id associated to "
"logdata");
l = hash_get(&logdata->user->connections, logdata->network);
if (!l)
fatal("log_beautify: no connection associated to "
"logdata");
struct chan_info *ci = hash_get(&l->chan_infos, destination);
if (ci && !ci->backlog) {
lfg->track_backlog = 0;
} else {
lfg->track_backlog = 1;
}
if (filename)
free(filename);
return lfg;
}
/* This is reached if lfg already exists */
time(&t);
ltime = localtime(&t);
lf = list_get_last(&lfg->file_group);
if (ltime->tm_mday != lf->last_log.tm_mday) {
logfile_t *oldlf;
/* day changed, we might want to rotate logfile */
filename = log_build_filename(logdata, destination);
if (!filename)
return NULL;
if (strcmp(lf->filename, filename) == 0) {
/* finally we don't */
free(filename);
return lfg;
}
/* we do want do rotate logfiles */
mylog(LOG_DEBUG, "Rotating logfile for %s from", destination);
oldlf = list_get_last(&lfg->file_group);
if (!log_add_file(logdata, destination, filename)) {
free(filename);
return NULL;
}
free(filename);
if (!logdata->user->backlog) {
/* remove oldlf from file_group */
if (list_remove_first(&lfg->file_group) != oldlf)
fatal("internal log_find_file 2");
logfile_free(oldlf);
if (list_get_first(&lfg->file_group)
!= list_get_last(&lfg->file_group))
fatal("internal log_find_file 3");
} else {
fclose(oldlf->file);
oldlf->file = NULL;
}
}
return lfg;
}
/*
* Da log routines
* There are a lot of snprintf's here without enforcing the last \0 in the
* buffer, but _log_write takes care of this for us.
*/
void log_join(log_t *logdata, char *ircmask, char *channel)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s has joined %s", timestamp(), ircmask,
channel);
log_write(logdata, channel, logdata->buffer);
}
void log_part(log_t *logdata, char *ircmask, char *channel,
char *message)
{
if (message)
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s has left %s [%s]", timestamp(), ircmask,
channel, message);
else
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s has left %s", timestamp(), ircmask,
channel);
log_write(logdata, channel, logdata->buffer);
}
void log_kick(log_t *logdata, char *ircmask, char *channel,
char *who, char *message)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s has been kicked by %s [%s]", timestamp(),
who, ircmask, message);
log_write(logdata, channel, logdata->buffer);
}
void log_quit(log_t *logdata, char *ircmask, char *channel, char *message)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s has quit [%s]", timestamp(), ircmask,
message);
log_write(logdata, channel, logdata->buffer);
}
void log_nick(log_t *logdata, char *ircmask, char *channel, char *newnick)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s is now known as %s",
timestamp(), ircmask, newnick);
log_write(logdata, channel, logdata->buffer);
}
static void _log_privmsg(log_t *logdata, char *ircmask, int src,
char *destination, char *message)
{
char dir = '<';
if (!ircmask)
ircmask = "Server message";
if (src)
dir = '>';
if (strlen(message) > 8 && ((*message == '\001' ||
((*message == '+' || *message == '-') &&
(*(message + 1) == '\001'))) &&
(*(message + strlen(message) - 1) == '\001'))) {
char *msg;
/* hack for freenode and the like */
char *real_message = message;
if (*message == '+' || *message == '-')
real_message++;
if (strncmp(real_message, "\001ACTION ", 8) != 0)
return;
msg = strdup(real_message);
if (!msg)
fatal("out of memory");
*(msg + strlen(msg) - 1) = '\0';
if (ischannel(*destination) || strchr(destination, '@')) {
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s %c * %s %s", timestamp(), dir,
ircmask, msg + 8);
} else {
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s (%s) %c * %s %s", timestamp(),
destination, dir, ircmask, msg + 8);
}
free(msg);
} else {
if (ischannel(*destination) || strchr(destination, '@')) {
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s %c %s: %s", timestamp(), dir,
ircmask, message);
} else {
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s %c %s (%s): %s", timestamp(),
dir, ircmask, destination, message);
}
}
log_write(logdata, destination, logdata->buffer);
}
void log_privmsg(log_t *logdata, char *ircmask, char *destination,
char *message)
{
_log_privmsg(logdata, ircmask, 0, destination, message);
}
void log_cli_privmsg(log_t *logdata, char *ircmask, char *destination,
char *message)
{
_log_privmsg(logdata, ircmask, 1, destination, message);
}
static void _log_notice(log_t *logdata, char *ircmask, int src,
char *destination, char *message)
{
char dir = '<';
if (!ircmask)
ircmask = "Server message";
if (src)
dir = '>';
if (*message == '\001' && *(message + strlen(message) - 1) == '\001')
return;
if (ischannel(*destination) || strchr(destination, '@')) {
snprintf(logdata->buffer, LOGLINE_MAXLEN, "%s %c %s: %s",
timestamp(), dir, ircmask, message);
} else {
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s %c %s (%s): %s", timestamp(),
dir, ircmask, destination, message);
}
log_write(logdata, destination, logdata->buffer);
}
void log_notice(log_t *logdata, char *ircmask, char *destination,
char *message)
{
_log_notice(logdata, ircmask, 0, destination, message);
}
void log_cli_notice(log_t *logdata, char *ircmask, char *destination,
char *message)
{
_log_notice(logdata, ircmask, 1, destination, message);
}
void log_topic(log_t *logdata, char *ircmask, char *channel, char *message)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- %s changed topic of %s to: %s", timestamp(),
ircmask, channel, message);
log_write(logdata, channel, logdata->buffer);
}
void log_init_topic(log_t *logdata, char *channel, char *message)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- Topic for %s: %s", timestamp(), channel,
message);
log_write(logdata, channel, logdata->buffer);
}
void log_init_topic_time(log_t *logdata, char *channel, char *who, char *when)
{
struct tm *time;
char *timestr;
time_t seconds;
seconds = atoi(when);
time = localtime(&seconds);
timestr = (char*)malloc(sizeof(char) * (50 + 1));
timestr[0] = '\0';
if (time)
strftime(timestr, 50, "%A %d %B %Y, %H:%M:%S", time);
timestr[50] = '\0';
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- Topic set by %s [%s]", timestamp(), who,
timestr);
free(timestr);
log_write(logdata, channel, logdata->buffer);
}
void log_mode(log_t *logdata, char *ircmask, char *channel, char *modes,
char **modargv, unsigned modargc)
{
unsigned i;
char *tmpbuf = malloc(LOGLINE_MAXLEN + 1);
char *tmpbuf2 = malloc(LOGLINE_MAXLEN + 1);
char *tmp;
snprintf(tmpbuf, LOGLINE_MAXLEN, "%s -!- mode/%s [%s", timestamp(),
channel, modes);
for (i = 0; i < modargc; i++) {
snprintf(tmpbuf2, LOGLINE_MAXLEN, "%s %s", tmpbuf, modargv[i]);
tmp = tmpbuf;
tmpbuf = tmpbuf2;
tmpbuf2 = tmp;
}
snprintf(logdata->buffer, LOGLINE_MAXLEN, "%s] by %s", tmpbuf,
ircmask);
log_write(logdata, channel, logdata->buffer);
free(tmpbuf);
free(tmpbuf2);
}
void log_disconnected(log_t *logdata)
{
logfilegroup_t *lfg;
hash_iterator_t hi;
snprintf(logdata->buffer, LOGLINE_MAXLEN, "%s -!- Disconnected"
" from server...", timestamp());
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
lfg = hash_it_item(&hi);
_log_write(logdata, lfg, hash_it_key(&hi), logdata->buffer);
}
}
void log_ping_timeout(log_t *logdata)
{
snprintf(logdata->buffer, LOGLINE_MAXLEN,
"%s -!- Ping timeout with server...", timestamp());
log_write(logdata, "privates", logdata->buffer);
log_disconnected(logdata);
}
void log_connected(log_t *logdata)
{
logfilegroup_t *lfg;
hash_iterator_t hi;
snprintf(logdata->buffer, LOGLINE_MAXLEN, "%s -!- Connected to"
" server...", timestamp());
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
lfg = hash_it_item(&hi);
_log_write(logdata, lfg, hash_it_key(&hi), logdata->buffer);
}
}
void log_client_disconnected(log_t *logdata)
{
(void)logdata;
mylog(LOG_DEBUG, "A client disconnected");
}
void log_reinit_all(log_t *logdata)
{
logfilegroup_t *lfg;
hash_iterator_t hi;
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
lfg = hash_it_item(&hi);
log_reset(lfg);
}
}
void log_client_none_connected(log_t *logdata)
{
logdata->connected = 0;
if (logdata->user->always_backlog)
return;
log_reinit_all(logdata);
}
void log_client_connected(log_t *logdata)
{
mylog(LOG_DEBUG, "A client connected");
logdata->connected = 1;
}
void log_advance_backlogs(log_t* ld, logfilegroup_t *lfg)
{
int c;
if (!lfg->track_backlog)
return;
if (!ld->user->backlog || ld->user->backlog_lines == 0)
return;
if (lfg->skip_advance < ld->user->backlog_lines) {
lfg->skip_advance++;
return;
}
logfile_t *lf;
while ((lf = list_get_first(&lfg->file_group))) {
if (!lf->file) {
lf->file = fopen(lf->filename, "r");
if (!lf->file) {
mylog(LOG_ERROR, "Can't open %s for reading",
lf->filename);
log_reinit(lfg);
return;
}
}
if (fseek(lf->file, lf->backlog_offset, SEEK_SET)) {
log_reinit(lfg);
return;
}
while ((c = fgetc(lf->file)) != EOF) {
lf->backlog_offset++;
if (c == '\n')
return;
}
if (lf == list_get_last(&lfg->file_group))
return;
fclose(lf->file);
lf->file = NULL;
list_remove_first(&lfg->file_group);
logfile_free(lf);
}
}
int log_has_backlog(log_t *logdata, char *destination)
{
logfilegroup_t *lfg = hash_get(&logdata->logfgs, destination);
if (!lfg)
return 0;
if (lfg->memlog)
return !list_is_empty(lfg->memlog);
if (!lfg->track_backlog)
return 0;
logfile_t *lf;
lf = list_get_first(&lfg->file_group);
if (lf != list_get_last(&lfg->file_group))
return 1;
return lf->backlog_offset != lf->len;
}
/*
* chan:
* 13-05-2005 12:14:29 > nohar: coucou
* 13-05-2005 12:14:30 < nohar!~nohar@je.suis.t1r.net: coucou
*
* private:
* 13-05-2005 12:14:53 > nohar (jj): 1 luv PHP
* 13-05-2005 12:14:55 < jj!john@thebox.ofjj.net (nohar): t00 s3xy
* 01-08-2005 10:46:11 < * jj!john@thebox.ofjj.net
*/
char *log_beautify(log_t *logdata, char *buf, char *dest)
{
int action = 0;
char *p;
/*
* so = startov, lo = lengthov
* ts = timestamp, n = sender nick, m = message or action
*/
char *sots, *son, *som, *sod = NULL;
size_t lots, lon, lom, lod;
char *ret;
int out;
int done;
if (!buf)
fatal("BUG log_beautify not called correctly!");
p = strchr(buf, ' ');
if (!p || !p[0] || !p[1])
return _log_wrap(dest, buf);
p++;
sots = p;
p = strchr(p, ' ');
if (!p || !p[0] || !p[1])
return _log_wrap(dest, buf);
lots = p - sots;
p++;
if (strncmp(p, "-!-", 3) == 0) {
if (logdata->user->bl_msg_only)
return NULL;
else
return _log_wrap(dest, buf);
}
if (*p == '>')
out = 1;
else if (*p == '<')
out = 0;
else
return _log_wrap(dest, buf);
p++;
if (*p != ' ')
return _log_wrap(dest, buf);
p++;
if (*p == '*') {
action = 1;
if (!p[1] || !p[2])
return _log_wrap(dest, buf);
p += 2;
}
son = p;
while (*p && *p != '!' && *p != ' ' && *p != ':')
p++;
if (!p[0])
return _log_wrap(dest, buf);
lon = p - son;
p = strchr(p, ' ');
if (!p || !p[0])
return _log_wrap(dest, buf);
done = ((p[-1] == ':') || (action && (p[1] != '(')));
p++;
/*
* TODO add a : before the action text in the log files
* otherwise "/me (bla) blabla" on a chan is logged exactly as
* "/me blabla" in a query with bla.
*/
if (!done) {
p++;
if (!p[0] || !p[1] || p[0] == ')')
return _log_wrap(dest, buf);
sod = p;
while (*p && *p != ')' && *p != ' ' && *p != '!')
p++;
lod = p - sod;
if (*p == '!')
while (*p && *p != ')' && *p != ' ')
p++;
if (!p[0] || p[0] != ')' || !p[1] || p[1] != ':' ||
!p[2] || p[2] != ' ' || !p[3])
return _log_wrap(dest, buf);
p += 3;
} else {
sod = dest;
lod = strlen(dest);
}
if (out && strcmp(dest, "privates") == 0) {
char *stmp;
size_t ltmp;
stmp = sod;
ltmp = lod;
sod = son;
lod = lon;
son = stmp;
lon = ltmp;
}
som = p;
lom = strlen(p);
if (lom == 0)
return _log_wrap(dest, buf);
p = ret = (char *)malloc(
1 + lon + strlen(LAMESTRING) + lod + 2 + lots + 2 + lom + 3
+ action * (2 + strlen("ACTION ")) + out * strlen(PMSG_ARROW));
if (!p)
fatal("out of memory");
*p++ = ':';
memcpy(p, son, lon);
p += lon;
strcpy(p, LAMESTRING);
p += strlen(LAMESTRING);
memcpy(p, sod, lod);
p += lod;
*p++ = ' ';
*p++ = ':';
if (action) {
*p++ = 1;
strcpy(p, "ACTION ");
p += strlen("ACTION ");
}
if (out) {
strcpy(p, PMSG_ARROW);
p += strlen(PMSG_ARROW);
}
if (logdata->user->backlog_no_timestamp == 0) {
memcpy(p, sots, lots);
p += lots;
*p++ = '>';
*p++ = ' ';
}
memcpy(p, som, lom);
p += lom;
if (action)
*p++ = 1;
*p++ = '\r';
*p++ = '\n';
*p = 0;
return ret;
}
char *log_backread(log_t *logdata, char *destination, int *skip)
{
char *buf;
size_t pos = 0;
logfile_t *lf;
logfilegroup_t *lfg;
int c;
char *ret;
*skip = 0;
if (!logdata->user->always_backlog && logdata->connected)
return NULL;
lfg = hash_get(&logdata->logfgs, destination);
if (!lfg)
return NULL;
if (!lfg->track_backlog)
return NULL;
if (!logdata->backlogging) {
logdata->backlogging = 1;
mylog(LOG_DEBUG, "backlogging!");
if (lfg->memlog)
list_it_init(lfg->memlog, &lfg->backlog_it);
else
list_it_init(&lfg->file_group, &logdata->file_it);
}
if (lfg->memlog) {
char *ptr;
ptr = list_it_item(&lfg->backlog_it);
if (!ptr) {
logdata->backlogging = 0;
return NULL;
}
list_it_next(&lfg->backlog_it);
return strdup(ptr);
}
if (!conf_log) {
mylog(LOG_DEBUG, "No conf_log, not backlogging");
return NULL;
}
buf = (char *)malloc((LOGLINE_MAXLEN + 1) * sizeof(char));
next_file:
/* check the files containing data to backlog */
lf = list_it_item(&logdata->file_it);
if (lf != list_get_last(&lfg->file_group)) {
mylog(LOG_DEBUGVERB, "%s not last file!", lf->filename);
/* if the file is not the current open for logging
* (it is an old file that has been rotated)
* open if necessary, backlog line per line, and close */
if (!lf->file) {
mylog(LOG_DEBUGVERB, "opening: %s!", lf->filename);
lf->file = fopen(lf->filename, "r");
if (!lf->file) {
mylog(LOG_ERROR, "Can't open %s for reading",
lf->filename);
log_reinit(lfg);
free(buf);
return _log_wrap("Error reading logfile",
destination);
}
mylog(LOG_DEBUGVERB, "seeking: %d!",
lf->backlog_offset);
if (fseek(lf->file, lf->backlog_offset, SEEK_SET)) {
log_reinit(lfg);
free(buf);
return _log_wrap(destination,
"Error reading in logfile");
}
}
for(;;) {
c = fgetc(lf->file);
if (!logdata->user->always_backlog)
lf->backlog_offset++;
if (c == EOF || c == '\n' || pos == LOGLINE_MAXLEN) {
/* change file if we reach EOF, if pos == maxlen
* then the log file is corrupted so we also
* drop this file */
if (pos == LOGLINE_MAXLEN)
mylog(LOG_DEBUG, "logline too long");
if (c == EOF || pos == LOGLINE_MAXLEN) {
mylog(LOG_DEBUGVERB, "EOF: %s (%d)!",
lf->filename,
logdata->user->always_backlog);
list_it_next(&logdata->file_it);
if (!logdata->user->always_backlog) {
list_remove_first(
&lfg->file_group);
logfile_free(lf);
} else {
fclose(lf->file);
lf->file = NULL;
}
pos = 0;
goto next_file;
}
buf[pos] = 0;
if (pos == 0) {
*skip = 1;
return buf;
}
ret = log_beautify(logdata, buf, destination);
if (ret == NULL) {
pos = 0;
continue;
}
free(buf);
return ret;
}
buf[pos++] = c;
}
}
/* the logfile to read is the one open for writing */
if (!logdata->lastfile_seeked) {
if (fseek(lf->file, lf->backlog_offset, SEEK_SET)) {
log_reinit(lfg);
return _log_wrap(destination,
"Error reading in logfile");
}
logdata->lastfile_seeked = 1;
mylog(LOG_DEBUG, "last file seeked!");
}
c = fgetc(lf->file);
if (c == EOF) {
mylog(LOG_DEBUG, "Nothing more to backlog");
logdata->lastfile_seeked = 0;
logdata->backlogging = 0;
free(buf);
return NULL;
}
if (!logdata->user->always_backlog)
lf->backlog_offset++;
if (c != '\n')
buf[pos++] = c;
for(;;) {
c = fgetc(lf->file);
if (!logdata->user->always_backlog)
lf->backlog_offset++;
if (c == EOF || c == '\n' || pos == LOGLINE_MAXLEN) {
if (pos == LOGLINE_MAXLEN) {
mylog(LOG_DEBUG, "logline too long");
fseek(lf->file, 0, SEEK_END);
/* in the corruption case we alwayse reset
* backlog offset */
lf->backlog_offset = ftell(lf->file);
free(buf);
return NULL;
}
if (!logdata->user->always_backlog && c == EOF)
lf->backlog_offset--;
buf[pos] = 0;
if (pos == 0) {
*skip = 1;
return buf;
}
ret = log_beautify(logdata, buf, destination);
if (ret == NULL) {
mylog(LOG_DEBUGVERB, "Line not backlogged");
pos = 0;
continue;
}
mylog(LOG_DEBUGVERB, "backlog: %s", ret);
free(buf);
return ret;
}
buf[pos++] = c;
}
/* unreachable */
fatal("internal error 12");
return NULL;
}
static char *_log_wrap(char *dest, char *line)
{
char *buf;
size_t count;
buf = malloc(LOGLINE_MAXLEN + 1);
count = snprintf(buf, LOGLINE_MAXLEN + 1,
":" P_IRCMASK " PRIVMSG %s :%s\r\n", dest, line);
if (count >= LOGLINE_MAXLEN + 1) {
mylog(LOG_DEBUG, "line too long");
buf[LOGLINE_MAXLEN - 2] = '\r';
buf[LOGLINE_MAXLEN - 1] = '\n';
buf[LOGLINE_MAXLEN] = 0;
}
return buf;
}
static int _log_write(log_t *logdata, logfilegroup_t *lfg, char *destination,
char *str)
{
size_t nbwrite;
size_t len;
str[LOGLINE_MAXLEN] = 0;
if (lfg->memlog) {
char *r = log_beautify(logdata, str, destination);
if (r != NULL) {
list_add_last(lfg->memlog, r);
if (lfg->memc == logdata->user->backlog_lines)
free(list_remove_first(lfg->memlog));
else
lfg->memc++;
}
}
if (!conf_log)
return 0;
logfile_t *lf = list_get_last(&lfg->file_group);
len = strlen(str);
nbwrite = fwrite(str, sizeof(char), len, lf->file);
nbwrite += fwrite("\n", sizeof(char), 1, lf->file);
log_updatelast(lf);
if (nbwrite != len + 1)
mylog(LOG_ERROR, "Error writing to %s logfile", lf->filename);
lf->len += nbwrite;
if (!logdata->connected || logdata->user->always_backlog)
log_advance_backlogs(logdata, lfg);
return nbwrite;
}
void log_write(log_t *logdata, char *destination, char *str)
{
logfilegroup_t *lfg = log_find_file(logdata, destination);
if (!lfg) {
mylog(LOG_ERROR, "Unable to find/create logfile for '%s'",
destination);
return;
}
_log_write(logdata, lfg, destination, str);
}
static list_t *log_all_logs = NULL;
void log_flush_all(void)
{
list_iterator_t li;
if (!log_all_logs)
return;
fflush(conf_global_log_file);
for (list_it_init(log_all_logs, &li); list_it_item(&li);
list_it_next(&li)) {
log_t *log = list_it_item(&li);
hash_iterator_t hi;
for (hash_it_init(&log->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
logfilegroup_t *lfg = hash_it_item(&hi);
list_iterator_t lj;
for (list_it_init(&lfg->file_group, &lj);
list_it_item(&lj); list_it_next(&lj)) {
logfile_t *lf = list_it_item(&lj);
if (lf->file)
fflush(lf->file);
}
}
}
}
log_t *log_new(struct user *user, char *network)
{
log_t *logdata;
logdata = (log_t*)calloc(sizeof(log_t), 1);
if (!logdata)
fatal("out of memory");
logdata->user = user;
logdata->network = strdup(network);
hash_init(&logdata->logfgs, HASH_NOCASE);
logdata->buffer = (char *)malloc((LOGLINE_MAXLEN + 1) * sizeof(char));
logdata->buffer[LOGLINE_MAXLEN - 1] = 0; // debug
logdata->buffer[LOGLINE_MAXLEN] = 0;
if (!logdata->user || !logdata->network || !logdata->buffer)
fatal("out of memory");
logdata->connected = 0;
if (!log_all_logs)
log_all_logs = list_new(list_ptr_cmp);
list_add_last(log_all_logs, logdata);
return logdata;
}
void log_free(log_t *log)
{
hash_iterator_t it;
logfilegroup_t *lfg;
logfile_t *lf;
list_remove(log_all_logs, log);
free(log->network);
free(log->buffer);
for (hash_it_init(&log->logfgs, &it); (lfg = hash_it_item(&it));
hash_it_next(&it)) {
log_reset(lfg);
if ((lf = list_remove_first(&lfg->file_group)))
logfile_free(lf);
}
hash_clean(&log->logfgs);
free(log);
}