/* Services -- main source file. * Copyright (c) 1996-2005 Andrew Church * 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) */ const 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 while we are linked to the network */ int linked = 0; /* 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[BUFSIZE] = ""; /* 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_connect = -1; int cb_save_data = -1; int cb_save_complete = -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_set_blocking(s, 1); 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) { /* We are no longer linked */ linked = 0; if (param == DISCONN_REMOTE || param == DISCONN_CONNFAIL) { int errno_save = errno; const char *msg = (param==DISCONN_REMOTE ? "Read error from server" : "Connection to server failed"); snprintf(quitmsg, sizeof(quitmsg), "%s: %s", msg, strerror(errno_save)); if (param == DISCONN_REMOTE) { /* If we were already connected, make sure any changed data is * updated before we terminate. */ delayed_quit = 1; save_data = 1; } else { /* The connection was never made in the first place, so we * discard any changes (such as expirations) made on the * assumption that either a configuration problem or other * external problem exists. Such changes will be saved the * next time Services successfully connects to a server. */ 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); } /* We're now linked to the network */ linked = 1; /* 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); /* Let modules do their startup stuff */ call_callback(NULL, cb_connect); /* 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(); } /*************************************************************************/ /*************************************************************************/ /* Subroutine to save databases. */ static void do_save_data(void) { if (!lock_data()) { if (errno == EEXIST) { log("warning: databases are locked, not updating"); wallops(NULL, "\2Warning:\2 Databases are locked, and cannot be updated." " Remove the `%s%s%s' file to allow database updates.", *LockFilename=='/' ? "" : services_dir, *LockFilename=='/' ? "" : "/", LockFilename); } else { log_perror("warning: unable to lock databases, not updating"); wallops(NULL, "\2Warning:\2 Unable to lock databases; databases" " will not be updated."); } call_callback_1(NULL, cb_save_complete, 0); } else { if (debug) log("debug: Saving databases"); call_callback(NULL, cb_save_data); if (!unlock_data()) { log_perror("warning: unable to unlock databases"); wallops(NULL, "\2Warning:\2 Unable to unlock databases; future database" " updates may fail until the `%s%s%s' file is removed.", *LockFilename=='/' ? "" : services_dir, *LockFilename=='/' ? "" : "/", LockFilename); } call_callback_1(NULL, cb_save_complete, 1); } } /*************************************************************************/ /* 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 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_send = time(NULL); last_update = 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 (!readonly && (save_data || now-last_update >= UpdateTimeout)) { do_save_data(); save_data = 0; last_update = now; } if (delayed_quit) break; if (sock_isconn(servsock)) { if (PingFrequency && now - last_send >= PingFrequency) send_cmd(NULL, "PING :%s", ServerName); } 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) { if (!*quitmsg) strscpy(quitmsg, "Restarting", sizeof(quitmsg)); cleanup(); 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 */ cleanup(); return 0; } /*************************************************************************/