/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * The system logger (syslogger) is new in Postgres 7.5. It catches all 
 * stderr output from backends, the postmaster and subprocesses by 
 * redirecting to a pipe, and writes it to a logfile and stderr if 
 * configured.
 * It's possible to have size and age limits for the logfile configured
 * in postgresql.conf. If these limits are reached or passed, the 
 * current logfile is closed and a new one is created (rotated).
 * The logfiles are stored in a subdirectory (configurable in 
 * postgresql.conf), using an internal naming scheme that mangles 
 * creation time and current postmaster pid. 
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/pmsignal.h"
#include "storage/pg_shmem.h"
#include "storage/ipc.h"
#include "postmaster/syslogger.h"
#include "utils/ps_status.h"
#include "utils/guc.h"

/*
 * GUC parameters
 */
int			Log_RotationAge = 24*60;
int			Log_RotationSize  = 10*1024;
char *      Log_directory = "pg_log";
char *      Log_filename_prefix = "postgresql-";


extern pid_t SysLoggerPID;


/*
 * Flags set by interrupt handlers for later service in the main loop.
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;

#define MAXRETRIES  3
static pg_time_t	last_rotation_time = 0;
static int          pipeRetry=0;
static char         currentLogDir[MAXPGPATH];


static void sigHupHandler(SIGNAL_ARGS);
static void rotationHandler(SIGNAL_ARGS);

#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec(void);
#endif

static char* logfile_getname(pg_time_t timestamp);
static bool logfile_rotate(void);

FILE *realStdErr = NULL;
FILE *syslogFile = NULL;
int syslogPipe[2] = {0, 0};



/*
 * Main entry point for syslogger process
 * argc/argv parameters are valid only in EXEC_BACKEND case.
 */
void
SysLoggerMain(int argc, char *argv[])
{
#ifdef EXEC_BACKEND

    int fd;
	Assert(argc == 8);

	argv += 3;
	StrNCpy(postgres_exec_path,	*argv++, MAXPGPATH);
	syslogPipe[0] = atoi(*argv++);
	syslogPipe[1] = atoi(*argv++);
	fd = atoi(*argv++);
	if (fd != 0)
	    syslogFile = fdopen(fd, "a+");
	fd = atoi(*argv++);
	if (fd != 0)
	    realStdErr = fdopen(fd, "a+");
	
#endif

	IsUnderPostmaster = true;
	MyProcPid = getpid();
	init_ps_display("system logger process", "", "");
	set_ps_display("");

	/*
	 * Properly accept or ignore signals the postmaster might send us
	 *
	 * Note: we ignore all termination signals, and wait for the postmaster
	 * to die to catch as much pipe output as possible.
	 */

	pqsignal(SIGHUP, sigHupHandler);	/* set flag to read config file */
	pqsignal(SIGINT,  SIG_IGN);	
	pqsignal(SIGTERM, SIG_IGN);	
	pqsignal(SIGQUIT, SIG_IGN);
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, rotationHandler);  /* request log rotation */
	pqsignal(SIGUSR2, SIG_IGN);

	/*
	 * Reset some signals that are accepted by postmaster but not here
	 */
	pqsignal(SIGCHLD, SIG_DFL);
	pqsignal(SIGTTIN, SIG_DFL);
	pqsignal(SIGTTOU, SIG_DFL);
	pqsignal(SIGCONT, SIG_DFL);
	pqsignal(SIGWINCH, SIG_DFL);

	PG_SETMASK(&UnBlockSig);

	/* 
	 * if we restarted, our stderr is redirected. 
	 * Direct it back to system stderr.
	 */
	if (realStdErr != NULL)
	{
	    if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
		{
		    char *errstr = strerror(errno);
			/*
			 * Now we have a real problem: we can't redirect to stderr,
			 * and can't ereport it correctly (it would go into our queue
			 * which will never be read
			 * We're writing everywhere, hoping to leave at least some
			 * hint of what happened.
			 */
		    
		    fprintf(realStdErr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
			fprintf(stderr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
			
			ereport(PANIC,
					(errcode_for_file_access(),
					 (errmsg("Syslogger couldn't redirect its stderr to the saved stderr: %s", errstr))));
			exit(1);
		}

		realStdErr = NULL;
	}

	/* we'll never write that pipe */
	close(syslogPipe[1]);
	syslogPipe[1] = 0;
	/* remember age of initial logfile */
	last_rotation_time = time(NULL);

	strncpy(currentLogDir, Log_directory, MAXPGPATH);
	/* main worker loop */
	for (;;)
	{
		pg_time_t	now;
		int			elapsed_secs;
		char        logbuffer[1024];
		char        bytesRead;
		int         rc;
		fd_set		rfds;
		struct timeval timeout;

		if (got_SIGHUP)
		{
			got_SIGHUP = false;
			ProcessConfigFile(PGC_SIGHUP);

		    /*
			 * check if the log directory changed 
			 * in postgresql.conf. If so, we rotate to make sure
			 * we're writing the logfiles where the backends
			 * expect us to do so.
			 */
			if (strncmp(Log_directory, currentLogDir, MAXPGPATH))
			{
				rotation_requested = true;
				strncpy(currentLogDir, Log_directory, MAXPGPATH);
			}
		}

		if (!rotation_requested && last_rotation_time != 0 && Log_RotationAge > 0)
		{
		    /*
			 * Do an unforced rotation if too much time has elapsed
			 * since the last one.
			 */
		    now = time(NULL);
			elapsed_secs = now - last_rotation_time;
			if (elapsed_secs >= Log_RotationAge * 60)
			    rotation_requested = true;
		}

		if (!rotation_requested && Log_RotationSize > 0)
		{
		    /*
			 * Do an unforced rotation if file is too big
			 */
		    if (ftell(syslogFile) >= Log_RotationSize * 1024)
			    rotation_requested = true;
		}


		if (rotation_requested)
		{
		    if (!logfile_rotate())
			{
			    ereport(ERROR,
						(errcode_for_file_access(),
						 (errmsg("logfile rotation failed, disabling auto rotation (SIGHUP to reenable): %m"))));
			  
				Log_RotationAge = 0;
				Log_RotationSize = 0;
			}
		    rotation_requested = false;
		}

		FD_ZERO(&rfds);
		FD_SET(syslogPipe[0], &rfds);
		timeout.tv_sec=1;
		timeout.tv_usec=0;

		/*
		 * Check if data is present
		 */
		rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout);
		PG_SETMASK(&UnBlockSig);

		if (rc < 0 && errno != EINTR)
		{
			ereport(COMMERROR,
					(errcode_for_socket_access(),
					 errmsg("select() failed in system logger: %m")));
			exit(1);
		}

		if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds))
		{
		    bytesRead = piperead(syslogPipe[0],
								 logbuffer, sizeof(logbuffer));
			
			if (bytesRead < 0 && errno != EINTR)
			{
			    /* 
				 * EMFILE has been observed when a backend terminated
				 * after a SSL client connection broke down.
				 * There might be others we should ignore.
				 */
			    if (errno == EMFILE)
				    continue;

				ereport(COMMERROR,
						(errcode_for_socket_access(),
						 errmsg("could not read from system logger pipe: %m")));
				exit(1);
			}
			else if (bytesRead > 0)
			{
			    pipeRetry = 0;

				if (fwrite(logbuffer, 1, bytesRead, syslogFile) < 1)
				{
				    ereport(COMMERROR,
							(errcode_for_file_access(),
							 errmsg("fwrite to logfile failed in system logger: %m")));
					exit(1);
				}
				fflush(syslogFile);

				if (Log_destination & LOG_DESTINATION_STDERR)
				{
				    if (realStdErr)
					{
						fwrite(logbuffer, 1, bytesRead, realStdErr);
						fflush(realStdErr);
					}
					else
					{
						fwrite(logbuffer, 1, bytesRead, stderr);
						fflush(stderr);
					}
				}
				continue;
			}
		}

		/*
		 * If postmaster died, there's nothing to log any more.
		 * We check this only after pipe timeouts to receive as much as possible
		 * from the pipe.
		 */
		if (!PostmasterIsAlive(true))
		{
			if (syslogFile)
			    fclose(syslogFile);
			exit(1);
		}
	}
}



void 
SysLogger_SIGHUP(void)
{
#ifdef WIN32
	got_SIGHUP = true;
#else
    kill(SysLoggerPID, SIGHUP);
#endif
}


void
SysLogger_SIGUSR1(void)
{
#ifdef WIN32
	rotation_requested = true;
#else
	kill(SysLoggerPID, SIGUSR1);
#endif
}

int
SysLogger_Start(void)
{
#ifndef WIN32
    pid_t sysloggerPid;
	pg_time_t now;
	char *filename;

	if (!(Log_destination & LOG_DESTINATION_FILE))
	    return 0;

	/* create the pipe which will receive stderr output */	
    if (!syslogPipe[0])
	{
	    if (pgpipe(syslogPipe) < 0)
		    ereport(FATAL,
					(errcode_for_file_access(),
					 (errmsg("pipe for syslogging not created: %m"))));

		if (!set_noblock(syslogPipe[1]))
		{
			ereport(FATAL,
					(errcode_for_socket_access(),
					 errmsg("could not set syslogging pipe to nonblocking mode: %m")));
		}
	}

	now = time(NULL);

	/*
	 * The initial logfile is created right in the postmaster,
	 * to insure that the logger process has a writable file.
	 */
	filename = logfile_getname(now);

	/*
	 * The file is opened for appending, in case the syslogger 
	 * is restarted right after a rotation.
	 */
	syslogFile = fopen(filename, "a+");

	if (!syslogFile)
	{
	    /* 
		 * if we can't open the syslog file for the syslogger process,
		 * we try to redirect stderr back to have some logging.
		 */
	    ereport(WARNING,
				(errcode_for_file_access(),
				 (errmsg("error opening syslog file %s: %m", filename))));

		if (realStdErr != NULL)
		{
		    if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
			    ereport(FATAL,
						(errcode_for_file_access(),
						 (errmsg("error redirecting stderr to default: %m"))));

			ereport(FATAL,
					(errmsg("logfile output corrupted")));
		}

	}
	pfree(filename);

	fflush(stdout);
	fflush(stderr);

#ifdef __BEOS__
	/* Specific beos actions before backend startup */
	beos_before_backend_startup();
#endif


#ifdef EXEC_BACKEND
	switch ((sysloggerPid = syslogger_forkexec()))
#else
	switch ((sysloggerPid = fork()))
#endif
	{
		case -1:
#ifdef __BEOS__
			/* Specific beos actions */
		    beos_backend_startup_failed();
#endif
			ereport(LOG,
					(errmsg("could not fork system logger: %m")));
			return 0;

#ifndef EXEC_BACKEND
		case 0:
			/* in postmaster child ... */
#ifdef __BEOS__
			/* Specific beos actions after backend startup */
			beos_backend_startup();
#endif
			/* Close the postmaster's sockets */
			ClosePostmasterPorts();

			/* Drop our connection to postmaster's shared memory, as well */
			PGSharedMemoryDetach();

			/* do the work */
			SysLoggerMain(0, NULL);
			break;
#endif

		default:
		    /* now we redirect stderr, if not done already */
		    if (realStdErr == NULL)
			{
			    int dh= dup(fileno(stderr));

				if (dh < 0)
				    ereport(FATAL,
							(errcode_for_file_access(),
							 (errmsg("stderr duplication failed: %m"))));

				realStdErr = fdopen(dh, "a");
				if (realStdErr == NULL)
				    ereport(FATAL,
							(errcode_for_file_access(),
							 (errmsg("realstderr reopen failed: %m"))));

				if (dup2(syslogPipe[1], fileno(stdout)) < 0)
				    ereport(FATAL,
							(errcode_for_file_access(),
						  (errmsg("stdout pipe redirection failed: %m"))));

				if (dup2(syslogPipe[1], fileno(stderr)) < 0)
				    ereport(FATAL,
							(errcode_for_file_access(),
						  (errmsg("stderr pipe redirection failed: %m"))));
			}

			/* postmaster will never write the file; close it */
			fclose(syslogFile);
			syslogFile = NULL;

			return (int) sysloggerPid;
	}
#endif
	/* we should never reach here */
	return 0;
}


#ifdef EXEC_BACKEND
static pid_t
syslogger_forkexec()
{
	char *av[10];
	int ac = 0, bufc = 0, i;
	char numbuf[2][32];

	av[ac++] = "postgres";
	av[ac++] = "-forklog";
	av[ac++] = NULL;			/* filled in by postmaster_forkexec */

	/* postgres_exec_path is not passed by write_backend_variables */
	av[ac++] = postgres_exec_path;

	/* Pipe file ids (those not passed by write_backend_variables) */
	snprintf(numbuf[bufc++],32,"%d", syslogPipe[0]);
	snprintf(numbuf[bufc++],32,"%d", syslogPipe[1]);
	if (syslogFile != 0)
	    snprintf(numbuf[bufc++],32,"%d", fileno(syslogFile));
    else
	    strcpy(numbuf[bufc++], "0");
	if (realStdErr != 0)
	    snprintf(numbuf[bufc++],32,"%d", fileno(realStdErr));
    else
	    strcpy(numbuf[bufc++], "0");

	/* Add to the arg list */
	Assert(bufc <= lengthof(pgstatBuf));
	for (i = 0; i < bufc; i++)
		av[ac++] = numbuf[i];

	av[ac] = NULL;
	Assert(ac < lengthof(av));

	return postmaster_forkexec(ac, av);
}
#endif

/* --------------------------------
 *		logfile routines
 * --------------------------------
 */


/*
 * perform rotation
 */
bool
logfile_rotate(void)
{
	char *filename;
	pg_time_t now;
	FILE *fh;

	now = time(NULL);
	filename = logfile_getname(now);
	
	fh = fopen(filename, "a+");
	if (!fh)
	{
	    /*
		 * if opening the new file fails, the caller is responsible
		 * for taking consequences. */

		pfree(filename);
		return false;
	}

	fclose(syslogFile);
	syslogFile = fh;

	last_rotation_time = now;

	/* official opening of the new logfile */
	ereport(NOTICE,
			(errcode(ERRCODE_WARNING),
			 errmsg("Opened new log file %s", filename)));

	pfree(filename);
	return true;
}



/*
 * creates logfile name using timestamp information
 */

#define TIMESTAMPPATTERN "%Y-%m-%d_%H%M%S"

static char*
logfile_getname(pg_time_t timestamp)
{
	char *filetemplate;
	char *filename;


	if (is_absolute_path(Log_directory))
	{
	  filetemplate = palloc(strlen(Log_directory)
							+ strlen(Log_filename_prefix)
							+ sizeof(TIMESTAMPPATTERN)+10 +2);
		if (filetemplate)
		    sprintf(filetemplate, "%s/%s%s_%05lu.log", 
					Log_directory, Log_filename_prefix, 
					TIMESTAMPPATTERN, (unsigned long)PostmasterPid);
	}
	else
	{
		filetemplate = palloc(strlen(DataDir) + strlen(Log_directory) 
							  + strlen(Log_filename_prefix)
							  + sizeof(TIMESTAMPPATTERN) +10 +3);
		if (filetemplate)
		    sprintf(filetemplate, "%s/%s/%s%s_%05lu.log", 
					DataDir, Log_directory, Log_filename_prefix,
					TIMESTAMPPATTERN, (unsigned long)PostmasterPid);
	}
	filename = palloc(MAXPGPATH);

	if (!filename || !filetemplate)
	    ereport(FATAL,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("Out of memory")));

	pg_strftime(filename, MAXPGPATH, filetemplate, pg_localtime(&timestamp));

	pfree(filetemplate);

	return filename;
}

/* --------------------------------
 *		API helper routines
 * --------------------------------
 */

/*
 * Rotate log file
 */
bool
LogFileRotate(void)
{
    if (!(Log_destination & LOG_DESTINATION_FILE))
	{
	    ereport(NOTICE,
				(errcode(ERRCODE_WARNING),
				 errmsg("no logfile configured; rotation not supported")));
	    return false;
	}

	SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);

	return true;
}

/* --------------------------------
 *		signal handler routines
 * --------------------------------
 */

/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
{
	got_SIGHUP = true;
}

/* SIGUSR1: set flag to rotate logfile */
static void
rotationHandler(SIGNAL_ARGS)
{
	rotation_requested = true;
}
