/* log.c -- Logging routines, for a single, program-wide logging facility
 * Created: Mon Mar 10 09:37:21 1997 by faith@dict.org
 * Copyright 1997-1999, 2001-2002 Rickard E. Faith (faith@dict.org)
 * Copyright 2002-2008 Aleksey Cheusov (vle@gmx.net)
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * 
 */

#include "maaP.h"
#ifdef HAVE_SYSLOG_NAMES
#define SYSLOG_NAMES
#endif
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

#if HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif

static int        logFd = -1;
static FILE       *logUserStream;
static int        logSyslog;
static int        inhibitFull = 0;

static int        logOpen;
static int        logFacility = LOG_USER;

static const char *logIdent;
static const char *logFilenameOrig;
static char       *logFilename;
static char       *logFilenameTmp;
static int        logFilenameLen;
static char       logHostname[MAXHOSTNAMELEN];

#ifndef HAVE_SYSLOG_NAMES
typedef struct _code {
    const char *c_name;
    int        c_val;
} CODE;
CODE facilitynames[] = {
#if LOG_AUTH
    { "auth",     LOG_AUTH },
#endif
#if LOG_AUTHPRIV
    { "authpriv", LOG_AUTHPRIV },
#endif
#if LOG_CRON
    { "cron",     LOG_CRON },
#endif
#if LOG_DAEMON
    { "daemon",   LOG_DAEMON },
#endif
#if LOG_FTP
    { "ftp",      LOG_FTP },
#endif
#if LOG_KERN
    { "kern",     LOG_KERN },
#endif
#if LOG_LPR
    { "lpr",      LOG_LPR },
#endif
#if LOG_MAIL
    { "mail",     LOG_MAIL },
#endif
#if LOG_NEWS
    { "news",     LOG_NEWS },
#endif
#if LOG_SYSLOG
    { "syslog",   LOG_SYSLOG },
#endif
#if LOG_USER
    { "user",     LOG_USER },
#endif
#if LOG_UUCP
    { "uucp",     LOG_UUCP },
#endif
#if LOG_LOCAL0
    { "local0",   LOG_LOCAL0 },
#endif
#if LOG_LOCAL1
    { "local1",   LOG_LOCAL1 },
#endif
#if LOG_LOCAL2
    { "local2",   LOG_LOCAL2 },
#endif
#if LOG_LOCAL3
    { "local3",   LOG_LOCAL3 },
#endif
#if LOG_LOCAL4
    { "local4",   LOG_LOCAL4 },
#endif
#if LOG_LOCAL5
    { "local5",   LOG_LOCAL5 },
#endif
#if LOG_LOCAL6
    { "local6",   LOG_LOCAL6 },
#endif
#if LOG_LOCAL7
    { "local7",   LOG_LOCAL7 },
#endif
    { NULL,       -1 },
};
#endif

static void _log_set_hostname( void )
{
   static int hostnameSet = 0;
   char       *pt;

   if (!hostnameSet) {
      memset( logHostname, 0, sizeof(logHostname) );
      gethostname( logHostname, sizeof(logHostname)-1 );
      if ((pt = strchr(logHostname, '.'))) *pt = '\0';
      ++hostnameSet;
   }
}

void log_set_facility(const char *facility)
{
    CODE *pt;

    for (pt = facilitynames; pt->c_name; pt++) {
        if (!strcmp(pt->c_name, facility)) {
            logFacility = pt->c_val;
            return;
        }
    }
    err_fatal(__func__, "%s is not a valid facility name\n", facility);
}

const char *log_get_facility(void)
{
    CODE *pt;

    for (pt = facilitynames; pt->c_name; pt++)
        if (pt->c_val == logFacility) return pt->c_name;
    return NULL;
}

void log_option( int option )
{
   if (option == LOG_OPTION_NO_FULL) inhibitFull = 1;
   else                              inhibitFull = 0;
}

void log_syslog( const char *ident )
{
   if (ident){
      if (logSyslog)
	 err_internal( __func__, "Syslog facility already open\n" );

      openlog( ident, LOG_PID|LOG_NOWAIT, logFacility );
      ++logOpen;
      ++logSyslog;
   }else{
      if (!logSyslog)
	 return;

      closelog ();
      --logOpen;
      --logSyslog;
   }
}

static void log_mkpath(const char *filename)
{
    char *tmp = alloca(strlen(filename) + 1);
    char *pt;

    strcpy(tmp, filename);
    for (pt = tmp; *pt; pt++) {
        if (*pt == '/' && pt != tmp) {
            *pt = '\0';
            mkdir(tmp, 0755);
            *pt = '/';
        }
    }
}

static void _log_check_filename(void)
{
   time_t    t;
   struct tm *tm;
   
   if (!logFilename || !logFilenameTmp || !logFilenameLen) return;

   time(&t);
   tm = localtime(&t);
   
   strftime(logFilenameTmp, logFilenameLen, logFilenameOrig, tm);
   if (strcmp(logFilenameTmp, logFilename)) {
       strcpy(logFilename, logFilenameTmp);
       if (logFd >= 0) close(logFd);
       log_mkpath(logFilename);
       if ((logFd = open( logFilename, O_WRONLY|O_CREAT|O_APPEND, 0644 )) < 0)
           err_fatal_errno( __func__,
                            "Cannot open \"%s\" for append\n", logFilename );
   }
}

void log_file( const char *ident, const char *filename )
{
   if (ident && filename){
      if (logFd >= 0)
	 err_internal( __func__,
		       "Log file \"%s\" open when trying to open \"%s\"\n",
		       logFilename, filename );

      logIdent        = str_find( ident );
      logFilenameOrig = str_find(filename);
      logFilenameLen  = strlen(filename)*3+1024;
      logFilename     = xmalloc(logFilenameLen + 1);
      logFilenameTmp  = xmalloc(logFilenameLen + 1);
      logFilename[0]  = '\0';

      _log_check_filename();
      _log_set_hostname();

      ++logOpen;
   }else{
      if (logFd < 0)
	 return;

      close (logFd);
      logFd          = -1;

      if (logFilename) xfree (logFilename);
      logFilename    = NULL;

      if (logFilenameTmp) xfree (logFilenameTmp);
      logFilenameTmp = NULL;

      logFilenameLen = 0;

      --logOpen;
   }
}

void log_stream( const char *ident, FILE *stream )
{
   if (ident && stream){
      if (logUserStream)
	 err_internal( __func__, "User stream already open\n" );

      logUserStream = stream;
      logIdent      = str_find( ident );

      _log_set_hostname();
      ++logOpen;
   }else{
      if (!logUserStream)
	 return;

      if (logUserStream != stdout &&
	  logUserStream != stderr)
      {
	 fclose (logUserStream);
      }

      logUserStream = NULL;

      --logOpen;
   }
}

void log_close( void )
{
   log_file (NULL, NULL);
   log_stream (NULL, NULL);
   log_syslog (NULL);
}

static void _log_base_va(
   const char *routine,
   int log_facility,
   const char *format, va_list ap )
{
   time_t t;
   static char   buf [4096] = "";
   static char   buf_main [4096] = "";
   static char   buf_preamble [256] = "";

   if (!logOpen) return;

   time(&t);

   if (logFd >= 0 || logUserStream) {
      /* preamble */
      if (inhibitFull) {
         buf_preamble [0] = 0;
      } else {
         snprintf (buf_preamble, sizeof (buf_preamble),
		   "%24.24s %s %s[%ld]: ",
		   ctime(&t),
		   logHostname,
		   logIdent,
		   (long int) getpid());
      }

      /* main part of log message */
      vsnprintf (buf_main, sizeof (buf_main), format, ap );

      /* full log message */
      if (routine){
	 snprintf (buf, sizeof (buf), "%s(%s) %s\n",
		   buf_preamble, routine, buf_main);
      }else{
	 snprintf (buf, sizeof (buf), "%s%s\n",
		   buf_preamble, buf_main);
      }

      /* writing */
      if (logFd >= 0) {
         _log_check_filename();
         while (-1 == write (logFd, buf, strlen (buf)) && errno == EINTR);
      }
      if (logUserStream) {
         fseek( logUserStream, 0L, SEEK_END ); /* might help if luser didn't
                                                  open stream with "a" */
	 if (logUserStream == stdout || logUserStream == stderr)
	    fprintf( logUserStream, "%s", buf_main );
	 else
	    fprintf( logUserStream, "%s", buf );

         fflush( logUserStream );
      }
   }

#if HAVE_VSYSLOG
   if (logSyslog) {
      vsyslog( log_facility, format, ap );
   }
#endif
}

void log_error_va( const char *routine, const char *format, va_list ap )
{
   _log_base_va (routine, LOG_ERR, format, ap);
}

void log_error( const char *routine, const char *format, ... )
{
   va_list ap;

   va_start( ap, format );
   log_error_va( routine, format, ap );
   va_end( ap );
}

void log_info_va( const char *format, va_list ap )
{
   _log_base_va (NULL, LOG_INFO, format, ap);
}

void log_info( const char *format, ... )
{
   va_list ap;

   va_start( ap, format );
   log_info_va( format, ap );
   va_end( ap );
}
