/* Services -- main source file.
* Copyright (c) 1996-2005 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) */
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;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1