/*****************************************************************************
 *
 * File ..................: mbsed/status.c
 * Purpose ...............: Keep track of server status 
 * Last modification date : 20-Feb-1999
 *
 *****************************************************************************
 * Copyright (C) 1997-1999
 *   
 * Michiel Broek		FIDO:		2:2801/16
 * Beekmansbos 10		Internet:	mbroek@ux123.pttnwb.nl
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS 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, or (at your option) any
 * later version.
 *
 * MB BBS 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MB BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/


#include "libs.h"
#include "status.h"
#include "logger.h"


typedef struct {
	long		tot_clt;	/* Total client connects 	*/
	long		peak_clt;	/* Peak simultaneous tot_cltes	*/
	long		s_error;	/* Syntax errors from clients	*/
	long		c_error;	/* Comms errors from clients	*/
} cl_stat;


typedef struct {
	time_t		start;		/* Start date/time		*/
	time_t		laststart;	/* Last start date/time		*/
	time_t		daily;		/* Last daily update		*/
	long		startups;	/* Total starts			*/
	long		clients;	/* Connected clients		*/
	cl_stat		total;		/* Total statistics		*/
	cl_stat		today;		/* Todays statistics		*/
	unsigned	open	: 1;	/* Is BBS open			*/
	unsigned long	sequence;	/* Sequencer counter		*/
} status_r;


char		*stat_fn;		/* Statusfile name		*/
char		*stat_lck;		/* Statusfile lockfile name	*/
int		stat_fd;		/* Statusfile descriptor	*/
int		stat_lfd;		/* Statusfile lock descriptor	*/
static status_r	status;			/* Status data			*/
extern int	logdebug;		/* Debug level			*/
extern char	*zmhstart;		/* ZMH start			*/
extern char	*zmhend;		/* ZMH end			*/


/************************************************************************
 *
 *  Initialize the statusfile, create it if necesary. At this time we
 *  don't do any file locking.
 */

void status_init(char *base)
{
	int		d;
	size_t		cnt;

	stat_fn  = calloc(128, sizeof(char));
	stat_lck = calloc(128, sizeof(char));

	sprintf(stat_fn,  "%s/var/status.mbsed", base);
	sprintf(stat_lck, "%s/var/status.LCK", base);

	/*
	 * First check if this is the very first time we start the show.
	 * If so, we generate an empty status file with only the start
	 * date in it.
	 */
	stat_fd = open(stat_fn, O_RDONLY);
	if (stat_fd == -1) {
		memset((char *)&status, 0, sizeof(status_r));
		status.start = time(NULL);
		status.daily = time(NULL);
		status.sequence = (unsigned long)time(NULL);
		stat_fd = open(stat_fn, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); 
		cnt = write(stat_fd, &status, sizeof(status_r));
		if (logdebug >= 2)
			mbselog((char *)"+", (char *)"New statusfile created");
	}
	d = close(stat_fd);
	
	/*
	 * The statusfile exists. Mark the last started date and increase
	 * the startup counter. Write the results back to disk.
	 */
	stat_fd = open(stat_fn, O_RDWR);
	if (stat_fd == -1) {
		perror("status rw");
		exit(1);
	}
	cnt = read(stat_fd, &status, sizeof(status_r));
	if (cnt != sizeof(status_r)) {
		printf("Error reading status file\n");
		exit(1);
	}
	status.startups++;
	status.laststart = time(NULL);
	status.clients = 0;
	lseek(stat_fd, 0, SEEK_SET);
	cnt = write(stat_fd, &status, sizeof(status_r));
	if (cnt != sizeof(status_r)) {
		printf("Error rewrite status file\n");
		exit(1);
	}
	d = close(stat_fd);
}



/*
 * Every new day we clear the today fields.
 */
void status_daily(void)
{
	struct tm ttm, ytm;
	time_t temp;

	temp = time(NULL);
	ttm = *localtime(&temp);
	ytm = *localtime(&status.daily);

	/*
	 * If we passed to the next day, zero the today counters 
	 */
	if (ttm.tm_yday != ytm.tm_yday) {
		mbselog((char *)"+", (char *)"Last days statistics:");
		mbselog((char *)"+", (char *)"Total clients : %lu", status.today.tot_clt);
		mbselog((char *)"+", (char *)"Peak clients  : %lu", status.today.peak_clt);
		mbselog((char *)"+", (char *)"Syntax errors : %lu", status.today.s_error);
		mbselog((char *)"+", (char *)"Comms errors  : %lu", status.today.c_error);

		memset((char *)&status.today, 0, sizeof(cl_stat));
		status.daily = time(NULL);
		if (logdebug >= 3)
			mbselog((char *)"-", (char *)"Zeroed todays status counters");
	}
}



/*
 * Open the statusfile, lock the status file, and if the file is
 * locked by another process, try to wait for 15 seconds for the 
 * lock to become free.
 */
int status_open(void)
{
	int	tries = 0;

	while ((stat_lfd = creat(stat_lck, 0)) == -1 && errno == EACCES) {
		if (logdebug >= 2)
			mbselog((char *)"+", (char *)"Statusfile locked %d", tries +1);
		if (++tries >= 10) {
			mbselog((char *)"?", (char *)"$Statusfile lock timeout");
			return -1;
		}
		usleep(250000);
	}
	close(stat_lfd);

	if ((stat_fd = open(stat_fn, O_RDWR)) == -1) {
		unlink(stat_lck);
		mbselog((char *)"?", (char *)"$Error opening statusfile");
		return -1;
	}

	return 0;
}



int status_read(void)
{
	int d;

	if (status_open() == -1)
		return -1;

	d = read(stat_fd, &status, sizeof(status_r));
	if (d != sizeof(status_r)) {
		mbselog((char *)"?", (char *)"Error reading statusfile, only %d bytes", d);
		unlink(stat_lck);
		return -1;
	}

	status_daily(); 
	return 0;
}



void status_close(void)
{
	/*
	 * CLose and unlock the statusfile
	 */
	if (close(stat_fd) != 0)
		mbselog((char *)"?", (char *)"$Error closing statusfile");

	if (unlink(stat_lck) == -1)
		mbselog((char *)"?", (char *)"$Error unlock statusfile");
}



void status_write(void)
{
	int d;

	d = lseek(stat_fd, 0, SEEK_SET);
	d = write(stat_fd, &status, sizeof(status_r));
	if (d != sizeof(status_r)) {
		mbselog((char *)"?", (char *)"Error writing statusfile, only %d bytes", d);
	}
	status_close();
}




/*************************************************************************
 *
 *  Various actions on the statusfile.
 */


/*
 * Check for Zone Mail Hour, return 1 if it is.
 */
int get_zmh(void);

int get_zmh()
{
	struct	tm *l_date; 
	char	sstime[6];
	time_t	Now;

	Now = time(NULL);
	l_date = gmtime(&Now);
	sprintf(sstime, "%02d:%02d", l_date->tm_hour, l_date->tm_min);

	if ((strncmp(sstime, zmhstart, 5) >= 0) && (strncmp(sstime, zmhend, 5) < 0))
		return 1;
	else
		return 0;
}



void stat_inc_clients()
{
	if (status_read() == -1) 
		return;

	status.clients++;
	status.total.tot_clt++;	
	status.today.tot_clt++;
	if (status.clients >= status.total.peak_clt)
		status.total.peak_clt = status.clients;
	if (status.clients >= status.today.peak_clt)
		status.today.peak_clt = status.clients;	

	status_write();
}



void stat_dec_clients()
{
	if (status_read() == 0) {
		status.clients--; 
		status_write();
	} 
}



void stat_set_open(int op)
{
	if (status_read() == 0) {
		status.open = op;
		status_write();
	}
}



void stat_inc_serr()
{
	if (status_read() == 0) {
		status.total.s_error++;
		status.today.s_error++;
		status_write();
	}
}



void stat_inc_cerr()
{
	if (status_read() == 0) {
		status.total.c_error++;
		status.today.c_error++;
		status_write();
	}
}



char *stat_status()
{
	static char buf[160];

	buf[0] = '\0';
	if (status_read() == 0) {
		sprintf(buf, "100:16,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%d,%d,%lu;",
			status.start, status.laststart, status.daily,
			status.startups, status.clients, 
			status.total.tot_clt, status.total.peak_clt,
			status.total.s_error, status.total.c_error,
			status.today.tot_clt, status.today.peak_clt,
			status.today.s_error, status.today.c_error,
			status.open, get_zmh(), status.sequence);
		
		status_close();
	} else {
		sprintf(buf, "201:1,16;");
	}

	return buf;
}



/*
 * Return open status:
 * 0 = open.
 * 1 = closed.
 * 2 = Zone Mail Hour.
 */
int stat_bbs_stat()
{
	if (status_read() == 0) {
		status_close();
		if (!status.open) 
			return 1;
		if (get_zmh())
			return 2;
	}
	return 0;
}



/*
 * Get next sequence number
 */
char *getseq(void)
{
	static char	buf[80];

	buf[0] = '\0';
	if (status_read() == 0) {
		status.sequence++;
		status_write();
		sprintf(buf, "100:1,%lu;", status.sequence);
	} else
		sprintf(buf, "201:1,16;");

	return buf;
}


