/* Services -- main source file.
 * Copyright (c) 1996-2002 Andrew Church <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation.
 *
 * This program 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 this program (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "services.h"
#include "modules.h"
#include "timeout.h"


/******** Global variables! ********/

/* Command-line options: (note that configuration variables are in init.c) */
char *services_dir = SERVICES_DIR;	/* -dir=dirname */
int   debug        = 0;			/* -debug */
int   readonly     = 0;			/* -readonly */
int   nofork       = 0;			/* -nofork */
int   noexpire     = 0;			/* -noexpire */
int   noakill      = 0;			/* -noakill */
int   forceload    = 0;			/* -forceload */


/* Set to 1 if we are to quit */
int quitting = 0;

/* Set to 1 if we are to quit after saving databases, or 2 to restart */
int delayed_quit = 0;

/* Contains a message as to why services is terminating */
char *quitmsg = NULL;

/* Input buffer - global, so we can dump it if something goes wrong */
char inbuf[BUFSIZE];

/* Socket for talking to server */
Socket *servsock = NULL;

/* Should we update the databases now? */
int save_data = 0;

/* At what time were we started? */
time_t start_time;

/* Were we unable to open the log? (and the error that occurred) */
int openlog_failed, openlog_errno;

/* Module callbacks (global so init.c can set them): */
int cb_expire = -1;
int cb_save_data = -1;


/*************************************************************************/
/*************************************************************************/

/* Callbacks for uplink IRC server socket. */

/*************************************************************************/

/* Actions to perform when connection to server completes. */

void connect_callback(Socket *s, void *param_unused)
{
#ifdef CLEAN_COMPILE
    param_unused = param_unused;
#endif
    sock_setcb(s, SCB_READLINE, readfirstline_callback);
    send_server();
}

/*************************************************************************/

/* Actions to perform when connection to server is broken. */

void disconnect_callback(Socket *s, void *param)
{
    if (param == DISCONN_REMOTE || param == DISCONN_CONNFAIL) {
	int errno_save = errno;
	char *msg = (param==DISCONN_REMOTE ? "Read error from server"
					   : "Connection to server failed");
	quitmsg = malloc(BUFSIZE);
	if (quitmsg) {
	    snprintf(quitmsg, BUFSIZE, "%s: %s", msg, strerror(errno_save));
	} else {
	    quitmsg = msg;
	}
	quitting = 1;
    }
    sock_setcb(s, SCB_READLINE, NULL);
}

/*************************************************************************/

/* Actions to perform when first line is read from socket. */

void readfirstline_callback(Socket *s, void *param_unused)
{
#ifdef CLEAN_COMPILE
    param_unused = param_unused;
#endif
    sock_setcb(s, SCB_READLINE, readline_callback);

    if (!sgets2(inbuf, sizeof(inbuf), s)) {
	/* This shouldn't happen, but just in case... */
	disconn(s);
	fatal("Unable to read greeting from server socket");
    }
    if (strnicmp(inbuf, "ERROR", 5) == 0) {
	/* Close server socket first to stop wallops, since the other
	 * server doesn't want to listen to us anyway */
	disconn(s);
	fatal("Remote server returned: %s", inbuf);
    }

    /* Announce a logfile error if there was one */
    if (openlog_failed) {
	wallops(NULL, "Warning: couldn't open logfile: %s",
		strerror(openlog_errno));
	openlog_failed = 0;
    }

    /* Bring in our pseudo-clients */
    introduce_user(NULL);

    /* Process the line we read in above */
    process();
}

/*************************************************************************/

/* Actions to perform when subsequent lines are read from socket. */

void readline_callback(Socket *s, void *param_unused)
{
#ifdef CLEAN_COMPILE
    param_unused = param_unused;
#endif
    if (sgets2(inbuf, sizeof(inbuf), s))
	process();
}

/*************************************************************************/
/*************************************************************************/

/* Main routine.  (What does it look like? :-) ) */

int main(int ac, char **av, char **envp)
{
    volatile time_t last_update; /* When did we last update the databases? */
    volatile time_t last_expire; /* When did we last expire nicks/channels? */
    volatile uint32 last_check;  /* When did we last check timeouts? */


    /*** Initialization stuff. ***/

    if (init(ac, av) < 0) {
	fprintf(stderr, "Initialization failed, exiting.\n");
	return 1;
    }


    /* Set up timers. */
    last_update = time(NULL);
    last_expire = time(NULL);
    last_check  = time(NULL);

    /* The signal handler routine will drop back here with quitting != 0
     * if it gets called. */
    DO_SIGSETJMP();


    /*** Main loop. ***/

    while (!quitting) {
	time_t now = time(NULL);
	int32 now_msec = time_msec();

	if (debug >= 2)
	    log("debug: Top of main loop");

	if (sock_isconn(servsock)) {
	    if (!readonly && !noexpire
	     && (save_data || now-last_expire >= ExpireTimeout)
	    ) {
		if (debug)
		    log("debug: Running expire routines");
		call_callback(NULL, cb_expire);
		last_expire = now;
	    }
	    if (!readonly && (save_data || now-last_update >= UpdateTimeout)) {
		if (!lock_data()) {
		    if (errno == EEXIST) {
			wallops(NULL,
				"\2Warning:\2 Data directory is locked;"
				" databases will not be updated.  Remove"
				" the `%s' file to allow database updates.",
				LockFilename);
			log("warning: data directory is locked, not"
			    " updating databases");
		    } else {
			wallops(NULL,
				"\2Warning:\2 Unable to lock data directory;"
				" databases will not be updated.");
			log_perror("warning: unable to lock data directory,"
				   " not updating databases");
		    }
		} else {
		    if (debug)
			log("debug: Saving databases");
		    call_callback(NULL, cb_save_data);
		    if (!unlock_data()) {
			wallops(NULL,
				"\2Warning:\2 Unable to unlock data"
				" directory; future database updates may"
				" fail until the `%s' file is removed.",
				LockFilename);
			log_perror("warning: unable to unlock data directory");
		    }
		    if (save_data < 0)
			break;	/* out of main loop */
		}
		save_data = 0;
		last_update = now;
	    }
	    if (delayed_quit)
		break;
	    if (PingFrequency && now-last_send >= PingFrequency)
		send_cmd(NULL, "PING :%s", ServerName);
	} /* if (sock_isconn(servsock)) */

	if (now_msec - last_check >= TimeoutCheck) {
	    check_timeouts();
	    last_check = now_msec;
	}

	check_sockets();

	if (!MergeChannelModes)
	    set_cmode(NULL, NULL);  /* flush out any mode changes made */
    }


    /*** Cleanup stuff. ***/

    /* Check for restart instead of exit */
    if (delayed_quit == 2) {
	log("Restarting");
	modules_exit();
	if (!quitmsg)
	    quitmsg = "Restarting";
	send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg);
	disconn(servsock);
	close_log();
	execve(SERVICES_BIN, av, envp);
	if (!readonly) {
	    int errno_save = errno;
	    open_log();
	    errno = errno_save;
	    log_perror("Restart failed");
	    close_log();
	}
	return 1;
    }

    /* Disconnect and exit */
    if (!quitmsg)
	quitmsg = "Terminating, reason unknown";
    log("%s", quitmsg);
    modules_exit();
    if (sock_isconn(servsock))
	send_cmd(ServerName, "SQUIT %s :%s", ServerName, quitmsg);
    disconn(servsock);
    return 0;
}

/*************************************************************************/
