mirror of
https://github.com/wrog/lambdamoo
synced 2026-08-13 00:26:05 -04:00
2039 lines
53 KiB
C
2039 lines
53 KiB
C
/******************************************************************************
|
|
Copyright (c) 1992, 1995, 1996 Xerox Corporation. All rights reserved.
|
|
Portions of this code were written by Stephen White, aka ghond.
|
|
Use and copying of this software and preparation of derivative works based
|
|
upon this software are permitted. Any distribution of this software or
|
|
derivative works must comply with all applicable United States export
|
|
control laws. This software is made available AS IS, and Xerox Corporation
|
|
makes no warranty about the software, its performance or its conformity to
|
|
any specification. Any person obtaining a copy of this software is requested
|
|
to send their name and post office or electronic mail address to:
|
|
Pavel Curtis
|
|
Xerox PARC
|
|
3333 Coyote Hill Rd.
|
|
Palo Alto, CA 94304
|
|
Pavel@Xerox.Com
|
|
*****************************************************************************/
|
|
|
|
#include "config.h"
|
|
#include "options.h"
|
|
|
|
#include "my-types.h" /* must be first on some systems */
|
|
/* ( Um, hello? Which systems and why? Leaving this here because I'll assume
|
|
| whoever left this comment did it for a reason, even though there is no
|
|
| way in hell this should go in front of config.h or options.h and
|
|
| I really want to have server.h at the top, but server.h does include
|
|
| rather a lot, so... Still not a happy camper. --wrog )
|
|
*/
|
|
|
|
#include "server.h"
|
|
#include "bf_register.h"
|
|
|
|
#include "my-signal.h"
|
|
#include "my-stdarg.h"
|
|
#include "my-stdio.h"
|
|
#include "my-stdlib.h"
|
|
#include "my-string.h"
|
|
#include "my-unistd.h"
|
|
#include "my-wait.h"
|
|
|
|
#include "db.h"
|
|
#include "db_io.h"
|
|
#include "disassemble.h"
|
|
#include "exceptions.h"
|
|
#include "execute.h"
|
|
#include "functions.h"
|
|
#include "list.h"
|
|
#include "log.h"
|
|
#include "network.h"
|
|
#include "parser.h"
|
|
#include "random.h"
|
|
#include "storage.h"
|
|
#include "streams.h"
|
|
#include "structures.h"
|
|
#include "tasks.h"
|
|
#include "timers.h"
|
|
#include "unparse.h"
|
|
#include "utils.h"
|
|
#include "version.h"
|
|
|
|
static pid_t parent_pid;
|
|
int in_child = 0;
|
|
|
|
static const char *shutdown_message = 0; /* shut down if non-zero */
|
|
static int in_emergency_mode = 0;
|
|
static Var checkpointed_connections = { .type = TYPE_NONE }; /* non-list */
|
|
|
|
typedef enum {
|
|
CHKPT_OFF, CHKPT_TIMER, CHKPT_SIGNAL, CHKPT_FUNC
|
|
} Checkpoint_Reason;
|
|
static Checkpoint_Reason checkpoint_requested = CHKPT_OFF;
|
|
|
|
static int checkpoint_finished = 0; /* 1 = failure, 2 = success */
|
|
|
|
typedef struct shandle {
|
|
struct shandle *next, **prev;
|
|
network_handle nhandle;
|
|
time_t connection_time;
|
|
time_t last_activity_time;
|
|
Objid player;
|
|
Objid listener;
|
|
task_queue tasks;
|
|
int disconnect_me;
|
|
int outbound, binary;
|
|
int print_messages;
|
|
} shandle;
|
|
|
|
static shandle *all_shandles = 0;
|
|
|
|
typedef struct slistener {
|
|
struct slistener *next, **prev;
|
|
network_listener nlistener;
|
|
Objid oid; /* listen(OID, DESC, PRINT_MESSAGES) */
|
|
Var desc;
|
|
int print_messages;
|
|
const char *name;
|
|
} slistener;
|
|
|
|
static slistener *all_slisteners = 0;
|
|
|
|
server_listener null_server_listener = {0};
|
|
|
|
static void
|
|
free_shandle(shandle * h)
|
|
{
|
|
*(h->prev) = h->next;
|
|
if (h->next)
|
|
h->next->prev = h->prev;
|
|
|
|
free_task_queue(h->tasks);
|
|
|
|
myfree(h, M_NETWORK);
|
|
}
|
|
|
|
static slistener *
|
|
new_slistener(Objid oid, Var desc, int print_messages, enum error *ee)
|
|
{
|
|
slistener *l = mymalloc(sizeof(slistener), M_NETWORK);
|
|
server_listener sl;
|
|
enum error e;
|
|
const char *name;
|
|
|
|
sl.ptr = l;
|
|
e = network_make_listener(sl, desc, &(l->nlistener), &(l->desc), &name);
|
|
|
|
if (ee)
|
|
*ee = e;
|
|
|
|
if (e != E_NONE) {
|
|
myfree(l, M_NETWORK);
|
|
return 0;
|
|
}
|
|
l->oid = oid;
|
|
l->print_messages = print_messages;
|
|
l->name = str_dup(name);
|
|
|
|
l->next = all_slisteners;
|
|
l->prev = &all_slisteners;
|
|
if (all_slisteners)
|
|
all_slisteners->prev = &(l->next);
|
|
all_slisteners = l;
|
|
|
|
return l;
|
|
}
|
|
|
|
static int
|
|
start_listener(slistener * l)
|
|
{
|
|
if (network_listen(l->nlistener)) {
|
|
oklog("LISTEN: #%"PRIdN" now listening on %s\n", l->oid, l->name);
|
|
return 1;
|
|
} else {
|
|
errlog("LISTEN: Can't start #%"PRIdN" listening on %s!\n", l->oid, l->name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_slistener(slistener * l)
|
|
{
|
|
network_close_listener(l->nlistener);
|
|
oklog("UNLISTEN: #%"PRIdN" no longer listening on %s\n", l->oid, l->name);
|
|
|
|
*(l->prev) = l->next;
|
|
if (l->next)
|
|
l->next->prev = l->prev;
|
|
|
|
free_var(l->desc);
|
|
free_str(l->name);
|
|
|
|
myfree(l, M_NETWORK);
|
|
}
|
|
|
|
static void
|
|
send_shutdown_message(const char *msg)
|
|
{
|
|
shandle *h;
|
|
Stream *s = new_stream(100);
|
|
char *message;
|
|
|
|
stream_printf(s, "*** Shutting down: %s ***", msg);
|
|
message = stream_contents(s);
|
|
for (h = all_shandles; h; h = h->next)
|
|
network_send_line(h->nhandle, message, 1);
|
|
free_stream(s);
|
|
}
|
|
|
|
static NORETURN_
|
|
abort_server(void)
|
|
{
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGTERM, SIG_DFL);
|
|
signal(SIGFPE, SIG_DFL);
|
|
signal(SIGHUP, SIG_DFL);
|
|
signal(SIGILL, SIG_DFL);
|
|
signal(SIGQUIT, SIG_DFL);
|
|
signal(SIGSEGV, SIG_DFL);
|
|
#ifdef SIGBUS
|
|
signal(SIGBUS, SIG_DFL);
|
|
#endif
|
|
signal(SIGUSR1, SIG_DFL);
|
|
signal(SIGUSR2, SIG_DFL);
|
|
signal(SIGCHLD, SIG_DFL);
|
|
|
|
abort();
|
|
}
|
|
|
|
NORETURN_
|
|
panic(const char *message)
|
|
{
|
|
static int in_panic = 0;
|
|
|
|
errlog("PANIC%s: %s\n", in_child ? " (in child)" : "", message);
|
|
if (in_panic) {
|
|
errlog("RECURSIVE PANIC: aborting\n");
|
|
abort_server();
|
|
}
|
|
in_panic = 1;
|
|
|
|
log_command_history();
|
|
|
|
if (in_child) { /* We're a forked checkpointer */
|
|
errlog("Child shutting down parent via USR1 signal\n");
|
|
kill(parent_pid, SIGUSR1);
|
|
_exit(1);
|
|
}
|
|
print_error_backtrace("server panic", output_to_log);
|
|
send_shutdown_message("server panic");
|
|
network_shutdown();
|
|
db_flush(FLUSH_PANIC);
|
|
|
|
abort_server();
|
|
}
|
|
|
|
enum Fork_Result
|
|
fork_server(const char *subtask_name)
|
|
{
|
|
pid_t pid;
|
|
Stream *s = new_stream(100);
|
|
|
|
stream_printf(s, "Forking %s", subtask_name);
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
log_perror(stream_contents(s));
|
|
free_stream(s);
|
|
return FORK_ERROR;
|
|
}
|
|
free_stream(s);
|
|
if (pid == 0) {
|
|
in_child = 1;
|
|
return FORK_CHILD;
|
|
} else
|
|
return FORK_PARENT;
|
|
}
|
|
|
|
static void
|
|
panic_signal(int sig)
|
|
{
|
|
char message[100];
|
|
|
|
sprintf(message, "Caught signal %d", sig);
|
|
panic(message);
|
|
}
|
|
|
|
static void
|
|
shutdown_signal(int sig UNUSED_)
|
|
{
|
|
shutdown_message = "shutdown signal received";
|
|
}
|
|
|
|
static void
|
|
checkpoint_signal(int sig)
|
|
{
|
|
checkpoint_requested = CHKPT_SIGNAL;
|
|
|
|
signal(sig, checkpoint_signal);
|
|
}
|
|
|
|
static void
|
|
call_checkpoint_notifier(int successful)
|
|
{
|
|
Var args;
|
|
|
|
args = new_list(1);
|
|
args.v.list[1].type = TYPE_INT;
|
|
args.v.list[1].v.num = successful;
|
|
run_server_task(-1, SYSTEM_OBJECT, "checkpoint_finished", args, "", 0);
|
|
}
|
|
|
|
static void
|
|
child_completed_signal(int sig)
|
|
{
|
|
int status;
|
|
|
|
/* (Void *) casts to avoid warnings on systems that mis-declare the
|
|
* argument type.
|
|
*/
|
|
#if HAVE_WAITPID
|
|
while (waitpid(-1, (void *) &status, WNOHANG) > 0);
|
|
#else
|
|
#if HAVE_WAIT3
|
|
while (wait3((void *) &status, WNOHANG, 0) >= 0);
|
|
#else
|
|
#if HAVE_WAIT2
|
|
while (wait2((void *) &status, WNOHANG) >= 0);
|
|
#else
|
|
wait((void *) &status);
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
signal(sig, child_completed_signal);
|
|
|
|
checkpoint_finished = (status == 0) + 1; /* 1 = failure, 2 = success */
|
|
}
|
|
|
|
static void
|
|
setup_signals(void)
|
|
{
|
|
signal(SIGFPE, SIG_IGN);
|
|
if (signal(SIGHUP, panic_signal) == SIG_IGN)
|
|
signal(SIGHUP, SIG_IGN);
|
|
signal(SIGILL, panic_signal);
|
|
signal(SIGQUIT, panic_signal);
|
|
signal(SIGSEGV, panic_signal);
|
|
#ifdef SIGBUS
|
|
signal(SIGBUS, panic_signal);
|
|
#endif
|
|
|
|
signal(SIGINT, shutdown_signal);
|
|
signal(SIGTERM, shutdown_signal);
|
|
signal(SIGUSR1, shutdown_signal); /* remote shutdown signal */
|
|
signal(SIGUSR2, checkpoint_signal); /* remote checkpoint signal */
|
|
|
|
signal(SIGCHLD, child_completed_signal);
|
|
}
|
|
|
|
static void
|
|
checkpoint_timer(Timer_ID id UNUSED_, Timer_Data data UNUSED_)
|
|
{
|
|
checkpoint_requested = CHKPT_TIMER;
|
|
}
|
|
|
|
static void
|
|
set_checkpoint_timer(int first_time)
|
|
{
|
|
Var v;
|
|
int interval, now = time(0);
|
|
static Timer_ID last_checkpoint_timer;
|
|
|
|
v = get_system_property("dump_interval");
|
|
if (v.type != TYPE_INT || v.v.num < 60 || now + v.v.num < now) {
|
|
free_var(v);
|
|
interval = 3600; /* Once per hour */
|
|
} else
|
|
interval = v.v.num;
|
|
|
|
if (!first_time)
|
|
cancel_timer(last_checkpoint_timer);
|
|
last_checkpoint_timer = set_timer(interval, checkpoint_timer, 0);
|
|
}
|
|
|
|
static const char *
|
|
object_name(Objid oid)
|
|
{
|
|
static Stream *s = 0;
|
|
|
|
if (!s)
|
|
s = new_stream(30);
|
|
|
|
if (valid(oid))
|
|
stream_printf(s, "%s (#%"PRIdN")", db_object_name(oid), oid);
|
|
else
|
|
stream_printf(s, "#%"PRIdN"", oid);
|
|
|
|
return reset_stream(s);
|
|
}
|
|
|
|
static void
|
|
call_notifier(Objid player, Objid handler, const char *verb_name)
|
|
{
|
|
Var args;
|
|
|
|
args = new_list(1);
|
|
args.v.list[1].type = TYPE_OBJ;
|
|
args.v.list[1].v.obj = player;
|
|
run_server_task(player, handler, verb_name, args, "", 0);
|
|
}
|
|
|
|
int
|
|
get_server_option(Objid oid, const char *name, Var * r)
|
|
{
|
|
if (((valid(oid) &&
|
|
db_find_property(oid, "server_options", r).ptr)
|
|
|| (valid(SYSTEM_OBJECT) &&
|
|
db_find_property(SYSTEM_OBJECT, "server_options", r).ptr))
|
|
&& r->type == TYPE_OBJ
|
|
&& valid(r->v.obj)
|
|
&& db_find_property(r->v.obj, name, r).ptr)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
send_message(Objid listener, network_handle nh, const char *msg_name,...)
|
|
{
|
|
va_list args;
|
|
Var msg;
|
|
const char *line;
|
|
|
|
va_start(args, msg_name);
|
|
if (get_server_option(listener, msg_name, &msg)) {
|
|
if (msg.type == TYPE_STR)
|
|
network_send_line(nh, msg.v.str, 1);
|
|
else if (msg.type == TYPE_LIST) {
|
|
int i;
|
|
|
|
for (i = 1; i <= msg.v.list[0].v.num; i++)
|
|
if (msg.v.list[i].type == TYPE_STR)
|
|
network_send_line(nh, msg.v.list[i].v.str, 1);
|
|
}
|
|
} else /* Use default message */
|
|
while ((line = va_arg(args, const char *)) != 0)
|
|
network_send_line(nh, line, 1);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
static void
|
|
main_loop(void)
|
|
{
|
|
int i;
|
|
|
|
/* First, notify DB of disconnections for all checkpointed connections */
|
|
for (i = 1;
|
|
i <= (TYPE_LIST == checkpointed_connections.type
|
|
? checkpointed_connections.v.list[0].v.num
|
|
: 0);
|
|
i++) {
|
|
|
|
Var v = checkpointed_connections.v.list[i];
|
|
call_notifier(v.v.list[1].v.obj, v.v.list[2].v.obj,
|
|
"user_disconnected");
|
|
}
|
|
free_var(checkpointed_connections);
|
|
checkpointed_connections = zero;
|
|
|
|
/* Second, run #0:server_started() */
|
|
run_server_task(-1, SYSTEM_OBJECT, "server_started", new_list(0), "", 0);
|
|
set_checkpoint_timer(1);
|
|
|
|
/* Now, we enter the main server loop */
|
|
while (shutdown_message == 0) {
|
|
/* Check how long we have until the next task will be ready to run.
|
|
* We only care about three cases (== 0, == 1, and > 1), so we can
|
|
* map a `never' result from the task subsystem into 2.
|
|
*/
|
|
int task_seconds = next_task_start();
|
|
int seconds_left = task_seconds < 0 ? 2 : task_seconds;
|
|
shandle *h, *nexth;
|
|
|
|
if (checkpoint_requested != CHKPT_OFF) {
|
|
if (checkpoint_requested == CHKPT_SIGNAL)
|
|
oklog("CHECKPOINTING due to remote request signal.\n");
|
|
checkpoint_requested = CHKPT_OFF;
|
|
run_server_task(-1, SYSTEM_OBJECT, "checkpoint_started",
|
|
new_list(0), "", 0);
|
|
network_process_io(0);
|
|
#ifdef UNFORKED_CHECKPOINTS
|
|
call_checkpoint_notifier(db_flush(FLUSH_ALL_NOW));
|
|
#else
|
|
if (!db_flush(FLUSH_ALL_NOW))
|
|
call_checkpoint_notifier(0);
|
|
#endif
|
|
set_checkpoint_timer(0);
|
|
}
|
|
#ifndef UNFORKED_CHECKPOINTS
|
|
if (checkpoint_finished) {
|
|
call_checkpoint_notifier(checkpoint_finished - 1);
|
|
checkpoint_finished = 0;
|
|
}
|
|
#endif
|
|
|
|
if (!network_process_io(seconds_left ? 1 : 0) && seconds_left > 1)
|
|
db_flush(FLUSH_ONE_SECOND);
|
|
else
|
|
db_flush(FLUSH_IF_FULL);
|
|
|
|
run_ready_tasks();
|
|
|
|
{ /* Get rid of old un-logged-in or useless connections */
|
|
int now = time(0);
|
|
|
|
for (h = all_shandles; h; h = nexth) {
|
|
Var v;
|
|
|
|
nexth = h->next;
|
|
|
|
if (!h->outbound && h->connection_time == 0
|
|
&& (get_server_option(h->listener, "connect_timeout", &v)
|
|
? (v.type == TYPE_INT && v.v.num > 0
|
|
&& now - h->last_activity_time > v.v.num)
|
|
: (now - h->last_activity_time
|
|
> DEFAULT_CONNECT_TIMEOUT))) {
|
|
call_notifier(h->player, h->listener, "user_disconnected");
|
|
oklog("TIMEOUT: #%"PRIdN" on %s\n",
|
|
h->player,
|
|
network_connection_name(h->nhandle));
|
|
if (h->print_messages)
|
|
send_message(h->listener, h->nhandle, "timeout_msg",
|
|
"*** Timed-out waiting for login. ***",
|
|
0);
|
|
network_close(h->nhandle);
|
|
free_shandle(h);
|
|
} else if (h->connection_time != 0 && !valid(h->player)) {
|
|
oklog("RECYCLED: #%"PRIdN" on %s\n",
|
|
h->player,
|
|
network_connection_name(h->nhandle));
|
|
if (h->print_messages)
|
|
send_message(h->listener, h->nhandle,
|
|
"recycle_msg", "*** Recycled ***", 0);
|
|
network_close(h->nhandle);
|
|
free_shandle(h);
|
|
} else if (h->disconnect_me) {
|
|
call_notifier(h->player, h->listener,
|
|
"user_disconnected");
|
|
oklog("DISCONNECTED: %s on %s\n",
|
|
object_name(h->player),
|
|
network_connection_name(h->nhandle));
|
|
if (h->print_messages)
|
|
send_message(h->listener, h->nhandle, "boot_msg",
|
|
"*** Disconnected ***", 0);
|
|
network_close(h->nhandle);
|
|
free_shandle(h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
oklog("SHUTDOWN: %s\n", shutdown_message);
|
|
send_shutdown_message(shutdown_message);
|
|
}
|
|
|
|
static shandle *
|
|
find_shandle(Objid player)
|
|
{
|
|
shandle *h;
|
|
|
|
for (h = all_shandles; h; h = h->next)
|
|
if (h->player == player)
|
|
return h;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *cmdline_buffer;
|
|
static int cmdline_buflen;
|
|
|
|
static void
|
|
init_cmdline(int argc, char *argv[])
|
|
{
|
|
char *p;
|
|
int i;
|
|
|
|
for (p = argv[0], i = 1;;) {
|
|
if (*p++ == '\0' && (i >= argc || p != argv[i++]))
|
|
break;
|
|
}
|
|
|
|
cmdline_buffer = argv[0];
|
|
cmdline_buflen = p - argv[0];
|
|
}
|
|
|
|
#define SERVER_CO_TABLE(DEFINE, H, VALUE, _) \
|
|
DEFINE(binary, _, TYPE_INT, num, \
|
|
H->binary, \
|
|
{ \
|
|
H->binary = is_true(VALUE); \
|
|
network_set_connection_binary(H->nhandle, H->binary); \
|
|
}) \
|
|
|
|
static int
|
|
server_set_connection_option(shandle * h, const char *option, Var value)
|
|
{
|
|
CONNECTION_OPTION_SET(SERVER_CO_TABLE, h, option, value);
|
|
}
|
|
|
|
static int
|
|
server_connection_option(shandle * h, const char *option, Var * value)
|
|
{
|
|
CONNECTION_OPTION_GET(SERVER_CO_TABLE, h, option, value);
|
|
}
|
|
|
|
static Var
|
|
server_connection_options(shandle * h, Var list)
|
|
{
|
|
CONNECTION_OPTION_LIST(SERVER_CO_TABLE, h, list);
|
|
}
|
|
|
|
#undef SERVER_CO_TABLE
|
|
|
|
static const char *abort_line = "abort";
|
|
|
|
/* Read even a very long line of input.
|
|
* Return NULL on EOF at the beginning of a line.
|
|
* Return abort_line if we see 1 or more chars
|
|
* followed by an EOF with no newline.
|
|
* (Assumes s is empty and always resets it).
|
|
*/
|
|
static const char *
|
|
read_stdin_line(Stream *s)
|
|
{
|
|
const char *line;
|
|
char buffer[1000];
|
|
size_t buflen;
|
|
int saw_newline = 0;
|
|
|
|
fflush(stdout);
|
|
do{
|
|
if (!fgets(buffer, sizeof(buffer), stdin)
|
|
|| (0 == (buflen = strlen(buffer))))
|
|
break;
|
|
if (buffer[buflen - 1] == '\n') {
|
|
buffer[--buflen] = '\0';
|
|
++saw_newline;
|
|
}
|
|
stream_add_bytes(s, buffer, buflen);
|
|
} while (buflen == sizeof(buffer) - 1);
|
|
|
|
if (!saw_newline) {
|
|
if (!stream_length(s))
|
|
return NULL;
|
|
(void)reset_stream(s);
|
|
return abort_line;
|
|
}
|
|
|
|
line = reset_stream(s);
|
|
while (*line == ' ')
|
|
line++;
|
|
|
|
return line;
|
|
}
|
|
|
|
static int
|
|
read_stdin_verbcode(Stream *s, Var *code)
|
|
{
|
|
Var str;
|
|
str.type = TYPE_STR;
|
|
for (;;) {
|
|
const char *line = read_stdin_line(s);
|
|
if (!line || line == abort_line)
|
|
return 0;
|
|
if (0 == strcmp(line, "."))
|
|
return 1;
|
|
str.v.str = str_dup(line);
|
|
*code = listappend(*code, str);
|
|
}
|
|
}
|
|
|
|
static void
|
|
emergency_notify(Objid player, const char *line)
|
|
{
|
|
printf("#%"PRIdN" <- %s\n", player, line);
|
|
}
|
|
|
|
static int
|
|
emergency_mode(void)
|
|
{
|
|
const char *line;
|
|
Var words;
|
|
int nargs;
|
|
const char *command;
|
|
Stream *s = new_stream(0);
|
|
Objid wizard = -1;
|
|
int debug = 1;
|
|
int start_ok = -1;
|
|
|
|
oklog("EMERGENCY_MODE: Entering mode...\n");
|
|
in_emergency_mode = 1;
|
|
|
|
printf("\nLambdaMOO Emergency Holographic Wizard Mode\n");
|
|
printf("-------------------------------------------\n");
|
|
printf("\"Please state the nature of the wizardly emergency...\"\n");
|
|
printf("(Type `help' for assistance.)\n\n");
|
|
|
|
while (start_ok < 0) {
|
|
/* Find/create a wizard to run commands as... */
|
|
if (!is_wizard(wizard)) {
|
|
Objid first_valid = -1;
|
|
|
|
if (wizard >= 0)
|
|
printf("** Object #%"PRIdN" is not a wizard...\n", wizard);
|
|
|
|
for (wizard = 0; wizard <= db_last_used_objid(); wizard++)
|
|
if (is_wizard(wizard))
|
|
break;
|
|
else if (valid(wizard) && first_valid < 0)
|
|
first_valid = wizard;
|
|
|
|
if (!is_wizard(wizard)) {
|
|
if (first_valid < 0) {
|
|
first_valid = db_create_object();
|
|
db_set_object_owner(first_valid, first_valid);
|
|
printf("** No objects in database; created #%"PRIdN".\n",
|
|
first_valid);
|
|
}
|
|
wizard = first_valid;
|
|
db_set_object_flag(wizard, FLAG_WIZARD);
|
|
printf("** No wizards in database; wizzed #%"PRIdN".\n", wizard);
|
|
}
|
|
printf("** Now running emergency commands as #%"PRIdN" ...\n", wizard);
|
|
}
|
|
printf("\nMOO (#%"PRIdN")%s: ", wizard, debug ? "" : "[!d]");
|
|
line = read_stdin_line(s);
|
|
|
|
if (!line)
|
|
start_ok = 0; /* EOF at start of line = "quit" */
|
|
else if (*line == ';') { /* eval command */
|
|
Var code, errors;
|
|
Program *program;
|
|
Var str;
|
|
|
|
str.type = TYPE_STR;
|
|
code = new_list(0);
|
|
|
|
if (*++line == ';')
|
|
line++;
|
|
else {
|
|
str.v.str = str_dup("return");
|
|
code = listappend(code, str);
|
|
}
|
|
|
|
while (*line == ' ')
|
|
line++;
|
|
|
|
if (*line == '\0') { /* long form */
|
|
printf("Type one or more lines of code, ending with `.' ");
|
|
printf("alone on a line.\n");
|
|
if (!read_stdin_verbcode(s, &code))
|
|
goto abort;
|
|
}
|
|
else {
|
|
str.v.str = str_dup(line);
|
|
code = listappend(code, str);
|
|
}
|
|
str.v.str = str_dup(";");
|
|
code = listappend(code, str);
|
|
|
|
program = parse_list_as_program(code, &errors);
|
|
free_var(code);
|
|
if (program) {
|
|
Var result;
|
|
|
|
switch (run_server_program_task(NOTHING, "emergency_mode",
|
|
new_list(0), NOTHING,
|
|
"emergency_mode", program,
|
|
wizard, debug, wizard, "",
|
|
&result)) {
|
|
case OUTCOME_DONE:
|
|
unparse_value(s, result);
|
|
printf("=> %s\n", reset_stream(s));
|
|
free_var(result);
|
|
break;
|
|
case OUTCOME_ABORTED:
|
|
printf("=> *Aborted*\n");
|
|
break;
|
|
case OUTCOME_BLOCKED:
|
|
printf("=> *Suspended*\n");
|
|
break;
|
|
}
|
|
free_program(program);
|
|
} else {
|
|
int i;
|
|
|
|
printf("** %"PRIdN" errors during parsing:\n",
|
|
errors.v.list[0].v.num);
|
|
for (i = 1; i <= errors.v.list[0].v.num; i++)
|
|
printf(" %s\n", errors.v.list[i].v.str);
|
|
}
|
|
free_var(errors);
|
|
} else {
|
|
words = parse_into_wordlist(line);
|
|
nargs = words.v.list[0].v.num - 1;
|
|
if (nargs < 0)
|
|
continue;
|
|
command = words.v.list[1].v.str;
|
|
|
|
if ((!mystrcasecmp(command, "program")
|
|
|| !mystrcasecmp(command, ".program"))
|
|
&& nargs == 1) {
|
|
const char *verbref = words.v.list[2].v.str;
|
|
db_verb_handle h;
|
|
const char *message, *vname;
|
|
|
|
h = find_verb_for_programming(wizard, verbref,
|
|
&message, &vname);
|
|
printf("%s\n", message);
|
|
if (h.ptr) {
|
|
Var code, errors;
|
|
Program *program;
|
|
|
|
code = new_list(0);
|
|
if (!read_stdin_verbcode(s, &code))
|
|
goto abort;
|
|
|
|
program = parse_list_as_program(code, &errors);
|
|
if (program) {
|
|
db_set_verb_program(h, program);
|
|
printf("Verb programmed.\n");
|
|
} else {
|
|
int i;
|
|
|
|
printf("** %"PRIdN" errors during parsing:\n",
|
|
errors.v.list[0].v.num);
|
|
for (i = 1; i <= errors.v.list[0].v.num; i++)
|
|
printf(" %s\n", errors.v.list[i].v.str);
|
|
printf("Verb not programmed.\n");
|
|
}
|
|
|
|
free_var(code);
|
|
free_var(errors);
|
|
}
|
|
} else if (!mystrcasecmp(command, "list") && nargs == 1) {
|
|
const char *verbref = words.v.list[2].v.str;
|
|
db_verb_handle h;
|
|
const char *message, *vname;
|
|
|
|
h = find_verb_for_programming(wizard, verbref,
|
|
&message, &vname);
|
|
if (h.ptr)
|
|
unparse_to_file(stdout, db_verb_program(h), 0, 1,
|
|
MAIN_VECTOR);
|
|
else
|
|
printf("%s\n", message);
|
|
} else if (!mystrcasecmp(command, "disassemble") && nargs == 1) {
|
|
const char *verbref = words.v.list[2].v.str;
|
|
db_verb_handle h;
|
|
const char *message, *vname;
|
|
|
|
h = find_verb_for_programming(wizard, verbref,
|
|
&message, &vname);
|
|
if (h.ptr)
|
|
disassemble_to_file(stdout, db_verb_program(h));
|
|
else
|
|
printf("%s\n", message);
|
|
} else if (!mystrcasecmp(command, "abort") && nargs == 0) {
|
|
goto abort;
|
|
} else if (!mystrcasecmp(command, "quit") && nargs == 0) {
|
|
start_ok = 0;
|
|
} else if (!mystrcasecmp(command, "continue") && nargs == 0) {
|
|
start_ok = 1;
|
|
} else if (!mystrcasecmp(command, "debug") && nargs == 0) {
|
|
debug = !debug;
|
|
} else if (!mystrcasecmp(command, "wizard") && nargs == 1
|
|
&& words.v.list[2].v.str[0] == '#') {
|
|
const char *w = words.v.list[2].v.str+1;
|
|
char *w2 = (char *)w;
|
|
intmax_t wix = strtoimax(w, &w2, 10);
|
|
if (w == w2)
|
|
printf("** Object number expected after 'wizard'\n");
|
|
else {
|
|
wizard = wix;
|
|
printf("** Attempting to switch to wizard #%"PRIdN"...\n",
|
|
wizard);
|
|
}
|
|
} else {
|
|
if (mystrcasecmp(command, "help")
|
|
&& mystrcasecmp(command, "?"))
|
|
printf("** Unknown or malformed command.\n");
|
|
|
|
printf(";EXPR "
|
|
"Evaluate MOO expression, print result.\n");
|
|
printf(";;CODE "
|
|
"Execute whole MOO verb, print result.\n");
|
|
printf(" (For above, omitting EXPR or CODE lets you "
|
|
"enter several lines\n");
|
|
printf(" of input at once; type a period alone on a "
|
|
"line to finish.)\n");
|
|
printf("program OBJ:VERB "
|
|
"Set the MOO code of an existing verb.\n");
|
|
printf("list OBJ:VERB "
|
|
"List the MOO code of an existing verb.\n");
|
|
printf("disassemble OBJ:VERB "
|
|
"List the internal form of an existing verb.\n");
|
|
printf("debug "
|
|
"Toggle evaluation with(out) `d' bit.\n");
|
|
printf("wizard #XX "
|
|
"Execute future commands as wizard #XX.\n");
|
|
printf("continue "
|
|
"End emergency mode, continue start-up.\n");
|
|
printf("quit "
|
|
"Exit server normally, saving database.\n");
|
|
printf("abort "
|
|
"Exit server *without* saving database.\n");
|
|
printf("help, ? "
|
|
"Print this text.\n\n");
|
|
|
|
printf("NOTE: *NO* forked or suspended tasks will run "
|
|
"until you exit this mode.\n\n");
|
|
printf("\"Please remember to turn me off when you go...\"\n");
|
|
}
|
|
|
|
free_var(words);
|
|
}
|
|
}
|
|
|
|
printf("Bye. (%s)\n\n", start_ok ? "continuing" : "saving database");
|
|
#if NETWORK_PROTOCOL != NP_SINGLE
|
|
fclose(stdout);
|
|
#endif
|
|
|
|
free_stream(s);
|
|
in_emergency_mode = 0;
|
|
oklog("EMERGENCY_MODE: Leaving mode; %s continue...\n",
|
|
start_ok ? "will" : "won't");
|
|
return start_ok;
|
|
abort:
|
|
printf("Bye. (%s)\n\n", "NOT saving database");
|
|
exit(1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Exported interface
|
|
*/
|
|
|
|
void
|
|
set_server_cmdline(const char *line)
|
|
{
|
|
/* This technique works for all UNIX systems I've seen on which this is
|
|
* possible to do at all, and it's safe on all systems. The only systems
|
|
* I know of where this doesn't work run System V Release 4, on which the
|
|
* kernel keeps its own copy of the original cmdline for printing by the
|
|
* `ps' command; on these systems, it would appear that there does not
|
|
* exist a way to get around this at all. Thus, this works everywhere I
|
|
* know of where it's possible to do the job at all...
|
|
*/
|
|
char *p = cmdline_buffer, *e = p + cmdline_buflen - 1;
|
|
|
|
while (*line && p < e)
|
|
*p++ = *line++;
|
|
while (p < e)
|
|
*p++ = ' '; /* Pad with blanks, not nulls; on SunOS and
|
|
* maybe other systems, nulls would confuse
|
|
* `ps'. (*sigh*)
|
|
*/
|
|
*e = '\0';
|
|
}
|
|
|
|
int
|
|
server_flag_option(const char *name, int defallt)
|
|
{
|
|
Var v;
|
|
|
|
if (get_server_option(SYSTEM_OBJECT, name, &v))
|
|
return is_true(v);
|
|
else
|
|
return defallt;
|
|
}
|
|
|
|
Num
|
|
server_int_option(const char *name, Num defallt)
|
|
{
|
|
Var v;
|
|
|
|
if (get_server_option(SYSTEM_OBJECT, name, &v))
|
|
return (v.type == TYPE_INT ? v.v.num : defallt);
|
|
else
|
|
return defallt;
|
|
}
|
|
|
|
const char *
|
|
server_string_option(const char *name, const char *defallt)
|
|
{
|
|
Var v;
|
|
|
|
if (get_server_option(SYSTEM_OBJECT, name, &v))
|
|
return (v.type == TYPE_STR ? v.v.str : 0);
|
|
else
|
|
return defallt;
|
|
}
|
|
|
|
Num
|
|
server_listener_int_option(server_listener sl,
|
|
const char *name, Num defallt)
|
|
{
|
|
slistener *l = sl.ptr;
|
|
Var v;
|
|
|
|
if (get_server_option((l ? l->oid : SYSTEM_OBJECT), name, &v)
|
|
&& v.type == TYPE_INT)
|
|
return v.v.num;
|
|
else
|
|
return defallt;
|
|
}
|
|
|
|
static Objid next_unconnected_player = NOTHING - 1;
|
|
|
|
server_handle
|
|
server_new_connection(server_listener sl, network_handle nh, int outbound)
|
|
{
|
|
slistener *l = sl.ptr;
|
|
shandle *h = (shandle *) mymalloc(sizeof(shandle), M_NETWORK);
|
|
server_handle result;
|
|
|
|
h->next = all_shandles;
|
|
h->prev = &all_shandles;
|
|
if (all_shandles)
|
|
all_shandles->prev = &(h->next);
|
|
all_shandles = h;
|
|
|
|
h->nhandle = nh;
|
|
h->connection_time = 0;
|
|
h->last_activity_time = time(0);
|
|
h->player = next_unconnected_player--;
|
|
h->listener = l ? l->oid : SYSTEM_OBJECT;
|
|
h->tasks = new_task_queue(h->player, h->listener);
|
|
h->disconnect_me = 0;
|
|
h->outbound = outbound;
|
|
h->binary = 0;
|
|
h->print_messages = l ? l->print_messages : !outbound;
|
|
|
|
if (l || !outbound) {
|
|
new_input_task(h->tasks, "", 0);
|
|
/*
|
|
* Suspend input at the network level until the above input task
|
|
* is processed. At the point when it is dequeued, tasks.c will
|
|
* notice that the queued input size is below the low water mark
|
|
* and resume input.
|
|
*/
|
|
task_suspend_input(h->tasks);
|
|
}
|
|
|
|
oklog("%s: #%"PRIdN" on %s\n",
|
|
outbound ? "CONNECT" : "ACCEPT",
|
|
h->player, network_connection_name(nh));
|
|
|
|
result.ptr = h;
|
|
return result;
|
|
}
|
|
|
|
void
|
|
server_refuse_connection(server_listener sl, network_handle nh)
|
|
{
|
|
slistener *l = sl.ptr;
|
|
|
|
if (l->print_messages)
|
|
send_message(l->oid, nh, "server_full_msg",
|
|
"*** Sorry, but the server cannot accept any more"
|
|
" connections right now.",
|
|
"*** Please try again later.",
|
|
0);
|
|
errlog("SERVER FULL: refusing connection on %s from %s\n",
|
|
l->name, network_connection_name(nh));
|
|
}
|
|
|
|
void
|
|
server_receive_line(server_handle sh, const char *line)
|
|
{
|
|
shandle *h = (shandle *) sh.ptr;
|
|
|
|
h->last_activity_time = time(0);
|
|
new_input_task(h->tasks, line, h->binary);
|
|
}
|
|
|
|
void
|
|
server_close(server_handle sh)
|
|
{
|
|
shandle *h = (shandle *) sh.ptr;
|
|
|
|
oklog("CLIENT DISCONNECTED: %s on %s\n",
|
|
object_name(h->player),
|
|
network_connection_name(h->nhandle));
|
|
h->disconnect_me = 1;
|
|
call_notifier(h->player, h->listener, "user_client_disconnected");
|
|
free_shandle(h);
|
|
}
|
|
|
|
void
|
|
server_suspend_input(Objid connection)
|
|
{
|
|
shandle *h = find_shandle(connection);
|
|
|
|
network_suspend_input(h->nhandle);
|
|
}
|
|
|
|
void
|
|
server_resume_input(Objid connection)
|
|
{
|
|
shandle *h = find_shandle(connection);
|
|
|
|
network_resume_input(h->nhandle);
|
|
}
|
|
|
|
void
|
|
player_connected(Objid old_id, Objid new_id, int is_newly_created)
|
|
{
|
|
shandle *existing_h = find_shandle(new_id);
|
|
shandle *new_h = find_shandle(old_id);
|
|
|
|
if (!new_h)
|
|
panic("Non-existent shandle connected");
|
|
|
|
new_h->player = new_id;
|
|
new_h->connection_time = time(0);
|
|
|
|
if (existing_h) {
|
|
/* we now have two shandles with the same player value while
|
|
* find_shandle assumes there can only be one. This needs to
|
|
* be remedied before any call_notifier() call; luckily, the
|
|
* latter only needs listener value.
|
|
*/
|
|
Objid existing_listener = existing_h->listener;
|
|
/* network_connection_name is allowed to reuse the same string
|
|
* storage, so we have to copy one of them.
|
|
*/
|
|
char *name1 = str_dup(network_connection_name(existing_h->nhandle));
|
|
|
|
oklog("REDIRECTED: %s, was %s, now %s\n",
|
|
object_name(new_id),
|
|
name1,
|
|
network_connection_name(new_h->nhandle));
|
|
free_str(name1);
|
|
if (existing_h->print_messages)
|
|
send_message(existing_listener, existing_h->nhandle,
|
|
"redirect_from_msg",
|
|
"*** Redirecting connection to new port ***", 0);
|
|
if (new_h->print_messages)
|
|
send_message(new_h->listener, new_h->nhandle, "redirect_to_msg",
|
|
"*** Redirecting old connection to this port ***", 0);
|
|
network_close(existing_h->nhandle);
|
|
free_shandle(existing_h);
|
|
if (existing_listener == new_h->listener)
|
|
call_notifier(new_id, new_h->listener, "user_reconnected");
|
|
else {
|
|
new_h->disconnect_me = 1;
|
|
call_notifier(new_id, existing_listener,
|
|
"user_client_disconnected");
|
|
new_h->disconnect_me = 0;
|
|
call_notifier(new_id, new_h->listener, "user_connected");
|
|
}
|
|
} else {
|
|
oklog("%s: %s on %s\n",
|
|
is_newly_created ? "CREATED" : "CONNECTED",
|
|
object_name(new_h->player),
|
|
network_connection_name(new_h->nhandle));
|
|
if (new_h->print_messages) {
|
|
if (is_newly_created)
|
|
send_message(new_h->listener, new_h->nhandle, "create_msg",
|
|
"*** Created ***", 0);
|
|
else
|
|
send_message(new_h->listener, new_h->nhandle, "connect_msg",
|
|
"*** Connected ***", 0);
|
|
}
|
|
call_notifier(new_id, new_h->listener,
|
|
is_newly_created ? "user_created" : "user_connected");
|
|
}
|
|
}
|
|
|
|
void
|
|
notify(Objid player, const char *message)
|
|
{
|
|
shandle *h = find_shandle(player);
|
|
|
|
if (h && !h->disconnect_me)
|
|
network_send_line(h->nhandle, message, 1);
|
|
else if (in_emergency_mode)
|
|
emergency_notify(player, message);
|
|
}
|
|
|
|
void
|
|
boot_player(Objid player)
|
|
{
|
|
shandle *h = find_shandle(player);
|
|
|
|
if (h)
|
|
h->disconnect_me = 1;
|
|
|
|
/* player is extremely unlikely to be on both lists, but we
|
|
* cover this case anyway, see write_active_connections()
|
|
*/
|
|
if (in_emergency_mode && (TYPE_LIST == checkpointed_connections.type)) {
|
|
int i = checkpointed_connections.v.list[0].v.num;
|
|
for (; i > 0; --i) {
|
|
if (player == checkpointed_connections.v.list[i].v.list[1].v.obj) {
|
|
checkpointed_connections
|
|
= listdelete(checkpointed_connections, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
write_active_connections(void)
|
|
{
|
|
/* As things currently stand, it is impossible for there to both
|
|
* be checkpointed and active connections at the same time, since
|
|
* the first call to network_process_io() is after the checkpointed
|
|
* connections have been cleared out, but that might conceivably
|
|
* change at some point. In any case, checkpointed connections
|
|
* should be preserved since we may be coming here straight
|
|
* from emergency wizard mode. --wrog
|
|
*/
|
|
int have_ckpted = (TYPE_LIST == checkpointed_connections.type);
|
|
int count = have_ckpted
|
|
? checkpointed_connections.v.list[0].v.num
|
|
: 0;
|
|
|
|
shandle *h;
|
|
|
|
for (h = all_shandles; h; h = h->next)
|
|
count++;
|
|
|
|
dbio_printf("%d active connections with listeners\n", count);
|
|
|
|
if (have_ckpted) {
|
|
int i;
|
|
for (i = 1; i <= count; i++) {
|
|
Var v = checkpointed_connections.v.list[i];
|
|
dbio_printf("%"PRIdN" %"PRIdN"\n", v.v.list[1].v.obj, v.v.list[2].v.obj);
|
|
}
|
|
}
|
|
|
|
for (h = all_shandles; h; h = h->next)
|
|
dbio_printf("%"PRIdN" %"PRIdN"\n", h->player, h->listener);
|
|
}
|
|
|
|
int
|
|
read_active_connections(void)
|
|
{
|
|
int count;
|
|
int ascn = dbio_scxnf("\v%d active connections\v with listeners", &count);
|
|
|
|
if (!ascn) {
|
|
errlog("READ_ACTIVE_CONNECTIONS: Bad active connections count.\n");
|
|
return 0;
|
|
}
|
|
if (ascn == 1) {
|
|
/* older database format did not record connections */
|
|
checkpointed_connections = zero;
|
|
return 1;
|
|
}
|
|
int have_listeners = (ascn > 2);
|
|
checkpointed_connections = new_list(count);
|
|
|
|
int i;
|
|
for (i = 1; i <= count; i++) {
|
|
Objid who, listener;
|
|
Var v;
|
|
|
|
if (have_listeners) {
|
|
if (!dbio_scxnf("%"SCNdN" %"SCNdN, &who, &listener)) {
|
|
errlog("READ_ACTIVE_CONNECTIONS: Bad conn/listener pair.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
else if (!dbio_read_num(&who))
|
|
return 0;
|
|
else
|
|
listener = SYSTEM_OBJECT;
|
|
|
|
checkpointed_connections.v.list[i] = v = new_list(2);
|
|
v.v.list[1].type = v.v.list[2].type = TYPE_OBJ;
|
|
v.v.list[1].v.obj = who;
|
|
v.v.list[2].v.obj = listener;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifndef UNIT_TESTS
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *this_program = str_dup(argv[0]);
|
|
const char *log_file = 0;
|
|
int emergency = 0;
|
|
Var desc;
|
|
slistener *l;
|
|
|
|
init_cmdline(argc, argv);
|
|
|
|
argc--;
|
|
argv++;
|
|
while (argc > 0 && argv[0][0] == '-') {
|
|
/* Deal with any command-line options */
|
|
switch (argv[0][1]) {
|
|
case 'e': /* Emergency wizard mode */
|
|
emergency = 1;
|
|
break;
|
|
case 'l': /* Specified log file */
|
|
if (argc > 1) {
|
|
log_file = argv[1];
|
|
argc--;
|
|
argv++;
|
|
} else
|
|
argc = 0; /* Provoke usage message below */
|
|
break;
|
|
default:
|
|
argc = 0; /* Provoke usage message below */
|
|
}
|
|
argc--;
|
|
argv++;
|
|
}
|
|
|
|
if (log_file) {
|
|
FILE *f = fopen(log_file, "a");
|
|
|
|
if (f)
|
|
set_log_file(f);
|
|
else {
|
|
perror("Error opening specified log file");
|
|
exit(1);
|
|
}
|
|
} else
|
|
set_log_file(stderr);
|
|
|
|
if (!db_initialize(&argc, &argv)
|
|
|| !network_initialize(argc, argv, &desc)) {
|
|
fprintf(stderr, "Usage: %s [-e] [-l log-file] %s %s\n",
|
|
this_program, db_usage_string(), network_usage_string());
|
|
exit(1);
|
|
}
|
|
#if NETWORK_PROTOCOL != NP_SINGLE
|
|
if (!emergency)
|
|
fclose(stdout);
|
|
#endif
|
|
if (log_file)
|
|
fclose(stderr);
|
|
|
|
oklog("STARTING: Version %s of the LambdaMOO server\n", server_version);
|
|
oklog(" (Using %s protocol)\n", network_protocol_name());
|
|
oklog(" (Task timeouts measured in %s seconds.)\n",
|
|
virtual_timer_available()? "server CPU" : "wall-clock");
|
|
|
|
do_all_registrations();
|
|
|
|
l = new_slistener(SYSTEM_OBJECT, desc, 1, 0);
|
|
if (!l) {
|
|
errlog("Can't create initial connection point!\n");
|
|
exit(1);
|
|
}
|
|
free_var(desc);
|
|
|
|
if (!db_load())
|
|
exit(1);
|
|
|
|
parent_pid = getpid();
|
|
setup_signals();
|
|
reset_command_history();
|
|
|
|
if (!emergency || emergency_mode()) {
|
|
if (!start_listener(l))
|
|
exit(1);
|
|
|
|
main_loop();
|
|
network_shutdown();
|
|
}
|
|
db_shutdown();
|
|
free_str(this_program);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* !UNIT_TESTS */
|
|
|
|
/**** built in functions ****/
|
|
|
|
static package
|
|
bf_server_version(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{
|
|
Var r;
|
|
if (arglist.v.list[0].v.num > 0) {
|
|
r = server_version_full(arglist.v.list[1]);
|
|
}
|
|
else {
|
|
r.type = TYPE_STR;
|
|
r.v.str = str_dup(server_version);
|
|
}
|
|
free_var(arglist);
|
|
if (r.type == TYPE_ERR)
|
|
return make_error_pack(r.v.err);
|
|
else
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_renumber(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{
|
|
Var r;
|
|
Objid o = arglist.v.list[1].v.obj;
|
|
free_var(arglist);
|
|
|
|
if (!valid(o))
|
|
return make_error_pack(E_INVARG);
|
|
else if (!is_wizard(progr))
|
|
return make_error_pack(E_PERM);
|
|
|
|
r.type = TYPE_OBJ;
|
|
r.v.obj = db_renumber_object(o);
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_reset_max_object(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{
|
|
free_var(arglist);
|
|
|
|
if (!is_wizard(progr))
|
|
return make_error_pack(E_PERM);
|
|
|
|
db_reset_last_used_objid();
|
|
return no_var_pack();
|
|
}
|
|
|
|
static package
|
|
bf_memory_usage(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{
|
|
Var r;
|
|
r = memory_usage();
|
|
free_var(arglist);
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_shutdown(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{
|
|
int nargs = arglist.v.list[0].v.num;
|
|
const char *msg = (nargs >= 1 ? arglist.v.list[1].v.str : 0);
|
|
|
|
if (!is_wizard(progr)) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_PERM);
|
|
}
|
|
|
|
Stream *s = new_stream(0);
|
|
stream_printf(s, "shutdown() called by %s", object_name(progr));
|
|
if (msg)
|
|
stream_printf(s, ": %s", msg);
|
|
shutdown_message = str_dup_then_free_stream(s);
|
|
|
|
free_var(arglist);
|
|
return no_var_pack();
|
|
}
|
|
|
|
static package
|
|
bf_dump_database(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{
|
|
free_var(arglist);
|
|
if (!is_wizard(progr))
|
|
return make_error_pack(E_PERM);
|
|
|
|
checkpoint_requested = CHKPT_FUNC;
|
|
return no_var_pack();
|
|
}
|
|
|
|
static package
|
|
bf_db_disk_size(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{
|
|
Var v;
|
|
|
|
free_var(arglist);
|
|
v.type = TYPE_INT;
|
|
if ((v.v.num = db_disk_size()) < 0)
|
|
return make_raise_pack(E_QUOTA, "No database file(s) available", zero);
|
|
else
|
|
return make_var_pack(v);
|
|
}
|
|
|
|
#ifdef OUTBOUND_NETWORK
|
|
static slistener *
|
|
find_slistener_by_oid(Objid obj)
|
|
{
|
|
slistener *l;
|
|
|
|
for (l = all_slisteners; l; l = l->next)
|
|
if (l->oid == obj)
|
|
return l;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* OUTBOUND_NETWORK */
|
|
|
|
static package
|
|
bf_open_network_connection(
|
|
Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr
|
|
#ifndef OUTBOUND_NETWORK
|
|
UNUSED_
|
|
#endif
|
|
)
|
|
{
|
|
#ifdef OUTBOUND_NETWORK
|
|
|
|
Var r;
|
|
enum error e;
|
|
server_listener sl;
|
|
slistener l;
|
|
|
|
if (!is_wizard(progr)) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_PERM);
|
|
}
|
|
|
|
if (arglist.v.list[0].v.num == 3) {
|
|
Objid oid;
|
|
|
|
if (arglist.v.list[3].type != TYPE_OBJ) {
|
|
return make_error_pack(E_TYPE);
|
|
}
|
|
oid = arglist.v.list[3].v.obj;
|
|
arglist = listdelete(arglist, 3);
|
|
|
|
sl.ptr = find_slistener_by_oid(oid);
|
|
if (!sl.ptr) {
|
|
/* Create a temporary */
|
|
l.print_messages = 0;
|
|
l.name = "open_network_connection";
|
|
l.desc = zero;
|
|
l.oid = oid;
|
|
sl.ptr = &l;
|
|
}
|
|
} else {
|
|
sl.ptr = NULL;
|
|
}
|
|
|
|
e = network_open_connection(arglist, sl);
|
|
free_var(arglist);
|
|
if (e == E_NONE) {
|
|
/* The connection was successfully opened, implying that
|
|
* server_new_connection was called, implying and a new negative
|
|
* player number was allocated for the connection. Thus, the old
|
|
* value of next_unconnected_player is the number of our connection.
|
|
*/
|
|
r.type = TYPE_OBJ;
|
|
r.v.obj = next_unconnected_player + 1;
|
|
} else {
|
|
r.type = TYPE_ERR;
|
|
r.v.err = e;
|
|
}
|
|
if (r.type == TYPE_ERR)
|
|
return make_error_pack(r.v.err);
|
|
else
|
|
return make_var_pack(r);
|
|
|
|
#else /* !OUTBOUND_NETWORK */
|
|
|
|
/* This function is disabled in this server. */
|
|
|
|
free_var(arglist);
|
|
return make_error_pack(E_PERM);
|
|
|
|
#endif
|
|
}
|
|
|
|
static package
|
|
bf_connected_players(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{
|
|
shandle *h;
|
|
int nargs = arglist.v.list[0].v.num;
|
|
int show_all = (nargs >= 1 && is_true(arglist.v.list[1]));
|
|
int count = 0;
|
|
Var result;
|
|
/* all checkpointed connections are deemed disconnected immediately
|
|
after emergency mode so that disfuncs do not see each other,
|
|
so emergency mode is the only time we see them here.
|
|
*/
|
|
int have_ckpted = (in_emergency_mode &&
|
|
(TYPE_LIST == checkpointed_connections.type));
|
|
int i;
|
|
|
|
free_var(arglist);
|
|
if (have_ckpted) {
|
|
count = checkpointed_connections.v.list[0].v.num;
|
|
if (!show_all)
|
|
for (i = count; i >= 1; --i)
|
|
/* best we can do; c_c does not record who was 'connected' */
|
|
if (checkpointed_connections.v.list[i].v.list[1].v.obj < 0)
|
|
--count;
|
|
}
|
|
for (h = all_shandles; h; h = h->next)
|
|
if ((show_all || h->connection_time != 0) && !h->disconnect_me)
|
|
++count;
|
|
|
|
result = new_list(count);
|
|
count = 0;
|
|
|
|
if (have_ckpted)
|
|
for (i = 1; i <= checkpointed_connections.v.list[0].v.num; ++i) {
|
|
Var li1 = checkpointed_connections.v.list[i].v.list[1];
|
|
if (show_all || !(li1.v.obj < 0))
|
|
result.v.list[++count] = var_ref(li1); /* force of habit */
|
|
}
|
|
|
|
for (h = all_shandles; h; h = h->next) {
|
|
if ((show_all || h->connection_time != 0) && !h->disconnect_me) {
|
|
++count;
|
|
result.v.list[count].type = TYPE_OBJ;
|
|
result.v.list[count].v.obj = h->player;
|
|
}
|
|
}
|
|
|
|
return make_var_pack(result);
|
|
}
|
|
|
|
static package
|
|
bf_connected_seconds(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{ /* (player) */
|
|
Var r;
|
|
shandle *h = find_shandle(arglist.v.list[1].v.obj);
|
|
|
|
r.type = TYPE_INT;
|
|
if (h && h->connection_time != 0 && !h->disconnect_me)
|
|
r.v.num = time(0) - h->connection_time;
|
|
else
|
|
r.v.num = -1;
|
|
free_var(arglist);
|
|
if (r.v.num < 0)
|
|
return make_error_pack(E_INVARG);
|
|
else
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_idle_seconds(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{ /* (player) */
|
|
Var r;
|
|
shandle *h = find_shandle(arglist.v.list[1].v.obj);
|
|
|
|
r.type = TYPE_INT;
|
|
if (h && !h->disconnect_me)
|
|
r.v.num = time(0) - h->last_activity_time;
|
|
else
|
|
r.v.num = -1;
|
|
free_var(arglist);
|
|
if (r.v.num < 0)
|
|
return make_error_pack(E_INVARG);
|
|
else
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_connection_name(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (player) */
|
|
Objid who = arglist.v.list[1].v.obj;
|
|
shandle *h = find_shandle(who);
|
|
const char *conn_name;
|
|
Var r;
|
|
|
|
if (h && !h->disconnect_me)
|
|
conn_name = network_connection_name(h->nhandle);
|
|
else
|
|
conn_name = 0;
|
|
|
|
free_var(arglist);
|
|
if (!is_wizard(progr) && progr != who)
|
|
return make_error_pack(E_PERM);
|
|
else if (!conn_name)
|
|
return make_error_pack(E_INVARG);
|
|
else {
|
|
r.type = TYPE_STR;
|
|
r.v.str = str_dup(conn_name);
|
|
return make_var_pack(r);
|
|
}
|
|
}
|
|
|
|
static package
|
|
bf_notify(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (player, string [, no_flush]) */
|
|
Objid conn = arglist.v.list[1].v.obj;
|
|
const char *line = arglist.v.list[2].v.str;
|
|
int no_flush = (arglist.v.list[0].v.num > 2
|
|
? is_true(arglist.v.list[3])
|
|
: 0);
|
|
shandle *h = find_shandle(conn);
|
|
Var r;
|
|
|
|
if (!is_wizard(progr) && progr != conn) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_PERM);
|
|
}
|
|
r.type = TYPE_INT;
|
|
if (h && !h->disconnect_me) {
|
|
if (h->binary) {
|
|
size_t length;
|
|
|
|
line = moobinary_to_raw_bytes(line, &length);
|
|
if (!line) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_INVARG);
|
|
}
|
|
r.v.num = network_send_bytes(h->nhandle, line, length, !no_flush);
|
|
} else
|
|
r.v.num = network_send_line(h->nhandle, line, !no_flush);
|
|
} else {
|
|
if (in_emergency_mode)
|
|
emergency_notify(conn, line);
|
|
r.v.num = 1;
|
|
}
|
|
free_var(arglist);
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
static package
|
|
bf_boot_player(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (object) */
|
|
Objid oid = arglist.v.list[1].v.obj;
|
|
|
|
free_var(arglist);
|
|
|
|
if (oid != progr && !is_wizard(progr))
|
|
return make_error_pack(E_PERM);
|
|
|
|
boot_player(oid);
|
|
return no_var_pack();
|
|
}
|
|
|
|
static package
|
|
bf_set_connection_option(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (conn, option, value) */
|
|
Objid oid = arglist.v.list[1].v.obj;
|
|
const char *option = arglist.v.list[2].v.str;
|
|
Var value = arglist.v.list[3];
|
|
shandle *h = find_shandle(oid);
|
|
enum error e = E_NONE;
|
|
|
|
if (oid != progr && !is_wizard(progr))
|
|
e = E_PERM;
|
|
else if (!h || h->disconnect_me
|
|
|| (!server_set_connection_option(h, option, value)
|
|
&& !tasks_set_connection_option(h->tasks, option, value)
|
|
&& !network_set_connection_option(h->nhandle, option, value)))
|
|
e = E_INVARG;
|
|
|
|
free_var(arglist);
|
|
if (e == E_NONE)
|
|
return no_var_pack();
|
|
else
|
|
return make_error_pack(e);
|
|
}
|
|
|
|
static package
|
|
bf_connection_options(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (conn [, opt-name]) */
|
|
Objid oid = arglist.v.list[1].v.obj;
|
|
int nargs = arglist.v.list[0].v.num;
|
|
const char *oname = (nargs >= 2 ? arglist.v.list[2].v.str : 0);
|
|
shandle *h = find_shandle(oid);
|
|
Var ans;
|
|
|
|
if (!h || h->disconnect_me) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_INVARG);
|
|
} else if (oid != progr && !is_wizard(progr)) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_PERM);
|
|
}
|
|
if (oname) {
|
|
if (!server_connection_option(h, oname, &ans)
|
|
&& !tasks_connection_option(h->tasks, oname, &ans)
|
|
&& !network_connection_option(h->nhandle, oname, &ans)) {
|
|
free_var(arglist);
|
|
return make_error_pack(E_INVARG);
|
|
}
|
|
} else {
|
|
ans = new_list(0);
|
|
ans = server_connection_options(h, ans);
|
|
ans = tasks_connection_options(h->tasks, ans);
|
|
ans = network_connection_options(h->nhandle, ans);
|
|
}
|
|
|
|
free_var(arglist);
|
|
return make_var_pack(ans);
|
|
}
|
|
|
|
static slistener *
|
|
find_slistener(Var desc)
|
|
{
|
|
slistener *l;
|
|
|
|
for (l = all_slisteners; l; l = l->next)
|
|
if (equality(desc, l->desc, 0))
|
|
return l;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static package
|
|
bf_listen(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (oid, desc) */
|
|
Objid oid = arglist.v.list[1].v.obj;
|
|
Var desc = arglist.v.list[2];
|
|
int nargs = arglist.v.list[0].v.num;
|
|
int print_messages = nargs >= 3 && is_true(arglist.v.list[3]);
|
|
enum error e;
|
|
slistener *l = 0;
|
|
|
|
if (!is_wizard(progr))
|
|
e = E_PERM;
|
|
else if (!valid(oid) || find_slistener(desc))
|
|
e = E_INVARG;
|
|
else if (!(l = new_slistener(oid, desc, print_messages, &e))); /* Do nothing; e is already set */
|
|
else if (!start_listener(l))
|
|
e = E_QUOTA;
|
|
|
|
free_var(arglist);
|
|
if (e == E_NONE)
|
|
return make_var_pack(var_ref(l->desc));
|
|
else
|
|
return make_error_pack(e);
|
|
}
|
|
|
|
static package
|
|
bf_unlisten(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* (desc) */
|
|
Var desc = arglist.v.list[1];
|
|
enum error e = E_NONE;
|
|
slistener *l = 0;
|
|
|
|
if (!is_wizard(progr))
|
|
e = E_PERM;
|
|
else if (!(l = find_slistener(desc)))
|
|
e = E_INVARG;
|
|
|
|
free_var(arglist);
|
|
if (e == E_NONE) {
|
|
free_slistener(l);
|
|
return no_var_pack();
|
|
} else
|
|
return make_error_pack(e);
|
|
}
|
|
|
|
static package
|
|
bf_listeners(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr UNUSED_)
|
|
{ /* () */
|
|
int i, count = 0;
|
|
Var list, entry;
|
|
slistener *l;
|
|
|
|
free_var(arglist);
|
|
for (l = all_slisteners; l; l = l->next)
|
|
count++;
|
|
list = new_list(count);
|
|
for (i = 1, l = all_slisteners; l; i++, l = l->next) {
|
|
list.v.list[i] = entry = new_list(3);
|
|
entry.v.list[1].type = TYPE_OBJ;
|
|
entry.v.list[1].v.obj = l->oid;
|
|
entry.v.list[2] = var_ref(l->desc);
|
|
entry.v.list[3].type = TYPE_INT;
|
|
entry.v.list[3].v.num = l->print_messages;
|
|
}
|
|
|
|
return make_var_pack(list);
|
|
}
|
|
|
|
static package
|
|
bf_buffered_output_length(Var arglist, Byte next UNUSED_, void *vdata UNUSED_, Objid progr)
|
|
{ /* ([connection]) */
|
|
int nargs = arglist.v.list[0].v.num;
|
|
Objid conn = nargs >= 1 ? arglist.v.list[1].v.obj : 0;
|
|
Var r;
|
|
|
|
free_var(arglist);
|
|
r.type = TYPE_INT;
|
|
if (nargs == 0)
|
|
r.v.num = MAX_QUEUED_OUTPUT;
|
|
else {
|
|
shandle *h = find_shandle(conn);
|
|
|
|
if (!h)
|
|
return make_error_pack(E_INVARG);
|
|
else if (progr != conn && !is_wizard(progr))
|
|
return make_error_pack(E_PERM);
|
|
|
|
r.v.num = network_buffered_output_length(h->nhandle);
|
|
}
|
|
|
|
return make_var_pack(r);
|
|
}
|
|
|
|
void
|
|
register_server(void)
|
|
{
|
|
register_function("server_version", 0, 1, bf_server_version, TYPE_ANY);
|
|
register_function("renumber", 1, 1, bf_renumber, TYPE_OBJ);
|
|
register_function("reset_max_object", 0, 0, bf_reset_max_object);
|
|
register_function("memory_usage", 0, 0, bf_memory_usage);
|
|
register_function("shutdown", 0, 1, bf_shutdown, TYPE_STR);
|
|
register_function("dump_database", 0, 0, bf_dump_database);
|
|
register_function("db_disk_size", 0, 0, bf_db_disk_size);
|
|
register_function("open_network_connection", 0, -1,
|
|
bf_open_network_connection);
|
|
register_function("connected_players", 0, 1, bf_connected_players,
|
|
TYPE_ANY);
|
|
register_function("connected_seconds", 1, 1, bf_connected_seconds,
|
|
TYPE_OBJ);
|
|
register_function("idle_seconds", 1, 1, bf_idle_seconds, TYPE_OBJ);
|
|
register_function("connection_name", 1, 1, bf_connection_name, TYPE_OBJ);
|
|
register_function("notify", 2, 3, bf_notify, TYPE_OBJ, TYPE_STR, TYPE_ANY);
|
|
register_function("boot_player", 1, 1, bf_boot_player, TYPE_OBJ);
|
|
register_function("set_connection_option", 3, 3, bf_set_connection_option,
|
|
TYPE_OBJ, TYPE_STR, TYPE_ANY);
|
|
register_function("connection_option", 2, 2, bf_connection_options,
|
|
TYPE_OBJ, TYPE_STR);
|
|
register_function("connection_options", 1, 1, bf_connection_options,
|
|
TYPE_OBJ);
|
|
register_function("listen", 2, 3, bf_listen, TYPE_OBJ, TYPE_ANY, TYPE_ANY);
|
|
register_function("unlisten", 1, 1, bf_unlisten, TYPE_ANY);
|
|
register_function("listeners", 0, 0, bf_listeners);
|
|
register_function("buffered_output_length", 0, 1,
|
|
bf_buffered_output_length, TYPE_OBJ);
|
|
}
|
|
|
|
|
|
/*
|
|
* $Log$
|
|
* Revision 2.11 1996/05/12 21:26:10 pavel
|
|
* Slightly cleaned up some code in bf_shutdown(). Release 1.8.0p5.
|
|
*
|
|
* Revision 2.10 1996/04/08 01:01:19 pavel
|
|
* Made get_server_option() public. Added `-l LOGFILE' option. Slightly
|
|
* reordered the start-up sequence to more frequently avoid writing into the
|
|
* log when the server is about to abort anyway. Release 1.8.0p3.
|
|
*
|
|
* Revision 2.9 1996/03/19 07:10:10 pavel
|
|
* Changed connect timeout handling to use an in-DB server option. Fixed
|
|
* task_id() in emergency-mode evals. Moved built-in function registration to
|
|
* happen after the start-up messages. Release 1.8.0p2.
|
|
*
|
|
* Revision 2.8 1996/03/10 01:10:50 pavel
|
|
* Added `connection_option()' built-in function. Made argument to
|
|
* shutdown() optional. Release 1.8.0.
|
|
*
|
|
* Revision 2.7 1996/02/11 00:49:03 pavel
|
|
* Made connection messages come from properties on LISTENER.server_options,
|
|
* if defined. Enhanced the robustness of the system-option-fetching code in
|
|
* the face of invalid objects. Release 1.8.0beta2.
|
|
*
|
|
* Revision 2.6 1996/02/08 06:53:21 pavel
|
|
* Added `list' and `disassemble' commands to emergency wizard mode. Now
|
|
* ignore SIGFPE. Added support for tasks-module connection options. Added
|
|
* connection_options() function. Renamed err/logf() to errlog/oklog() and
|
|
* TYPE_NUM to TYPE_INT. Updated copyright notice for 1996.
|
|
* Release 1.8.0beta1.
|
|
*
|
|
* Revision 2.5 1996/01/16 07:25:25 pavel
|
|
* Fixed uninitialized-variable bug in `program' in emergency wizard mode.
|
|
* Release 1.8.0alpha6.
|
|
*
|
|
* Revision 2.4 1996/01/11 07:36:41 pavel
|
|
* Added CHKPT_FUNC reason, missing forever. Added support for binary I/O.
|
|
* Added calls to $user_disconnected() on timeout and for booted un-logged-in
|
|
* connections. Added buffered_output_length() built-in.
|
|
* Release 1.8.0alpha5.
|
|
*
|
|
* Revision 2.3 1995/12/31 00:06:00 pavel
|
|
* Added support for the new multiple-listening-points interface, including
|
|
* listen(), unlisten(), and listeners(). Allowed DB-configurable messages to
|
|
* be given as lists of strings. Suppressed DB-configurable messages to
|
|
* outbound connections. Moved server-full handling to here from net_multi.c,
|
|
* also making the message DB-configurable. Release 1.8.0alpha4.
|
|
*
|
|
* Revision 2.2 1995/12/28 00:33:08 pavel
|
|
* Added db_disk_size() built-in. Release 1.8.0alpha3.
|
|
*
|
|
* Revision 2.1 1995/12/11 07:41:12 pavel
|
|
* Fixed checkpoint timing to test for overflow when adding $dump_interval to
|
|
* the current time. Added an optional argument to `connected_players()' to
|
|
* include even un-logged-in or outbound connections.
|
|
*
|
|
* Release 1.8.0alpha2.
|
|
*
|
|
* Revision 2.0 1995/11/30 04:31:19 pavel
|
|
* New baseline version, corresponding to release 1.8.0alpha1.
|
|
*
|
|
* Revision 1.19 1992/10/23 23:03:47 pavel
|
|
* Added copyright notice.
|
|
*
|
|
* Revision 1.18 1992/10/23 22:22:24 pavel
|
|
* Change to #include "my-wait.h" instead of <sys/wait.h>.
|
|
*
|
|
* Revision 1.17 1992/10/21 03:02:35 pavel
|
|
* Converted to use new automatic configuration system.
|
|
*
|
|
* Revision 1.16 1992/10/17 20:52:15 pavel
|
|
* Global rename of strdup->str_dup, strref->str_ref, vardup->var_dup, and
|
|
* varref->var_ref.
|
|
*
|
|
* Revision 1.15 1992/10/02 00:01:28 pavel
|
|
* Fixed a bug in the main loop whereby external shutdown requests (e.g., the
|
|
* interrupt signal) might not be noticed until some user I/O or a task
|
|
* execution occurred.
|
|
*
|
|
* Revision 1.14 1992/09/26 02:26:20 pavel
|
|
* Added support for printing the network protocol name on server start-up.
|
|
*
|
|
* Revision 1.13 1992/09/15 15:53:00 pjames
|
|
* Renamed FLUSH choices.
|
|
*
|
|
* Revision 1.12 1992/09/14 17:46:16 pjames
|
|
* Moved db_modification code to db modules.
|
|
* Added set up for disk based data base updating.
|
|
*
|
|
* Revision 1.11 1992/09/08 22:08:56 pjames
|
|
* Added all code from bf_server.c.
|
|
*
|
|
* Revision 1.10 1992/09/03 23:52:33 pavel
|
|
* Added support for multiple complete network implementations.
|
|
*
|
|
* Revision 1.9 1992/08/28 16:01:04 pjames
|
|
* Changed myfree(*, M_STRING) to free_str(*).
|
|
*
|
|
* Revision 1.8 1992/08/26 18:30:45 pavel
|
|
* Dump interval and retry interval are now local integers.
|
|
* Changed logic in regards to using POSIX.
|
|
*
|
|
* Revision 1.7 1992/08/13 21:28:11 pjames
|
|
* Now only calls one procedure to register built in functions.
|
|
*
|
|
* Revision 1.6 1992/08/12 01:50:42 pjames
|
|
* run_ready_tasks() was moved to before logoff actions, so that logoffs
|
|
* would happen faster.
|
|
*
|
|
* Revision 1.5 1992/08/10 16:55:31 pjames
|
|
* Updated #includes. Added register_bf_log().
|
|
*
|
|
* Revision 1.4 1992/07/30 23:22:26 pavel
|
|
* Added a remote checkpoint-request signal (SIGUSR2), analogous to the existing
|
|
* shutdown-requestion signal (SIGUSR1).
|
|
*
|
|
* Revision 1.3 1992/07/30 00:40:59 pavel
|
|
* Add support for compiling on RISC/os 4.52 and NonStop-UX A22.
|
|
*
|
|
* Revision 1.2 1992/07/21 00:06:27 pavel
|
|
* Added rcsid_<filename-root> declaration to hold the RCS ident. string.
|
|
*
|
|
* Revision 1.1 1992/07/20 23:23:12 pavel
|
|
* Initial RCS-controlled version.
|
|
*/
|