Select Git revision
connections.c 24.74 KiB
/*
* $Id: connections.c,v 0.110 2003/08/01 09:37:19 ceder Exp $
* Copyright (C) 1991-2002 Lysator Academic Computer Association.
*
* This file is part of the LysKOM server.
*
* LysKOM 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 1, or (at your option)
* any later version.
*
* LysKOM 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 LysKOM; see the file COPYING. If not, write to
* Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN,
* or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
* MA 02139, USA.
*
* Please mail bug reports to bug-lyskom@lysator.liu.se.
*/
/*
* connections.c
*
* Denna fil inneh}ller niv}n ovanf|r isc.
*
* Created by Willf|r 31/3-90. Mostly written by ceder.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <setjmp.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include "timewrap.h"
#include <sys/socket.h>
#include <signal.h>
#include <assert.h>
#include "oop.h"
#include "unused.h"
#include "ldifftime.h"
#include "misc-types.h"
#include "s-string.h"
#include "kom-types.h"
#include "kom-memory.h"
#include "debug.h"
#include "isc-interface.h"
#include "com.h"
#include "async.h"
#include "connections.h"
#include "internal-connections.h"
#include "prot-a-parse-arg.h"
#include "log.h"
#include "lyskomd.h"
#include "services.h"
#include "isc-parse.h"
#include "prot-a.h"
#include "prot-a-parse.h"
#include "server/smalloc.h"
#include "end-of-atomic.h"
#include "send-async.h"
#include "cache.h"
#include "rfc931.h"
#include "param.h"
#include "kom-config.h"
#include "kom-errno.h"
#include "sigflags.h"
#include "server-time.h"
#include "aux-items.h"
#include "eintr.h"
#include "text-garb.h"
#include "timeval-util.h"
#include "stats.h"
oop_source_sys * kom_server_oop_src = NULL;
struct isc_mcb * kom_server_mcb = NULL;
Connection * active_connection = NULL;
/*
* This is set TRUE when the server should be closed. It is checked
* each time around the main loop. It is set if someone with enough
* privileges issues a `shutdown', or of lyskomd receives a SIGHUP.
* This not an abort: all data is saved before we exit.
*/
Bool go_and_die = FALSE;
/*
* The number of times that the session penalties has been averaged.
* Beware: this number will wrap around.
*/
static unsigned int penalty_generation = 0;
/*
* These state variables are used to find out if we are busy or not.
* When a packet arrives, data_available_callback() will set work_done
* to TRUE and is_idle to FALSE.
*/
static Bool work_done = FALSE;
static Bool is_idle = FALSE;
jmp_buf parse_env;
const Fnc_descriptor fnc_defs[]={
#include "fnc-def-init.incl"
};
const int num_fnc_defs = sizeof (fnc_defs) / sizeof (Fnc_descriptor);
unsigned long service_statistics[sizeof (fnc_defs) / sizeof (Fnc_descriptor)];
BUGDECL;
static oop_call_fd data_available_callback;
static oop_call_time check_kill_flg;
static oop_call_time check_idle_callback;
static Connection *queue_first = NULL;
static Connection *queue_last = NULL;
static void
queue_add(Connection *c)
{
assert(c->on_queue == FALSE);
c->on_queue = TRUE;
c->queue_prev = queue_last;
c->queue_next = NULL;
if (queue_first == NULL)
queue_first = c;
else
queue_last->queue_next = c;
queue_last = c;
update_stat(STAT_RUN_QUEUE, 1);
}
static void
queue_remove(Connection *c)
{
assert(c->on_queue == TRUE);
c->on_queue = FALSE;
if (c->queue_next != NULL)
c->queue_next->queue_prev = c->queue_prev;
else
queue_last = c->queue_prev;
if (c->queue_prev != NULL)
c->queue_prev->queue_next = c->queue_next;
else
queue_first = c->queue_next;
c->queue_prev = NULL;
c->queue_next = NULL;
update_stat(STAT_RUN_QUEUE, -1);
}
void
set_time(void)
{
struct timeval last_time;
static int limiter = 0;
last_time = current_time;
if (gettimeofday(¤t_time, NULL) < 0)
{
if (limiter < 50)
{
kom_log("WARNING: gettimeofday failed: %s\n", strerror(errno));
if (++limiter == 50)
kom_log("WARNING: will not log the above message again.\n");
}
}
if (timeval_less(current_time, last_time))
{
kom_log("WARNING: Time is moving in the wrong direction.\n");
/* FIXME (bug 62): Should we take more decisive action here? */
}
}
static void
logout_client(Connection *cp)
{
Connection *real_active_connection;
int ret;
if ( active_connection != NULL )
{
kom_log("BUGCHK: logout_client(%ld): connection %ld is active.\n",
cp->session_no, active_connection->session_no);
}
if ( cp->pers_no != 0 )
{
int ctr = 0;
if (active_connection != NULL)
{
kom_log("WNG: logout_client(): active_connection != NULL\n");
if (ctr < 100)
ctr++;
else
kom_log("WNG: won't log the above message more\n");
}
real_active_connection = active_connection;
active_connection = cp;
logout();
active_connection = real_active_connection;
}
else
{
#if 0
/* FIXME (bug 908): send a new async here instead. This causes the
elisp client to say that a secret (or unknown) person has
left the system. */
async_logout( 0, cp->session_no );
#endif
}
switch(cp->protocol)
{
case 0: /* Hasn't yet allocated any protocol. */
break;
case 'A':
prot_a_destruct(cp);
break;
default:
restart_kom("logout_client(): Bad protocol.\n");
}
ret = isc_destroy(kom_server_mcb, cp->isc_session);
if (ret < 0)
kom_log("logout_client(): isc_destroyed returned %d\n", ret);
cp->isc_session = NULL;
if (cp->on_queue)
queue_remove(cp);
kill_client(cp); /* Free the Connection */
update_stat(STAT_CLIENTS, -1);
}
/*
* This function is part of the shutdown tidy-up sequence.
*/
void
logout_all_clients(void)
{
Session_no sess = 0;
Connection *conn;
while ( (sess = traverse_connections (sess)) != 0)
{
conn = get_conn_by_number (sess);
if ( conn == NULL )
restart_kom("logout_all_clients(): cant get session %ld.\n",
sess);
else
logout_client (conn);
}
if ( traverse_connections (0) != 0)
restart_kom("logout_all_clients(): traverse_connections(0) == %ld.\n",
traverse_connections(0));
}
/*
* Call a function in services.c. A pointer to the result is returned.
* The pointer points to static data which is overwritten on each call.
*/
static Success
call_function(Connection *client,
union result_holder *res)
{
Success status=FAILURE; /* OK if the call was successful. */
if ( active_connection != NULL )
{
kom_log("call_function(%ld): active_connection = %ld\n",
client->session_no, active_connection->session_no);
}
if (client->function == illegal_fnc)
{
err_stat = 0;
kom_errno = KOM_NOT_IMPL;
return FAILURE;
}
active_connection = client;
service_statistics[client->function_index]++;
#include "call-switch.incl"
active_connection = NULL;
return status;
}
static void
parse_packet(Connection *client)
{
if ( client->protocol == '\0' ) /* Not known yet. */
{
client->protocol = parse_char(client);
switch(client->protocol)
{
case 'A':
prot_a_init(client);
break;
default:
client->protocol = '\0';
isc_puts("%%LysKOM unsupported protocol.\n", client->isc_session);
isc_flush(client->isc_session);
BUG(("%%%%Unsupported protocol.\n"));
longjmp(parse_env, KOM_LOGOUT);
}
}
switch(client->protocol)
{
case 'A':
prot_a_parse_packet(client);
break;
default:
restart_kom("parse_packet(): Bad protocol.\n");
break;
}
}
/*
* Free all parsed areas which are no longer needed. Re-initialize all
* parse_pos fields so that the parse will expect a new function.
*
* This function is called
* when a parse error occurs
* when a parse is complete and the function has executed.
*/
static void
free_parsed(Connection *client)
{
s_clear(&client->c_string0);
s_clear(&client->c_string1);
client->string0 = EMPTY_STRING; /* So that no one frees it. */
sfree(client->misc_info_list.misc);
client->misc_info_list.misc = 0;
client->misc_info_list.no_of_misc = 0;
s_clear(&client->aux_item.data);
s_clear(&client->dummy_aux_item.data);
sfree( client->c_local_text_no_p);
client->c_local_text_no_p = NULL;
sfree(client->read_range_list.ranges);
client->read_range_list.ranges = NULL;
client->read_range_list.length = 0;
client->parse_pos = 0;
client->fnc_parse_pos = 0;
client->array_parse_index = 0;
client->array_parse_parsed_length = 0;
client->array_parse_pos = 0;
client->struct_parse_pos = 0;
client->string_parse_pos = 0;
client->hunt_parse_pos = 0;
client->array_hunt_num = 0;
client->array_hunt_depth = 0;
sfree(client->num_list.data);
client->num_list.data = NULL;
client->num_list.length = 0;
free_aux_item_list(&client->aux_item_list);
client->info.highest_aux_no = 0;
}
/*
* Send a reply to a call.
*/
static void
reply(Connection *client,
Success status,
union result_holder *result)
{
switch(client->protocol)
{
case 'A':
prot_a_reply(client, status, result);
break;
default:
restart_kom("reply(): Bad protocol.\n");
break;
}
}
/*
* Try to parse enough data from client->unparsed to call a function.
* If more data is needed set client->more_to_parse to FALSE. Returns
* TRUE if anything was (or might have been) written to the client.
*/
static Bool
parse_unparsed(Connection *client)
{
Success status;
union result_holder result;
switch ( setjmp(parse_env) )
{
case 0 :
/* Parse message. If message is complete call function and reply. */
parse_packet(client);
update_stat(STAT_REQUESTS, 1);
status = call_function(client, &result);
update_stat(STAT_REQUESTS, -1);
reply(client, status, &result);
client->penalty += param.penalty_per_call;
free_parsed(client);
end_of_atomic();
return TRUE;
case KOM_PROTOCOL_ERR:
s_clear(&client->string0);
free_parsed(client);
isc_puts("%% LysKOM protocol error.\n", client->isc_session);
BUG(("%%%% Protocol error.\n"));
client->penalty += param.max_penalty;
s_clear(&client->unparsed);
client->first_to_parse = 0;
client->more_to_parse = FALSE;
end_of_atomic();
return TRUE;
case KOM_MSG_INCOMPLETE:
client->more_to_parse = FALSE;
return FALSE;
case KOM_LOGOUT:
add_to_kill_list(client);
client->more_to_parse = FALSE;
return TRUE;
default:
restart_kom("Bad longjmp return value.\n");
}
/*NOTREACHED*/
}
/* Return 1 if the named file exists, 0 otherwise */
static int
fexists(const char *filename)
{
struct stat buf;
int code;
code = !stat(filename, &buf);
errno = 0;
return code;
}
void
dump_statistics(void)
{
static struct timeval last_dump = {0, 0};
int i;
FILE *fp;
if ((fp = i_fopen(param.statistic_name, "a")) == NULL)
{
kom_log("dump_statistics(): can't open file %s\n",
param.statistic_name);
return;
}
if (timeval_zero(last_dump))
{
fprintf(fp, "RESTART\n");
last_dump = current_time;
}
fprintf(fp, "TIME: %s", ctime(¤t_time.tv_sec));
fprintf(fp, "SECONDS: %ld\n", timeval_diff_sec(current_time, last_dump));
fprintf(fp, "STATISTICS:");
/* The last entry corresponds to the dummy entry that is used to
skip arguments to unimplemented requests. Skip that, since it
contains no useful statistics. */
for (i = 0; i < num_fnc_defs - 1; i++)
{
fprintf(fp, " %d:%lu", fnc_defs[i].function, service_statistics[i]);
service_statistics[i]=0;
}
fprintf(fp, "\n");
i_fclose(fp);
last_dump = current_time;
}
/* List of connections to kill. */
static Session_no *kill_list = NULL;
static int kill_list_size = 0;
static int kill_pending = 0;
/* Schedule this client for termination. */
void
add_to_kill_list(Connection *conn)
{
oop_source *source;
int i;
if (conn->kill_pending)
{
for (i = 0; i < kill_list_size; i++)
if (kill_list[i] == conn->session_no)
return;
restart_kom("add_to_kill_list(): kill_pending set but not on list.\n");
}
for (i = 0; i < kill_list_size; i++)
if (kill_list[i] == conn->session_no)
restart_kom("add_to_kill_list(): on list but not kill_pending.\n");
if (kill_list == NULL)
{
if (kill_list_size != 0)
restart_kom("add_to_kill_list(): size = %d\n", kill_list_size);
kill_list_size = 1;
kill_list = smalloc(sizeof(Session_no));
}
else
{
kill_list_size++;
kill_list = srealloc(kill_list, kill_list_size * sizeof(Session_no));
}
kill_list[kill_list_size-1] = conn->session_no;
conn->kill_pending = TRUE;
if (!kill_pending)
{
source = isc_getoopsource(conn->isc_session);
source->on_time(source, OOP_TIME_NOW, check_kill_flg, NULL);
kill_pending = 1;
}
}
void
dump_connections(void)
{
Session_no s;
Connection *conn;
FILE *fp;
if ((fp = i_fopen(param.connection_status_file_tmp, "w")) == NULL)
{
kom_log("dump_connections(): can't open file %s: %s\n",
param.connection_status_file_tmp, strerror(errno));
return;
}
for (s = 0; (s = traverse_connections(s)) != 0;)
{
conn = get_conn_by_number(s);
fprintf(fp, "%d %lu %s\n", conn->isc_session->fd, conn->session_no,
conn->peer);
}
if (fflush(fp) != 0)
kom_log("dump_connections(): fflush() says an error has occured.\n");
if (ferror(fp))
kom_log("dump_connections(): ferror() says an error has occured.\n");
if (i_fclose(fp) < 0)
{
kom_log("dump_connections(): fclose failed: %s (ignored)\n",
strerror(errno));
}
errno = 0;
if (i_rename(param.connection_status_file_tmp,
param.connection_status_file) < 0)
{
kom_log("dump_connections(): can't rename %s to %s: %s\n",
param.connection_status_file_tmp,
param.connection_status_file,
strerror(errno));
}
}
/*
* check_kill_flg must NEVER be called inside an atomic call!
*/
static void *
check_kill_flg(oop_source *UNUSED(source),
struct timeval UNUSED(tv),
void *UNUSED(user))
{
Connection *conn;
Bool changed = FALSE;
kill_pending = 0;
if ( active_connection != NULL )
{
restart_kom("check_kill_flg: active_connection == %ld",
active_connection->session_no);
}
while (kill_list_size > 0)
{
--kill_list_size;
conn = get_conn_by_number (kill_list[kill_list_size]);
if (conn == NULL)
{
kom_log("check_kill_flg(): Connection %ld doesn't exist.\n",
kill_list[kill_list_size]);
}
else
{
assert(conn->kill_pending);
conn->kill_pending = FALSE;
logout_client(conn);
end_of_atomic();
changed = TRUE;
}
}
if (kill_list != NULL)
{
sfree (kill_list);
kill_list = NULL;
}
if (changed == TRUE)
dump_connections();
return OOP_CONTINUE;
}
static char *
get_host_name(union isc_address *addr)
{
char *res = NULL;
char *hostname = NULL;
struct timeval before;
struct timeval after;
double diff = -1.0;
if (param.use_dns)
{
if (gettimeofday(&before, NULL) < 0)
kom_log("gettimeofday failed: %s\n", strerror(errno));
update_stat(STAT_DNS_QUEUE, 1);
res = hostname = isc_gethostname(addr, NULL, 0);
update_stat(STAT_DNS_QUEUE, -1);
if (gettimeofday(&after, NULL) < 0)
kom_log("gettimeofday failed: %s\n", strerror(errno));
diff = (after.tv_sec - before.tv_sec
+ 1e-6 * (after.tv_usec - before.tv_usec));
}
if (res == NULL)
res = isc_getipnum(addr, NULL, 0);
if (param.use_dns && diff > param.dns_log_threshold)
{
if (hostname == NULL)
kom_log("Slow bad DNS: %s failed after %f seconds\n", res, diff);
else
kom_log("Slow DNS: got %s after %f seconds\n", res, diff);
}
return res;
}
static void
write_err_cb(struct isc_mcb *UNUSED(cb_mcb),
struct isc_scb *UNUSED(cb_session),
int saved_errno,
void *user)
{
Connection * cp = user;
if (saved_errno != ECONNRESET && saved_errno != EPIPE)
kom_log("Failed to write to client %lu from %s: %s\n",
cp->session_no, cp->peer, strerror(saved_errno));
cp->penalty += param.max_penalty;
add_to_kill_list(cp);
}
static void
stale_cb(struct isc_mcb *UNUSED(cb_mcb),
struct isc_scb *UNUSED(cb_session),
void *user)
{
Connection *cp = user;
kom_log("Client %lu from %s has stalled. Killing it.\n",
cp->session_no, cp->peer);
cp->penalty += param.max_penalty;
add_to_kill_list(cp);
}
static void
login_request(struct isc_scb *session)
{
Connection * cp;
const char *realuser;
char *hostname = NULL;
char peername[256];
char portbuf[1+2+3*sizeof(long)];
size_t hostlen;
size_t portlen;
/* Supress logins if /etc/nologin exists */
if (fexists(param.nologin_file))
{
isc_puts("%% No logins allowed.\n", session);
isc_flush(session);
isc_destroy(kom_server_mcb, session);
return;
}
hostname = get_host_name(session->raddr);
if (hostname == NULL)
kom_log("WNG: login_request(): unknown hostid.\n");
/* Get the real user name, as returned by the Ident protocol (rfc 931). */
realuser = get_real_username(session, hostname);
if (realuser == NULL && param.authentication_level == 2)
{
kom_log("Connection from %s rejected - no IDENT available.\n",
hostname);
isc_puts("%% No IDENT server reachable at your site.\n",
session);
isc_flush(session);
isc_destroy(kom_server_mcb, session);
return;
}
/* Create a Connection, and link the Connection and the
isc_session together. */
cp = new_client();
cp->isc_session = session;
session->udg = cp;
/* Start with max penalty, so that it doesn't pay to make a lot of
new connections. */
cp->penalty = param.max_penalty;
cp->penalty_generation = penalty_generation;
if (hostname == NULL)
s_crea_str(&cp->hostname, "unknown");
else
s_crea_str(&cp->hostname, hostname);
if (realuser != NULL)
s_crea_str(&cp->ident_user, realuser);
BUG(("\n[Client %lu from %s is connecting]\n", cp->session_no, hostname));
isc_getipnum(session->raddr, peername, sizeof(peername));
hostlen = strlen(peername);
if (hostlen >= sizeof(peername)-1)
{
kom_log("login_request(): truncated remote peer address %s.\n",
peername);
}
sprintf(portbuf, " %d", isc_getportnum(session->raddr));
portlen = strlen(portbuf);
cp->peer = smalloc(hostlen + portlen + 1);
strcpy(cp->peer, peername);
strcpy(cp->peer + hostlen, portbuf);
update_stat(STAT_CLIENTS, 1);
dump_connections();
isc_set_read_callback(session, data_available_callback, write_err_cb,
stale_cb, cp);
}
static void
adjust_penalty(Connection *conn)
{
while (penalty_generation - conn->penalty_generation > 0)
{
conn->penalty /= 2;
if (conn->penalty == 0)
conn->penalty_generation = penalty_generation;
else
conn->penalty_generation++;
}
}
static void
read_from_connection(Connection *conn)
{
Bool would_block = FALSE;
Bool need_flush = FALSE;
adjust_penalty(conn);
while (!would_block && !go_and_die && conn->penalty < param.max_penalty
&& !conn->kill_pending)
{
while (conn->more_to_parse
&& !go_and_die && conn->penalty < param.max_penalty
&& !conn->kill_pending)
need_flush |= parse_unparsed(conn);
if (go_and_die || conn->penalty >= param.max_penalty
|| conn->kill_pending)
break;
if (!conn->more_to_parse)
{
switch (isc_read_data(conn->isc_session,
&conn->unparsed,
&conn->first_to_parse))
{
case ISC_READ_DATA:
conn->penalty += param.penalty_per_read;
conn->more_to_parse = TRUE;
break;
case ISC_READ_ERROR:
if (errno != ECONNRESET)
kom_log("Error reading from client: %s\n",
strerror(errno));
/*FALLTHROUGH*/
case ISC_READ_LOGOUT:
add_to_kill_list(conn);
break;
case ISC_READ_WOULDBLOCK:
would_block = TRUE;
break;
case ISC_READ_NOMEM:
restart_kom("isc_read_data() reports no memory\n");
}
if (!conn->more_to_parse)
break;
}
}
if (need_flush && !kill_pending)
isc_flush(conn->isc_session);
/* Delete the parsed part of 'unparsed' */
if (s_trim_left(&conn->unparsed, conn->first_to_parse) != OK)
restart_kom("parse_unparsed: s_trim_left\n");
conn->first_to_parse = 0;
if (conn->penalty >= param.max_penalty)
{
/* isc_disable() will fail if we have received EPIPE on
this socket. In that case will soon close it, since
write_err_cb() has added it to the kill list. */
if (isc_disable(conn->isc_session) == 0)
queue_add(conn);
}
}
static void
enable_idle_check(void)
{
oop_source *source = oop_sys_source(kom_server_oop_src);
source->on_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
}
static void *
check_idle_callback(oop_source *UNUSED(source),
struct timeval UNUSED(tv),
void *UNUSED(user))
{
Connection *c;
Connection *next;
Connection *head = NULL;
Connection *tail = NULL;
set_time();
is_idle = !work_done;
work_done = FALSE;
if (is_idle && queue_first != NULL)
{
is_idle = FALSE;
++penalty_generation;
for (next = queue_first; next != NULL; )
{
c = next;
next = next->queue_next;
adjust_penalty(c);
if (c->penalty < param.low_penalty)
{
queue_remove(c);
if (head == NULL)
head = c;
else
tail->queue_next = c;
tail = c;
}
}
}
for (next = head; next != NULL; )
{
c = next;
next = next->queue_next;
c->queue_next = NULL;
if (isc_enable(c->isc_session) < 0)
restart_kom("failed to re-enable session\n");
/* The call to read_from_connection() might add the
connection to the queue. That's why we remove all entries
first, and use a private queue within this function. */
if (!go_and_die)
read_from_connection(c);
}
if (!is_idle)
enable_idle_check();
/* Check if a client issued a shutdown command. */
return go_and_die ? OOP_HALT : OOP_CONTINUE;
}
static void *
saver_callback(oop_source *source,
struct timeval UNUSED(tv),
void *user)
{
struct timeval timeout;
struct timeval *next_timer = user;
timeout = end_of_atomic();
if (setup_timer(next_timer, timeout) < 0)
kom_log("gettimeofday failed: %s\n", strerror(errno));
source->on_time(source, *next_timer, saver_callback, user);
return OOP_CONTINUE;
}
static void *
data_available_callback(oop_source *source,
int fd,
oop_event event,
void *user)
{
Connection *conn = user;
assert(event == OOP_READ);
assert(conn->isc_session->fd == fd);
assert(isc_getoopsource(conn->isc_session) == source);
assert(conn->on_queue == FALSE);
/* Something arrived, so we are busy. */
if (is_idle)
{
is_idle = FALSE;
enable_idle_check();
}
work_done = TRUE;
set_time();
read_from_connection(conn);
/* Check if the client issued a shutdown command. */
return go_and_die ? OOP_HALT : OOP_CONTINUE;
}
void *
handle_accept_event(struct isc_mcb *mcb,
struct isc_scb *UNUSED(accepting_session),
struct isc_scb *new_session)
{
assert(mcb == kom_server_mcb);
if (new_session->fd <= PROTECTED_FDS || new_session->fd >= fd_ceiling)
{
BUG(("Connection attempt rejected.\n"));
isc_puts("%% No connections left.\n", new_session);
isc_flush(new_session);
isc_destroy(mcb, new_session);
async_rejected_connection();
}
else
login_request(new_session);
return OOP_CONTINUE;
}
void
toploop(void)
{
struct timeval saver_timer;
void *exit_reason;
oop_source *source = oop_sys_source(kom_server_oop_src);
/* Start the garb right away. */
start_garb_thread(source);
/* Hack to find out when we are idle. */
source->on_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
/* Save the database even if we happen to be idle. */
saver_timer = OOP_TIME_NOW;
source->on_time(source, saver_timer, saver_callback, &saver_timer);
exit_reason = oop_sys_run(kom_server_oop_src);
if (exit_reason == OOP_ERROR)
kom_log("ERROR: unexpected error from oop_sys_run: %s\n",
strerror(errno));
else if (exit_reason == OOP_CONTINUE)
kom_log("ERROR: all oop sinks disappeared\n");
else if (exit_reason != OOP_HALT)
kom_log("ERROR: unexpected error from oop_sys_run: %s\n",
strerror(errno));
if (kill_pending)
check_kill_flg(NULL, OOP_TIME_NOW, NULL);
if (is_idle == FALSE)
source->cancel_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
stop_garb_thread(source);
source->cancel_time(source, saver_timer, saver_callback, &saver_timer);
}
Bool
server_idle(void)
{
return is_idle;
}