bip/src/log.c

1461 lines
34 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
* Copyright (C) 2004,2022 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"
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
extern int errno;
#pragma GCC diagnostic pop
extern int log_level;
extern char *conf_log_root;
extern char *conf_log_format;
extern int conf_log;
extern FILE *conf_global_log_file;
static size_t _log_write(log_t *logdata, logstore_t *lf, const char *d,
const char *str);
static char *_log_wrap(const char *dest, const char *line);
void logfile_free(logfile_t *lf);
void log_drop(log_t *log, const char *storename);
static void log_reset(logstore_t *store);
#define BOLD_CHAR 0x02
#define LAMESTRING "!bip@" P_SERV " PRIVMSG "
#define PMSG_ARROW "\002->\002"
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)
{
size_t count = 0;
char *dir, *tmp;
size_t len = strlen(dirname);
size_t pos;
mylog(LOG_DEBUGVERB, "Recursive check of %s engaged", dirname);
tmp = dirname;
dir = (char *)bip_malloc(len + 1);
while (*tmp) {
int slash_ok = 1;
while (*tmp == '/') {
if (slash_ok) {
strncpy(dir + count, "/", (size_t)2);
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 = (char)tolower(
*c); // tolower()->int but should be safe to cast
}
/*
* 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;
size_t lenvar = strlen(var);
size_t 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);
memcpy(pos, value, lenval);
}
}
char *log_build_filename(log_t *logdata, const char *destination)
{
char *logfile, year[5], day[3], month[3], hour[3], *tmp, *logdir;
struct tm *now;
time_t s;
char *dest = bip_strdup(destination);
strtolower(dest);
logfile = (char *)bip_malloc((size_t)MAX_PATH_LEN + 1);
time(&s);
now = localtime(&s);
strftime(year, (size_t)5, "%Y", now);
strftime(day, (size_t)3, "%d", now);
strftime(month, (size_t)3, "%m", now);
strftime(hour, (size_t)3, "%H", now);
snprintf(logfile, (size_t)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);
replace_var(logfile, "%h", hour, MAX_PATH_LEN);
logdir = bip_strdup(logfile);
/* 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);
}
static void log_reset(logstore_t *store)
{
logfile_t *olf;
long ftell_r;
store->skip_advance = 0;
if (store->memlog) {
while (!list_is_empty(store->memlog))
free(list_remove_first(store->memlog));
return;
}
while ((olf = list_get_first(&store->file_group))
&& olf != list_get_last(&store->file_group)) {
logfile_free(olf);
list_remove_first(&store->file_group);
}
if (!olf || !olf->file)
return;
list_it_init_last(&store->file_group, &store->file_it);
fseek(olf->file, (long)0, SEEK_END);
ftell_r = ftell(olf->file);
if (ftell_r < 0) {
mylog(LOG_ERROR, "log_reset: ftell error %s", strerror(errno));
return;
}
store->file_offset = ftell_r;
olf->len = (size_t)ftell_r;
}
void log_reinit(logstore_t *store)
{
mylog(LOG_ERROR, "%s is inconsistent, droping backlog info",
store->name);
log_reset(store);
}
static char *filename_uniq(const char *filename)
{
struct stat filestat;
int i;
if (stat(filename, &filestat) != -1) {
char *buf = bip_malloc(strlen(filename) + 4 + 1);
for (i = 0; i < 256; i++) {
sprintf(buf, "%s.%d", filename, i);
if (stat(buf, &filestat) == -1)
return buf;
}
free(buf);
}
return bip_strdup(filename);
}
static int log_has_file(log_t *logdata, const char *fname)
{
hash_iterator_t hi;
list_iterator_t li;
logstore_t *store;
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
store = hash_it_item(&hi);
for (list_it_init(&store->file_group, &li); list_it_item(&li);
list_it_next(&li)) {
logfile_t *lf = list_it_item(&li);
if (strcmp(fname, lf->filename) == 0)
return 1;
}
}
return 0;
}
static int log_add_file(log_t *logdata, const char *destination,
const char *filename)
{
FILE *f;
logstore_t *store;
char *uniq_fname;
char *canonical_fname = NULL;
logfile_t *lf = NULL;
long ftell_r;
if (logdata->log_to_file) {
if (log_has_file(logdata, filename)) {
canonical_fname = bip_strdup(filename);
uniq_fname = filename_uniq(filename);
} else {
canonical_fname = bip_strdup(filename);
uniq_fname = bip_strdup(filename);
}
f = fopen(uniq_fname, "a+");
if (!f) {
mylog(LOG_ERROR, "fopen(%s) %s", uniq_fname,
strerror(errno));
free(uniq_fname);
free(canonical_fname);
return 0;
}
if (fseek(f, (long)0, SEEK_END) == -1) {
mylog(LOG_ERROR, "fseek(%s) %s", uniq_fname,
strerror(errno));
free(uniq_fname);
free(canonical_fname);
fclose(f);
return 0;
}
lf = bip_malloc(sizeof(logfile_t));
lf->file = f;
ftell_r = ftell(f);
lf->len = (size_t)ftell_r;
if (ftell_r < 0) {
mylog(LOG_ERROR, "log_add_file: ftell error %s",
strerror(errno));
free(uniq_fname);
free(canonical_fname);
fclose(f);
return 0;
}
lf->filename = uniq_fname;
lf->canonical_filename = canonical_fname;
log_updatelast(lf);
}
store = hash_get(&logdata->logfgs, destination);
if (!store) {
store = bip_calloc(sizeof(logstore_t), (size_t)1);
list_init(&store->file_group, NULL);
store->name = bip_strdup(destination);
store->skip_advance = 0;
// should be safe to cast as lf->len comes from ftell()
if (lf)
store->file_offset = (long)lf->len;
hash_insert(&logdata->logfgs, destination, store);
}
if (!logdata->log_to_file && logdata->user->backlog) {
if (!store->memlog)
store->memlog = list_new(NULL);
}
if (lf) {
list_add_last(&store->file_group, lf);
if (list_it_item(&store->file_it) == NULL)
list_it_init_last(&store->file_group, &store->file_it);
}
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);
if (lf->canonical_filename)
free(lf->canonical_filename);
free(lf);
}
logstore_t *log_find_file(log_t *logdata, const char *destination)
{
logfile_t *lf;
logstore_t *store;
char *filename = NULL;
struct link *l;
store = hash_get(&logdata->logfgs, destination);
if (store && !logdata->log_to_file)
return store;
if (!store) {
if (logdata->log_to_file) {
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)) {
if (filename)
free(filename);
return NULL;
}
store = hash_get(&logdata->logfgs, destination);
assert(store);
/* ok we are allocating a new store now, let's set it up for
* backlogging if applicable */
assert(logdata->user);
assert(logdata->network);
l = hash_get(&logdata->user->connections, logdata->network);
assert(l);
struct chan_info *ci = hash_get(&l->chan_infos, destination);
if (ci && !ci->backlog)
store->track_backlog = 0;
else
store->track_backlog = 1;
if (filename)
free(filename);
return store;
}
/* This is reached if store already exists */
lf = list_get_last(&store->file_group);
time_t t;
struct tm *ltime;
time(&t);
ltime = localtime(&t);
if (ltime->tm_year != lf->last_log.tm_year
|| ltime->tm_yday != lf->last_log.tm_yday) {
logfile_t *oldlf;
/* day changed, we might want to rotate logfile */
filename = log_build_filename(logdata, destination);
if (!filename)
return NULL;
if (strcmp(lf->canonical_filename, filename) == 0) {
/* finally we don't */
free(filename);
return store;
}
/* we do want to rotate logfiles */
mylog(LOG_DEBUG, "Rotating logfile for %s %s %s", destination,
lf->filename, filename);
oldlf = list_get_last(&store->file_group);
if (!log_add_file(logdata, destination, filename)) {
free(filename);
return NULL;
}
free(filename);
if (!logdata->user->backlog) {
/* remove oldlf from file_group */
assert(list_remove_first(&store->file_group) == oldlf);
logfile_free(oldlf);
assert(list_get_first(&store->file_group)
== list_get_last(&store->file_group));
} else {
fclose(oldlf->file);
oldlf->file = NULL;
}
}
return store;
}
void log_join(log_t *logdata, const char *ircmask, const char *channel)
{
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- %s has joined %s", timestamp(), ircmask, channel);
log_write(logdata, channel, logdata->buffer);
}
void log_part(log_t *logdata, const char *ircmask, const char *channel,
const char *message)
{
if (message)
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- %s has left %s [%s]", timestamp(), ircmask,
channel, message);
else
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- %s has left %s", timestamp(), ircmask,
channel);
log_write(logdata, channel, logdata->buffer);
}
void log_kick(log_t *logdata, const char *ircmask, const char *channel,
const char *who, const char *message)
{
snprintf(logdata->buffer, (size_t)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, const char *ircmask, const char *channel,
const char *message)
{
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- %s has quit [%s]", timestamp(), ircmask, message);
log_write(logdata, channel, logdata->buffer);
}
void log_nick(log_t *logdata, const char *ircmask, const char *channel,
const char *newnick)
{
char *oldnick = nick_from_ircmask(ircmask);
logstore_t *oldstore;
logstore_t *newstore;
if ((oldstore = hash_get(&logdata->logfgs, oldnick))) {
if ((newstore = hash_get(&logdata->logfgs, newnick))
&& oldstore != newstore) {
log_drop(logdata, newnick);
}
hash_rename_key(&logdata->logfgs, oldnick, newnick);
}
free(oldnick);
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- %s is now known as %s", timestamp(), ircmask, newnick);
log_write(logdata, channel, logdata->buffer);
}
static void do_log_privmsg(log_t *logdata, const char *storage, int src,
const char *from, const char *message)
{
char dir = '<';
if (!from)
from = "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 */
const char *real_message = message;
if (*message == '+' || *message == '-')
real_message++;
if (strncmp(real_message, "\001ACTION ", (size_t)8) != 0)
return;
msg = bip_strdup(real_message);
*(msg + strlen(msg) - 1) = '\0';
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s %c * %s %s", timestamp(), dir, from, msg + 8);
free(msg);
} else {
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s %c %s: %s", timestamp(), dir, from, message);
}
log_write(logdata, storage, logdata->buffer);
}
void log_privmsg(log_t *logdata, const char *ircmask, const char *destination,
const char *message)
{
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (!ischannel(*destination)) {
char *nick = nick_from_ircmask(ircmask);
do_log_privmsg(logdata, nick, 0, ircmask, message);
free(nick);
} else {
do_log_privmsg(logdata, destination, 0, ircmask, message);
}
#pragma GCC diagnostic pop
}
void log_cli_privmsg(log_t *logdata, const char *ircmask,
const char *destination, const char *message)
{
do_log_privmsg(logdata, destination, 1, ircmask, message);
}
void log_notice(log_t *logdata, const char *ircmask, const char *destination,
const char *message)
{
if (!ircmask)
ircmask = P_IRCMASK;
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (!ischannel(*destination)) {
char *nick = nick_from_ircmask(ircmask);
do_log_privmsg(logdata, nick, 0, ircmask, message);
free(nick);
} else {
do_log_privmsg(logdata, destination, 0, ircmask, message);
}
#pragma GCC diagnostic pop
}
void log_cli_notice(log_t *logdata, const char *ircmask,
const char *destination, const char *message)
{
do_log_privmsg(logdata, destination, 1, ircmask, message);
}
void log_topic(log_t *logdata, const char *ircmask, const char *channel,
const char *message)
{
snprintf(logdata->buffer, (size_t)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, const char *channel, const char *message)
{
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- Topic for %s: %s", timestamp(), channel, message);
log_write(logdata, channel, logdata->buffer);
}
void log_init_topic_time(log_t *logdata, const char *channel, const char *who,
const char *when)
{
struct tm *time;
char *timestr;
time_t seconds;
seconds = atoi(when);
time = localtime(&seconds);
timestr = (char *)bip_malloc((size_t)50 + 1);
timestr[0] = '\0';
if (time)
strftime(timestr, (size_t)50, "%A %d %B %Y, %H:%M:%S", time);
timestr[50] = '\0';
snprintf(logdata->buffer, (size_t)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, const char *ircmask, const char *channel,
const char *modes, array_t *mode_args)
{
int i;
char *tmpbuf = bip_malloc((size_t)LOGLINE_MAXLEN + 1);
char *tmpbuf2 = bip_malloc((size_t)LOGLINE_MAXLEN + 1);
char *tmp;
snprintf(tmpbuf, (size_t)LOGLINE_MAXLEN, "%s -!- mode/%s [%s",
timestamp(), channel, modes);
if (mode_args) {
for (i = 0; i < array_count(mode_args); i++) {
snprintf(tmpbuf2, (size_t)LOGLINE_MAXLEN, "%s %s",
tmpbuf, (const char *)array_get(mode_args, i));
tmp = tmpbuf;
tmpbuf = tmpbuf2;
tmpbuf2 = tmp;
}
}
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN, "%s] by %s", tmpbuf,
ircmask);
log_write(logdata, channel, logdata->buffer);
free(tmpbuf);
free(tmpbuf2);
}
void log_disconnected(log_t *logdata)
{
hash_iterator_t hi;
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- Disconnected"
" from server...",
timestamp());
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi))
log_write(logdata, hash_it_key(&hi), logdata->buffer);
}
void log_ping_timeout(log_t *logdata)
{
list_t *l = log_backlogs(logdata);
char *s;
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- Ping timeout with server...", timestamp());
while ((s = list_remove_first(l))) {
log_write(logdata, s, logdata->buffer);
free(s);
}
list_free(l);
log_disconnected(logdata);
}
void log_connected(log_t *logdata)
{
hash_iterator_t hi;
snprintf(logdata->buffer, (size_t)LOGLINE_MAXLEN,
"%s -!- Connected to"
" server...",
timestamp());
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
log_write(logdata, hash_it_key(&hi), logdata->buffer);
}
}
void log_client_disconnected(log_t *logdata)
{
(void)logdata;
mylog(LOG_DEBUG, "A client disconnected");
}
void log_store_free(logstore_t *store)
{
logfile_t *lf;
log_reset(store);
while ((lf = list_remove_first(&store->file_group)))
logfile_free(lf);
free(store->name);
if (store->memlog)
list_free(store->memlog);
free(store);
}
void log_drop(log_t *log, const char *storename)
{
logstore_t *store;
store = hash_remove(&log->logfgs, storename);
log_store_free(store);
}
void log_reset_all(log_t *logdata)
{
logstore_t *store;
hash_iterator_t hi;
list_t drop;
list_init(&drop, NULL);
for (hash_it_init(&logdata->logfgs, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
store = hash_it_item(&hi);
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (ischannel(*hash_it_key(&hi)))
log_reset(store);
else
list_add_last(&drop, bip_strdup(hash_it_key(&hi)));
#pragma GCC diagnostic pop
}
char *name;
while ((name = list_remove_first(&drop))) {
log_drop(logdata, name);
free(name);
}
}
void log_reset_store(log_t *log, const char *storename)
{
logstore_t *store;
store = hash_get(&log->logfgs, storename);
if (store) {
log_reset(store);
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (!ischannel(*storename))
log_drop(log, storename);
#pragma GCC diagnostic pop
}
}
void log_client_none_connected(log_t *logdata)
{
logdata->connected = 0;
if (logdata->user->always_backlog)
return;
log_reset_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, logstore_t *store)
{
int c;
// mylog(LOG_ERROR, "LOG: log_advance_backlogs %s", store->name);
if (!store->track_backlog)
return;
if (!ld->user->backlog || ld->user->backlog_lines == 0)
return;
if (store->skip_advance < ld->user->backlog_lines) {
store->skip_advance++;
return;
}
logfile_t *lf;
while ((lf = list_it_item(&store->file_it))) {
// mylog(LOG_ERROR, "LOG: %s %d", lf->filename,
// store->file_offset);
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(store);
return;
}
}
if (fseek(lf->file, store->file_offset, SEEK_SET)) {
// mylog(LOG_ERROR, "LOG: fseek FAIL %s %d",
// lf->filename, store->file_offset);
log_reinit(store);
return;
}
while ((c = fgetc(lf->file)) != EOF) {
store->file_offset++;
if (c == '\n') {
// mylog(LOG_ERROR, "LOG: %s advanced at %d",
// lf->filename, store->file_offset);
return;
}
}
// mylog(LOG_ERROR, "LOG: %s advanced at %d", lf->filename,
// store->file_offset);
if (lf == list_get_last(&store->file_group)) {
// mylog(LOG_ERROR, "LOG: %s is last of file_group",
// lf->filename, store->file_offset);
return;
}
// mylog(LOG_ERROR, "LOG: next file %s %d", lf->filename,
// store->file_offset);
fclose(lf->file);
lf->file = NULL;
list_it_next(&store->file_it);
store->file_offset = 0;
}
}
int log_has_backlog(log_t *logdata, const char *destination)
{
logstore_t *store = hash_get(&logdata->logfgs, destination);
if (!store)
return 0;
if (store->memlog)
return !list_is_empty(store->memlog);
if (!store->track_backlog)
return 0;
logfile_t *lf;
lf = list_it_item(&store->file_it);
if (lf != list_get_last(&store->file_group))
return 1;
// should be safe to cast to unsigned as we check ftell
// when setting file_offset and only ++ since then
return (size_t)store->file_offset != lf->len;
}
/*
query:
09-01-2009 14:16:10 < nohar!~nohar@haruka.t1r.net: repl querytest
09-01-2009 14:16:37 > bip4ever: je dis yo la quand meem
chan:
09-01-2009 14:15:57 > bip4ever: chantest
09-01-2009 14:16:21 < nohar!~nohar@haruka.t1r.net: chantestrepl
*/
char *log_beautify(log_t *logdata, const char *buf, const char *storename,
const char *dest)
{
int action = 0;
char *p;
/*
* so = startov, lo = lengthov
* ts = timestamp, n = sender nick, m = message or action
*/
const char *sots, *son, *som;
size_t lots, lon, lom;
char *ret;
int out;
assert(buf);
p = strchr(buf, ' ');
// buf...p
if (!p || !p[0] || !p[1])
return _log_wrap(dest, buf);
p++;
// buf...p
sots = logdata->user->backlog_timestamp == BLTSDateTime ? buf : p;
// buf...sots=p OR sots=buf...p
p = strchr(p, ' ');
// buf...sots...p OR sots=buf...p
if (!p || !p[0] || !p[1])
return _log_wrap(dest, buf);
lots = (size_t)(p - sots);
p++;
if (strncmp(p, "-!-", (size_t)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 = (size_t)(p - son);
p = strchr(p, ' ');
if (!p || !p[0] || !p[1])
return _log_wrap(dest, buf);
p++;
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (out && !ischannel(*dest)) {
son = storename;
lon = strlen(storename);
}
#pragma GCC diagnostic pop
som = p;
lom = strlen(p);
if (lom == 0)
return _log_wrap(dest, buf);
// action and out are 0 or 1, safe to cast
p = ret = (char *)bip_malloc(1 + lon + strlen(LAMESTRING) + strlen(dest)
+ 2 + lots + 2 + lom + 3
+ (size_t)action * (2 + strlen("ACTION "))
+ (size_t)out * strlen(PMSG_ARROW));
*p++ = ':';
memcpy(p, son, lon);
p += lon;
strcpy(p, LAMESTRING);
p += strlen(LAMESTRING);
memcpy(p, dest, strlen(dest));
p += strlen(dest);
// memcpy(p, sod, lod);
// p += lod;
*p++ = ' ';
*p++ = ':';
if (action) {
*p++ = 1;
strcpy(p, "ACTION ");
p += strlen("ACTION ");
}
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (out && !ischannel(*dest)) {
strcpy(p, PMSG_ARROW);
p += strlen(PMSG_ARROW);
}
#pragma GCC diagnostic pop
if (logdata->user->backlog_timestamp != BLTSNone) {
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;
}
static time_t compute_time(const char *buf)
{
struct tm tm;
time_t tv;
/* this is to fill tm_isdst to current tm, expect brokennes when dst
* changes */
time(&tv);
tm = *localtime(&tv);
if (strptime(buf, "%d-%m-%Y %H:%M:%S", &tm) == NULL)
return (time_t)-1;
return mktime(&tm);
}
static int log_backread_file(log_t *log, logstore_t *store, logfile_t *lf,
list_t *res, const char *dest, time_t start)
{
char *buf, *logbr;
int close = 0;
// mylog(LOG_ERROR, "log_backread_file store:", store->name);
if (!lf->file) {
lf->file = fopen(lf->filename, "r");
if (!lf->file) {
mylog(LOG_ERROR, "Can't open %s for reading",
lf->filename);
list_add_last(res, _log_wrap("Error opening logfile",
store->name));
return 0;
}
close = 1;
}
if (!start && list_it_item(&store->file_it) == lf) {
/*
mylog(LOG_ERROR, "bread Seeking %s to %d", lf->filename,
store->file_offset);
*/
if (fseek(lf->file, store->file_offset, SEEK_SET)) {
mylog(LOG_ERROR, "Can't seek in %s", lf->filename);
list_add_last(res,
_log_wrap(store->name,
"Error seeking in logfile"));
return 0;
}
} else {
// mylog(LOG_ERROR, "bread Seeking %s to %d", lf->filename, 0);
if (fseek(lf->file, (long)0, SEEK_SET)) {
mylog(LOG_ERROR, "Can't seek in %s", lf->filename);
list_add_last(res,
_log_wrap(store->name,
"Error seeking in logfile"));
return 0;
}
}
buf = bip_malloc((size_t)LOGLINE_MAXLEN + 1);
for (;;) {
if (!fgets(buf, LOGLINE_MAXLEN, lf->file)) {
if (ferror(lf->file)) {
list_add_last(res, _log_wrap("Error reading "
"logfile",
store->name));
}
/* error or oef */
break;
}
size_t slen = strlen(buf);
if (slen == 0)
break; // should not happen, per previous fgets block
if (buf[slen - 1] == '\n')
buf[slen - 1] = 0;
if (slen >= 2 && buf[slen] == '\r')
buf[slen - 2] = 0;
if (buf[0] == 0 || buf[0] == '\n')
continue;
if (start != 0) {
time_t linetime = compute_time(buf);
/* parse error, don't backlog */
if (linetime == (time_t)-1) {
list_add_last(res, _log_wrap("Error in "
"timestamp in %s",
store->name));
continue;
}
/* too old line, don't backlog */
if (linetime < start)
continue;
}
logbr = log_beautify(log, buf, store->name, dest);
if (logbr)
list_add_last(res, logbr);
}
if (close) {
fclose(lf->file);
lf->file = NULL;
}
free(buf);
// mylog(LOG_ERROR, "end of log_backread_file store: %s", store->name);
return 1;
}
static list_t *log_backread(log_t *log, const char *storename, const char *dest)
{
list_t *ret;
if (!log->user->always_backlog && log->connected)
return NULL;
logstore_t *store = hash_get(&log->logfgs, storename);
if (!store)
return NULL;
if (!store->track_backlog)
return NULL;
if (store->memlog) {
list_iterator_t li;
ret = list_new(NULL);
for (list_it_init(store->memlog, &li); list_it_item(&li);
list_it_next(&li))
list_add_last(ret, bip_strdup(list_it_item(&li)));
return ret;
}
if (!log->log_to_file) {
mylog(LOG_DEBUG, "No conf_log, not backlogging");
return NULL;
}
list_iterator_t file_it = store->file_it;
logfile_t *logf;
ret = list_new(NULL);
for (file_it = store->file_it; (logf = list_it_item(&file_it));
list_it_next(&file_it)) {
if (!log_backread_file(log, store, logf, ret, dest,
(time_t)0)) {
log_reinit(store);
return ret;
}
}
return ret;
}
static char *_log_wrap(const char *dest, const char *line)
{
char *buf;
int count;
buf = bip_malloc((size_t)LOGLINE_MAXLEN + 1);
count = snprintf(buf, (size_t)LOGLINE_MAXLEN + 1,
":" P_IRCMASK " PRIVMSG %s :%s\r\n", dest, line);
if (count < 0) {
mylog(LOG_ERROR, "_log_wrap: error on snprintf: %s",
strerror(errno));
buf[LOGLINE_MAXLEN - 2] = '\r';
buf[LOGLINE_MAXLEN - 1] = '\n';
buf[LOGLINE_MAXLEN] = 0;
return buf;
}
if (count >= LOGLINE_MAXLEN + 1) {
mylog(LOG_WARN, "_log_wrap: line too long");
buf[LOGLINE_MAXLEN - 2] = '\r';
buf[LOGLINE_MAXLEN - 1] = '\n';
buf[LOGLINE_MAXLEN] = 0;
}
return buf;
}
static size_t _log_write(log_t *logdata, logstore_t *store,
const char *destination, const char *str)
{
size_t nbwrite;
size_t len;
static char tmpstr[LOGLINE_MAXLEN + 1];
strncpy(tmpstr, str, (size_t)LOGLINE_MAXLEN);
tmpstr[LOGLINE_MAXLEN] = 0;
if (store->memlog) {
char *r =
log_beautify(logdata, tmpstr, store->name, destination);
if (r != NULL) {
list_add_last(store->memlog, r);
if (store->memc == logdata->user->backlog_lines)
free(list_remove_first(store->memlog));
else
store->memc++;
}
}
if (!logdata->log_to_file)
return 0;
logfile_t *lf = list_get_last(&store->file_group);
len = strlen(tmpstr);
nbwrite = fwrite(tmpstr, sizeof(char), len, lf->file);
nbwrite += fwrite("\n", sizeof(char), (size_t)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, store);
return nbwrite;
}
void log_write(log_t *logdata, const char *destination, const char *str)
{
logstore_t *store = log_find_file(logdata, destination);
size_t written;
if (!store) {
mylog(LOG_ERROR, "Unable to find/create logfile for '%s'",
destination);
return;
}
written = _log_write(logdata, store, destination, str);
if (written <= 0)
mylog(LOG_WARN, "log_write to '%s' failed", destination);
}
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)) {
logstore_t *store = hash_it_item(&hi);
list_iterator_t lj;
for (list_it_init(&store->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 bipuser *user, const char *network)
{
log_t *logdata;
logdata = (log_t *)bip_calloc(sizeof(log_t), (size_t)1);
logdata->user = user;
logdata->network = bip_strdup(network);
hash_init(&logdata->logfgs, HASH_NOCASE);
logdata->buffer = (char *)bip_malloc((size_t)LOGLINE_MAXLEN + 1);
logdata->buffer[LOGLINE_MAXLEN - 1] = 0; // debug
logdata->buffer[LOGLINE_MAXLEN] = 0;
logdata->connected = 0;
logdata->log_to_file = conf_log;
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;
logstore_t *store;
list_remove(log_all_logs, log);
free(log->network);
free(log->buffer);
for (hash_it_init(&log->logfgs, &it); (store = hash_it_item(&it));
hash_it_next(&it)) {
log_reset(store);
log_store_free(store);
}
hash_clean(&log->logfgs);
free(log);
}
list_t *log_backlogs(log_t *log)
{
return hash_keys(&log->logfgs);
}
array_t *str_split(const char *str, const char *splt)
{
const char *p = str;
const char *start = str;
size_t len;
char *extracted;
array_t *array = array_new();
;
do {
if (!*p || strchr(splt, *p)) {
len = (size_t)(p - start);
extracted = bip_malloc(len + 1);
memcpy(extracted, start, len);
extracted[len] = 0;
array_push(array, extracted);
if (!*p)
return array;
else
start = p + 1;
}
} while (*p++);
fatal("never reached");
return NULL;
}
/* 26-12-2008 08:54:39 */
int log_parse_date(char *strdate, int *year, int *month, int *mday, int *hour,
int *min, int *sec)
{
array_t *fields;
char *sptr;
int ret = 0;
fields = str_split(strdate, ":- ");
if (array_count(fields) == 6) {
*year = atoi(array_get(fields, 2));
*month = atoi(array_get(fields, 1));
*mday = atoi(array_get(fields, 0));
*hour = atoi(array_get(fields, 3));
*min = atoi(array_get(fields, 4));
*sec = atoi(array_get(fields, 5));
ret = 1;
}
int i;
array_each(fields, i, sptr) free(sptr);
array_free(fields);
return ret;
}
void logstore_get_file_at(logstore_t *store, time_t at, list_iterator_t *li)
{
for (list_it_init(&store->file_group, li); list_it_item(li);
list_it_next(li)) {
logfile_t *lf = list_it_item(li);
if (mktime(&lf->last_log) > at)
return;
}
}
static list_t *log_backread_hours(log_t *log, const char *storename,
const char *dest, int hours)
{
time_t blstarttime;
struct timeval tv;
logstore_t *store;
logfile_t *logf;
list_t *ret;
list_iterator_t file_it;
gettimeofday(&tv, NULL);
if (tv.tv_sec <= 3600 * hours)
return NULL;
blstarttime = tv.tv_sec - 3600 * hours;
store = hash_get(&log->logfgs, storename);
if (!store)
return NULL;
ret = list_new(NULL);
for (logstore_get_file_at(store, blstarttime, &file_it);
(logf = list_it_item(&file_it)); list_it_next(&file_it)) {
if (!log_backread_file(log, store, logf, ret, dest,
blstarttime)) {
log_reinit(store);
return ret;
}
}
return ret;
}
list_t *backlog_lines(log_t *log, const char *bl, const char *cli_nick,
int hours)
{
list_t *ret;
struct line l;
const char *dest;
ret = NULL;
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (ischannel(*bl))
dest = bl;
else
dest = cli_nick;
#pragma GCC diagnostic pop
if (log_has_backlog(log, bl) || hours) {
if (hours == 0)
ret = log_backread(log, bl, dest);
else
ret = log_backread_hours(log, bl, dest, hours);
if (ret && !list_is_empty(ret)) {
/*
* This exception is cosmetic, but you want it.
* Most of the time, you get backlog from your own nick
* for your mode changes only.
* Hence opening a query just to say "end of backlog"...
*/
if (strcmp(bl, cli_nick) != 0) {
/* clean this up */
irc_line_init(&l);
l.origin = P_IRCMASK;
if (dest == cli_nick)
l.origin = bip_strdup(bl);
_irc_line_append(&l, "PRIVMSG");
_irc_line_append(&l, dest);
_irc_line_append(&l, "End of backlog");
list_add_last(ret, irc_line_to_string(&l));
_irc_line_deinit(&l);
}
}
}
return ret;
}