/* $Header: log.c,v 2.3 90/05/03 10:13:31 chip Exp $
 *
 * Deliver logging.
 *
 * $Log:	log.c,v $
 * Revision 2.3  90/05/03  10:13:31  chip
 * Print correct filename when log can't be opened.
 * 
 * Revision 2.2  90/03/05  17:54:53  chip
 * Create log.c from various routines throughout the code.
 * 
 */

#include "deliver.h"
#include <time.h>

/*----------------------------------------------------------------------
 * Open temporary log files.
 */

openlogs()
{
#ifdef ERRLOG
	/* If we're delivering and not being verbose, keep an error log. */

	if (!dryrun && !verbose)
	{
		errlogfile = tempfile();
		if ((errlog = ftcreate(errlogfile)) == NULL)
			syserr("can't create %s", errlogfile);
	}
#endif

#ifdef LOG
	/* If we're delivering and the log file exists, keep data for it. */

	if (!dryrun && exists(LOG))
	{
		logfile = tempfile();
		if ((log = ftcreate(logfile)) == NULL)
			syserr("can't create %s", logfile);
	}
#endif
}

/*------------------------------------------------------------------------
 * Discard temporary log files.
 */

tosslogs()
{
	if (logfile && unlink(logfile) == -1)
		(void) syserr("can't remove %s", logfile);
	if (errlogfile && unlink(errlogfile) == -1)
		(void) syserr("can't remove %s", logfile);
}

/*----------------------------------------------------------------------
 * Write a report to the log file.
 */

logreport(ac, av)
int     ac;
char    **av;
{
	int     a;

	if (!log)
		return;

	logstart(log);

	if (sender && *sender)
		(void) fprintf(log, "sender: %s\n", sender);
	if (boxdelivery)
		(void) fprintf(log, "mailbox%s:", (ac > 1) ? "es" : "");
	else
		(void) fprintf(log, "destination%s:", (ac > 1) ? "s" : "");
	for (a = 0; a < ac; ++a)
		(void) fprintf(log, " \"%s\"", av[a]);
	(void) fputc('\n', log);

	logstate("delivered", ST_DONE);
	logstate("failed", ST_ERROR);

	logdone(log);
}

/*----------------------------------------------------------------------
 * Log the destinations with the given state.
 * If any are found, the list is prefixed with the given description.
 */

logstate(desc, state)
char    *desc;
DSTATE  state;
{
	DEST    *d;
	int     dcount;

	dcount = 0;
	for (d = first_dest(); d; d = next_dest(d))
	{
		if (d->d_state != state)
			continue;

		if (++dcount == 1)
			(void) fprintf(log, "%s:", desc);
		(void) fprintf(log, " %s", d->d_name);
		if (d->d_class == CL_MBOX)
			(void) fprintf(log, ":%s", d->d_param);
		else if (d->d_class == CL_PROG)
			(void) fprintf(log, "|\"%s\"", d->d_param);
	}
	if (dcount)
		(void) fputc('\n', log);
}

/*----------------------------------------------------------------------
 * Save contents of temporary logs in the real logfiles.
 */

savelogs()
{
	/* If logs weren't kept, forget it. */

	if (!log && !errlog)
		return;

	/* If temporary logs contain anything, append them to real logs. */

	if ((log && ftell(log) > 0)
	 || (errlog && ftell(errlog) > 0))
	{
		if (create_lockfile(LOGLOCK) == 0)
		{
#ifdef LOG
			applog(&log, LOG);
#endif
			errdone();
#ifdef ERRLOG
			applog(&errlog, ERRLOG);
#endif
			(void) remove_lockfile(LOGLOCK);
		}
	}
}

/*----------------------------------------------------------------------
 * Append a temporary log file to a real logfile.
 * We pass a FILE **, so that it can be set to NULL when closed;
 * this is important, since errlog is used by syserr().
 * Note:  The logfile is ass_u_med to be locked already!
 */

applog(fpp, realfile)
FILE	**fpp;
char	*realfile;
{
	FILE	*fp = fpp ? *fpp : NULL;
	int	fd, realfd;

	/* If log data weren't kept, never mind. */

	if (fp == NULL)
		return;

	/* Flush buffered data. */

	(void) fflush(fp);

	/* If the file is empty, never mind. */

	if (ftell(fp) == 0)
	{
		(void) fclose(fp);
		*fpp = NULL;
		return;
	}

	/* Get an fd and close the stream. */

	if ((fd = dup(fileno(fp))) == -1)
	{
		syserr("can't dup log fd");
		(void) fclose(fp);
		*fpp = NULL;
		return;
	}
	(void) fclose(fp);
	*fpp = NULL;

	/*
	 * Open the real logfile, creating it if necessary.
	 * Note that there is no race condition since the logs are locked.
	 */

#ifdef O_CREAT
	realfd = open(realfile, O_WRONLY|O_CREAT, 0666);
#else
	if ((realfd = open(realfile, O_WRONLY)) == -1)
		realfd = creat(realfile, 0666);
#endif
	if (realfd == -1)
		syserr("can't open %s for writing", realfile);
	else
	{
		/* Append the temporary log to the real log. */

		(void) lseek(fd, 0L, 0);
		(void) lseek(realfd, 0L, 2);
		(void) copyfd(fd, realfd);
		(void) close(realfd);
	}

	/* Close the temporary log. */

	(void) close(fd);
}

/*----------------------------------------------------------------------
 * Record any interesting information in the error log file.
 */

errinfo()
{
	if (!errlog)
		return;

	/* Log undelivered mail. */

	errundel();

	/* If any errors have been logged, record the failed header. */

	if (ftell(errlog) > 0)
		errheader();
}

/*----------------------------------------------------------------------
 * Log undelivered mail.
 *
 * Note that this algorithm assumes that delivery to the MBX_UNDEL mailbox
 * is always worth reporting.
 */

errundel()
{
	DEST    *d;

	if (!errlog)
		return;

	for (d = first_dest(); d; d = next_dest(d))
	{
		if (d->d_state == ST_DONE
		 && d->d_class == CL_MBOX
		 && strcmp(d->d_param, MBX_UNDEL) == 0)
		{
			CONTEXT *ct;
			char    *home;

			if ((ct = name_context(d->d_name)) != NULL)
				home = ct->ct_home;
			else
				home = "~";     /* should never happen */

			errstart();
			(void) fprintf(errlog,
			    "Undelivered mail for %s put in %s/%s\n",
			    d->d_name, home, MBX_UNDEL);
		}
	}
}

/*----------------------------------------------------------------------
 * Log the message header.
 */

errheader()
{
	FILE    *hfp;
	int     hfd;

	if (!errlog)
		return;

	/* Copy the failed message's header. */

	hfd = dup(tfd[T_HDR]);
	hfp = (hfd < 0) ? NULL : fdopen(hfd, "r");
	if (hfp == NULL)
	{
		(void) fprintf(errlog, "%s: can't open header file %s\n",
					progname, tfile[T_HDR]);
	}
	else
	{
		int     c, oc;

		(void) fprintf(errlog, "+ Header:\n");

		(void) fseek(hfp, 0L, 0);
		oc = '\n';
		while ((c = getc(hfp)) != EOF)
		{
			if (oc != '\n' || c != '\n')
			{
				if (oc == '\n')
					(void) fputs("| ", errlog);
				(void) putc(c, errlog);
			}
			oc = c;
		}

		(void) fclose(hfp);
	}
}

/*----------------------------------------------------------------------
 * Record a time stamp in the error log file.
 */

errstart()
{
	/* If we've already written a time stamp, don't do it again. */

	if (!errlog || ftell(errlog) > 0)
		return;

	/* Write a time stamp and various useful info. */

	logstart(errlog);
	(void) fprintf(errlog, "process %d", getpid());
	if (rec_parent > 0)
		(void) fprintf(errlog, ", parent %d", rec_parent);
	(void) fprintf(errlog, ": %s %s\n", progname, version);
}

/*----------------------------------------------------------------------
 * Record the end of this process's error log entry.
 */

errdone()
{
	/* If we never wrote to the error log file, do nothing. */

	if (!errlog || ftell(errlog) == 0)
		return;

	/* Write a simple closing line for the error log entry. */

	(void) fprintf(errlog, "process %d", getpid());
	if (rec_parent > 0)
		(void) fprintf(errlog, ", parent %d", rec_parent);
	(void) fprintf(errlog, ": exit\n");

	logdone(errlog);
}

/*----------------------------------------------------------------------
 * Start a log entry.
 * Various useful info goes here -- especially a timestamp.
 */

logstart(fp)
FILE    *fp;
{
	struct tm *lt;
	time_t  now;
	static char month[12][4] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};

	(void) time(&now);
	lt = localtime(&now);

	(void) fputc('\n', fp);
	if (rec_level)
		(void) fprintf(fp, "[%d]", rec_level);
	else
		(void) fputs("---", fp);
	(void) fputs("------------------------ ", fp);
	(void) fprintf(fp, "%d %s %d, %02d:%02d:%02d %s\n",
			lt->tm_mday, month[lt->tm_mon], lt->tm_year + 1900,
			lt->tm_hour, lt->tm_min, lt->tm_sec,
#ifdef USG
			tzname[lt->tm_isdst ? 1 : 0]
#else
			lt->tm_zone
#endif
			);
}

/*----------------------------------------------------------------------
 * Write a concluding marker to the given logfile.
 * This marker separates instances of Deliver at recursion level zero.
 */

logdone(fp)
FILE	*fp;
{
	if (rec_level == 0)
		(void) fputs("===========================\n\n", fp);
}
