/*
+-------------------------------------------------------------------------+
|                           loghorn vers. 0.1.01                          |
|    Copyright (c) Andrey V. Stolyarov  <croco at croco dot net>  2026    |
| ----------------------------------------------------------------------- |
| This is free software.  Permission is granted to everyone to use, copy  |
|    or modify this software under the terms and conditions of either     |
|             CROCO'S INDIVIDUALISTIC FREE SOFTWARE LICENSE               |
|                          or, at your option,                            |
|               GNU LESSER GENERAL PUBLIC LICENSE, v. 2.1                 |
|                                                                         |
| Please visit http://www.croco.net/software/loghorn  to get a fresh copy |
| ----------------------------------------------------------------------- |
|   This code is provided strictly and exclusively on the "AS IS" basis.  |
|        THERE IS NO WARRANTY OF ANY KIND, DON'T EVEN THINK ABOUT IT      |
+-------------------------------------------------------------------------+
*/




#define _XOPEN_SOURCE 500      /* for vsnprintf */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>

#include "servlog.h"



static int stderr_mode = srvl_normal | srvl_private;

static int syslog_mode = 0;

static FILE *log_file = NULL;
static int filelog_mode = 0;


struct extra_log_item *first_extra = NULL;

void setup_stderr_log(int mode)
{
    stderr_mode = mode;
}

int setup_file_log(int mode, const char *filename)
{
    if(log_file) {  /* we'll setup it again anyway */
        fclose(log_file);
        log_file = NULL;
        filelog_mode = 0;
    }
    if(mode == 0)
        return 1;
    log_file = fopen(filename, "a");
    if(!log_file) {
        servlog_perror(srvl_alert, "servlog", filename);
        return 0;
    }
    filelog_mode = mode;
    return 1;
}

static int facil_srvl_to_sys(int n)
{
    switch(n) {
        case srvl_facil_user:   return LOG_USER;
        case srvl_facil_daemon: return LOG_DAEMON;
        case srvl_facil_local0: return LOG_LOCAL0;
        case srvl_facil_local1: return LOG_LOCAL1;
        case srvl_facil_local2: return LOG_LOCAL2;
        case srvl_facil_local3: return LOG_LOCAL3;
        case srvl_facil_local4: return LOG_LOCAL4;
        case srvl_facil_local5: return LOG_LOCAL5;
        case srvl_facil_local6: return LOG_LOCAL6;
        case srvl_facil_local7: return LOG_LOCAL7;
    }
    return LOG_USER;
}

static int prio_srvl_to_sys(int n)
{
    switch(n & 0xff) {
        case srvl_alert:  return LOG_ERR;
        case srvl_normal: return LOG_NOTICE;
        case srvl_info:   return LOG_INFO;
        case srvl_debug:  return LOG_DEBUG;
/*      case srvl_debug2: return LOG_DEBUG; // debug2 never sent to syslog */
    }
    return LOG_ERR;
}

void setup_syslog(int mode, const char *ident, int facility)
{
    int fac;

    if(syslog_mode != srvl_disable)
        closelog();
    syslog_mode = srvl_disable;

    if(mode == srvl_disable)
        return;

    if(mode > srvl_debug)
        mode = srvl_debug;  /* debug2 messages are never sent to syslog */

    fac = facil_srvl_to_sys(facility);
    openlog(ident, LOG_CONS | LOG_NDELAY | LOG_NOWAIT | LOG_PID, fac);
    syslog_mode = mode;
}

static int should_log(int channel_mode, int message_mode)
{
    return channel_mode &&
        (channel_mode & 0xff) >= (message_mode & 0xff) &&
        (!(message_mode & srvl_private) || (channel_mode & srvl_private));
}

/* we have to compose the date on our own, to avoid locales */
static const char * const month_names[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

void servlog_message_vl(int level, const char *fmt, va_list args)
{
    static char buf[4096];
    char *message;
    int len, bufrest, mlen;
    time_t tt;  /* we have to :-( gmtime needs a pointer to it */
    struct tm *gmt;
    int do_stderr, do_file, do_syslog;
    struct extra_log_item *tmp;

    do_stderr = should_log(stderr_mode, level);
    do_file   = should_log(filelog_mode, level);
    do_syslog = should_log(syslog_mode, level);

    if(!do_stderr && !do_file && !do_syslog && !first_extra)
        return;

    tt = time(NULL);
    gmt = gmtime(&tt);
    len = sprintf(buf, "%3.3s %02d %02d:%02d:%02d [%ld] ",
                  month_names[gmt->tm_mon], gmt->tm_mday,
                  gmt->tm_hour, gmt->tm_min, gmt->tm_sec,
                  (long)getpid());

    message = buf + len;
    bufrest = sizeof(buf) - len;

    mlen = vsnprintf(message, bufrest, fmt, args);
    if(mlen >= bufrest)
        mlen = bufrest - 1;   /* because it couldn't write more! */
    len += mlen;

    if(do_syslog) {
            /* syslog is the only case where we don't need the '\n' */
        syslog(prio_srvl_to_sys(level), "%s", message);
    }

    buf[len] = '\n';
    len++;
    buf[len] = 0;

    if(do_stderr) {
        fputs(buf, stderr);
        fflush(stderr);
    }
    if(do_file) {
        fputs(buf, log_file);
        fflush(log_file);
    }
    for(tmp = first_extra; tmp; tmp = tmp->p2)
        if(should_log(tmp->level, level))
            tmp->f(tmp->userdata, buf);
}

void servlog_message(int level, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    servlog_message_vl(level, fmt, args);
    va_end(args);
}

void servlog_perror(int level, const char *s1, const char *s2)
{
    if(s1 && *s1)
        servlog_message(level, "%s: %s: %s", s1, s2, strerror(errno));
    else
        servlog_message(level, "%s: %s", s2, strerror(errno));
}

void add_extra_log(struct extra_log_item *item)
{
    item->p1 = NULL;   /* no previous element! */
    item->p2 = first_extra;
    if(first_extra)
        first_extra->p1 = item;
    first_extra = item;
}

void remove_extra_log(struct extra_log_item *item)
{
    if(item->p2)
        item->p2->p1 = item->p1;
    if(item->p1)
        item->p1->p2 = item->p2;
    else
        first_extra = item->p2;
    item->p1 = NULL;
    item->p2 = NULL;
}
