/******************************************************************************
  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 <errno.h>

#include "my-types.h"		/* must be first on some systems */
#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 "config.h"
#include "db.h"
#include "db_io.h"
#include "disassemble.h"
#include "execute.h"
#include "functions.h"
#include "list.h"
#include "log.h"
#include "network.h"
#include "options.h"
#include "parser.h"
#include "profiler.h"
#include "random.h"
#include "server.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"

#include "execute.h"

static pid_t parent_pid;
int in_child = 0;

static const char *shutdown_message = 0;	/* shut down if non-zero */
static Stream*     shutdown_stream;             /* the last output stream */
static int in_emergency_mode = 0;
static Var checkpointed_connections;

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: #%d now listening on %s\n", l->oid, l->name);
	return 1;
    } else {
	errlog("LISTEN: Can't start #%d listening on %s!\n", l->oid, l->name);
	return 0;
    }
}

static void
free_slistener(slistener * l)
{
    network_close_listener(l->nlistener);
    oklog("UNLISTEN: #%d 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 void
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();
}

void
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)
{
    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, Timer_Data data)
{
    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 (#%d)", db_object_name(oid), oid);
    else
	stream_printf(s, "#%d", 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;
	int res;
    /* First, notify DB of disconnections for all checkpointed connections */
    for (i = 1; i <= checkpointed_connections.v.list[0].v.num; i++) {
	Var v;

	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);

    /* Open /dev/urandom and keep it open as long as the server is running
       so that our random() has a source of random bits.  Dev_Random is
       defined globally in server.h */
    extern FILE *Dev_Random;
    Dev_Random = fopen("/dev/urandom", "r");
    if (Dev_Random == NULL) panic("Couldn't open /dev/urandom");

    /* Next, 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_useconds = next_task_start();
	int useconds_left = task_useconds < 0 ? 1000000 : task_useconds;
	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
	res = network_process_io(useconds_left);

	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: #%d 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: #%d 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);
		}
	    }
	}
    }

    fclose(Dev_Random);
    oklog("SHUTDOWN: %s\n", shutdown_message);
    send_shutdown_message(shutdown_message);
    free_stream(shutdown_stream);
}

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];
}

static int
server_set_connection_option(shandle * h, const char *option, Var value)
{
    if (!mystrcasecmp(option, "binary")) {
	h->binary = is_true(value);
	network_set_connection_binary(h->nhandle, h->binary);
	return 1;
    }
    return 0;
}

static int
server_connection_option(shandle * h, const char *option, Var * value)
{
    if (!mystrcasecmp(option, "binary")) {
	value->type = TYPE_INT;
	value->v.num = h->binary;
	return 1;
    }
    return 0;
}

static Var
server_connection_options(shandle * h, Var list)
{
    Var pair;

    pair = new_list(2);
    pair.v.list[1].type = TYPE_STR;
    pair.v.list[1].v.str = str_dup("binary");
    pair.v.list[2].type = TYPE_INT;
    pair.v.list[2].v.num = h->binary;

    return listappend(list, pair);
}

static char *
read_stdin_line()
{
    static Stream *s = 0;
    char *line, buffer[1000];
    int buflen;

    if (!s)
	s = new_stream(100);

    do {			/* Read even a very long line of input */
	fgets(buffer, sizeof(buffer), stdin);
	buflen = strlen(buffer);
	if (buflen == 0)
	    return 0;
	if (buffer[buflen - 1] == '\n') {
	    buffer[buflen - 1] = '\0';
	    buflen--;
	}
	stream_add_string(s, buffer);
    } while (buflen == sizeof(buffer) - 1);
    line = reset_stream(s);
    while (*line == ' ')
	line++;

    return line;
}

static void
emergency_notify(Objid player, const char *line)
{
    printf("#%d <- %s\n", player, line);
}

static int
emergency_mode()
{
    char *line;
    Var words;
    int nargs;
    const char *command;
    Stream *s = new_stream(100);
    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 #%d 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();
		    printf("** No objects in database; created #%d.\n",
			   first_valid);
		}
		wizard = first_valid;
		db_set_object_flag(wizard, FLAG_WIZARD);
		printf("** No wizards in database; wizzed #%d.\n", wizard);
	    }
	    printf("** Now running emergency commands as #%d ...\n", wizard);
	}
	printf("\nMOO (#%d)%s: ", wizard, debug ? "" : "[!d]");
	line = read_stdin_line();

	if (!line)
	    start_ok = 0;	/* treat EOF as "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");
		for (;;) {
		    line = read_stdin_line();
		    if (!strcmp(line, "."))
			break;
		    else {
			str.v.str = str_dup(line);
			code = listappend(code, str);
		    }
		}
	    } 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:
		    printf("=> %s\n", value_to_literal(result));
		    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("** %d 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, str, errors;
		    char *line;
		    Program *program;

		    code = new_list(0);
		    str.type = TYPE_STR;

		    while (strcmp(line = read_stdin_line(), ".")) {
			str.v.str = str_dup(line);
			code = listappend(code, str);
		    }

		    program = parse_list_as_program(code, &errors);
		    if (program) {
			db_set_verb_program(h, program);
			printf("Verb programmed.\n");
		    } else {
			int i;

			printf("** %d 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) {
		exit(1);
	    } 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
		 && sscanf(words.v.list[2].v.str, "#%d", &wizard) == 1) {
		printf("** Switching to wizard #%d...\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);
	}
    }

    free_stream(s);
    in_emergency_mode = 0;
    oklog("EMERGENCY_MODE: Leaving mode; %s continue...\n",
	  start_ok ? "will" : "won't");
    return start_ok;
}


/*
 * 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)
{
    Var v;

    if (get_server_option(SYSTEM_OBJECT, name, &v))
	return is_true(v);
    else
	return 0;
}

int
server_int_option(const char *name, int 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;
}

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 = outbound ? SYSTEM_OBJECT : l->oid;
    h->tasks = new_task_queue(h->player, h->listener);
    h->disconnect_me = 0;
    h->outbound = outbound;
    h->binary = 0;
    h->print_messages = (!outbound && l->print_messages);

    if (!outbound)
	new_input_task(h->tasks, "");

    oklog("%s: #%d 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);
}

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) {
	/* 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_h->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_h->listener == new_h->listener)
	    call_notifier(new_id, new_h->listener, "user_reconnected");
	else {
	    call_notifier(new_id, existing_h->listener,
			  "user_client_disconnected");
	    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");
    }
}

#ifdef CONNECTION_TRANSFERS
void
server_connection_transferred(Objid old_id, Objid new_id)
{
    /* this is basically player_connected, but quiet */

    shandle *existing_h = find_shandle(new_id);
    shandle *new_h = find_shandle(old_id);
        
    if (!new_h)
        panic("Connection transferred from non-existent handle");
            
    new_h->player = new_id;
    new_h->connection_time = time(0);

    oklog("TRANSFERRED: %s (#%d) to %s (#%d)\n",
	db_object_name(old_id), old_id, db_object_name(new_id), new_id);

    if (existing_h) {
        network_close(existing_h->nhandle);
        free_shandle(existing_h);
    }

    Var args;

    args = new_list(2);
    args.v.list[1].type  = TYPE_OBJ;
    args.v.list[1].v.obj = old_id;
    args.v.list[2].type  = TYPE_OBJ;
    args.v.list[2].v.obj = new_id;

    run_server_task(new_id, new_h->listener, "user_transferred", args, "", 0);
}
#endif

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;
}

void
write_active_connections(void)
{
    int count = 0;
    shandle *h;

    for (h = all_shandles; h; h = h->next)
	count++;

    dbio_printf("%d active connections with listeners\n", count);

    for (h = all_shandles; h; h = h->next)
	dbio_printf("%d %d\n", h->player, h->listener);
}

int
read_active_connections(void)
{
    int count, i, have_listeners = 0;
    char c;

    i = dbio_scanf("%d active connections%c", &count, &c);
    if (i == EOF) {		/* older database format */
	checkpointed_connections = new_list(0);
	return 1;
    } else if (i != 2) {
	errlog("READ_ACTIVE_CONNECTIONS: Bad active connections count.\n");
	return 0;
    } else if (c == ' ') {
	if (strcmp(dbio_read_string(), "with listeners") != 0) {
	    errlog("READ_ACTIVE_CONNECTIONS: Bad listeners tag.\n");
	    return 0;
	} else
	    have_listeners = 1;
    } else if (c != '\n') {
	errlog("READ_ACTIVE_CONNECTIONS: Bad EOL.\n");
	return 0;
    }
    checkpointed_connections = new_list(count);
    for (i = 1; i <= count; i++) {
	Objid who, listener;
	Var v;

	if (have_listeners) {
	    if (dbio_scanf("%d %d\n", &who, &listener) != 2) {
		errlog("READ_ACTIVE_CONNECTIONS: Bad conn/listener pair.\n");
		return 0;
	    }
	} else {
	    who = dbio_read_num();
	    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;
}

int
main(int argc, char **argv)
{
    char *this_program = str_dup(argv[0]);
    const char *log_file = 0;
    const char *profile_file = 0;
    int emergency = 0;
    int recovery = 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 'r':               /* Contents/child rebuild */
	    recovery = 1;
	    break;
	case 'l':		/* Specified log file */
	    if (argc > 1) {
		log_file = argv[1];
		argc--;
		argv++;
	    } else
		argc = 0;	/* Provoke usage message below */
	    break;
        case 'p':               /* Specified profile file */
	    if (argc > 1) {
		profile_file = argv[1];
		argc--;
		argv++;
	    } else
		argc = 0;
	    break;		/* Provoke usage message below */
	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 (profile_file) {
	FILE *f = fopen(profile_file, "a");

	if (f)
	    set_profiler_file(f);
	else {
	    perror("Error opening specified profile file");
	    exit(2);
	}
    } else
	set_profiler_file(stdout);

    if (!db_initialize(&argc, &argv)
		|| !network_initialize(argc, argv, &desc)) {
		fprintf(stderr, "Usage: %s [-e] [-r] [-l log-file] [-p profile-file] %s %s\n",
			this_program, db_usage_string(), network_usage_string());
		exit(1);
    }
    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");

    register_bi_functions();

    l = new_slistener(SYSTEM_OBJECT, desc, 1, 0);
    if (!l) {
		errlog("Can't create initial connection point!\n");
		exit(1);
    }
    free_var(desc);

    if (recovery) {
		oklog("*** Contents/child list recovery mode ON ***\n");
	        db_recoverymode(1);
    }

    if (!db_load())
	exit(1);

    load_server_options();

    SRANDOM(time(0));

    parent_pid = getpid();
    setup_signals();
    reset_command_history();

    if (!emergency || emergency_mode()) {
		if (!start_listener(l))
		    exit(1);

		main_loop();
		network_shutdown();
    }

#ifdef MEMORY_TRACE
    verify_mem();
#endif

    db_shutdown();

    free_str(this_program);

    return 0;
}


/**** built in functions ****/

static package
bf_server_version(Var arglist, Byte next, void *vdata, Objid progr)
{
    Var r;
    r.type = TYPE_STR;
    r.v.str = str_dup(server_version);
    free_var(arglist);
    return make_var_pack(r);
}

static package
bf_renumber(Var arglist, Byte next, void *vdata, 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, void *vdata, 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, void *vdata, Objid progr)
{
    Var r;
    r = memory_usage();
    free_var(arglist);
    return make_var_pack(r);
}

static package
bf_shutdown(Var arglist, Byte next, void *vdata, Objid progr)
{
    /*
     * The stream 's' and its contents will leak, but we're shutting down,
     * so it doesn't really matter.
     */

    Stream *s;
    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);
    }
    s = new_stream(100);
    stream_printf(s, "shutdown() called by %s", object_name(progr));
    if (msg)
	stream_printf(s, ": %s", msg);
    shutdown_message = stream_contents(s);
    shutdown_stream  = s;

    free_var(arglist);
    return no_var_pack();
}

#ifdef MEMORY_TRACE
static package
bf_set_live_trace(Var arglist, Byte next, void *vdata, Objid progr)
{
    set_live_trace(arglist.v.list[1].v.num);
    free_var(arglist);
    return no_var_pack();
}
#endif

static package
bf_dump_database(Var arglist, Byte next, void *vdata, 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, void *vdata, Objid progr)
{
    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);
}

static package
bf_open_network_connection(Var arglist, Byte next, void *vdata, Objid progr)
{
#ifdef OUTBOUND_NETWORK

    Var r;
    enum error e;

    if (!is_wizard(progr)) {
        free_var(arglist);
        return make_error_pack(E_PERM);
    }

    e = network_open_connection(arglist);
    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, void *vdata, Objid progr)
{
    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;

    free_var(arglist);
    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;

    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, void *vdata, Objid progr)
{				/* (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_is_connected(Var arglist, Byte next, void *vdata, Objid progr)
{                               /* (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 = 1;
    else
        r.v.num = 0;
    free_var(arglist);
    
    return make_var_pack(r);
}


static package
bf_idle_seconds(Var arglist, Byte next, void *vdata, Objid progr)
{				/* (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, void *vdata, 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, void *vdata, 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) { */
	    int length;

	    line = binary_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, void *vdata, 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, void *vdata, 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, void *vdata, 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_set_profiler_filename(Var arglist, Byte next, void *vdata, Objid progr)
{
    if (!is_wizard(progr)) {
	free_var(arglist);
	return make_error_pack(E_PERM);
    } else {
	FILE *f = fopen(arglist.v.list[1].v.str, "a");

	if (f) {
		set_profiler_file(f);
	        oklog("*** PROFILING FILE CHANGED: now '%s', changed by %s (#%d) ***\n",
       		    arglist.v.list[1].v.str, db_object_name(progr), progr);
		free_var(arglist);

		return make_var_pack(zero);
	} else {
		char *error;

		error = strerror(errno);
                oklog("*** PROFILING FILE NOT CHANGED: tried '%s', %s (#%d): %s ***\n",
                    arglist.v.list[1].v.str, db_object_name(progr), progr, error);

		Var v;
		v.type = TYPE_STR;
		v.v.str = str_dup(error);
		return make_var_pack(v);
	}
    }
}


static package
bf_listen(Var arglist, Byte next, void *vdata, 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, void *vdata, 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, void *vdata, Objid progr)
{				/* () */
    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, void *vdata, 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(arglist.v.list[1].v.obj);

	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, 0, bf_server_version);
    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("is_connected", 1, 1, bf_is_connected, 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);
    register_function("set_profiler_filename", 1, 1, bf_set_profiler_filename, TYPE_STR);
#ifdef MEMORY_TRACE
    register_function("set_live_trace", 1, 1, bf_set_live_trace, TYPE_INT);
#endif
}

char rcsid_server[] = "$Id: server.c,v 1.11 2010/05/17 01:52:27 blacklite Exp $";

/* 
 * $Log: server.c,v $
 * Revision 1.11  2010/05/17 01:52:27  blacklite
 * add set_profiler_filename
 *
 * Revision 1.10  2009/07/27 01:46:57  blacklite
 * typo
 *
 * Revision 1.9  2009/07/26 21:57:42  blacklite
 * CONNECTION_TRANSFERS define, disabled though. Plus the bf_transfer_connection
 * function and various supporting things. Which don't actually work without
 * causing segfaults, but, hey, it's a start.
 *
 * Revision 1.8  2009/07/25 03:21:59  blacklite
 * add profiling when PROFILE_VERBS is defined, spits info out to a profile log
 * (specify with -p). I may still be missing some spots but this seems to give
 * usable info right now.
 *
 * Revision 1.7  2009/07/23 04:22:37  blacklite
 * added is_connected function.
 *
 * Revision 1.6  2009/03/08 12:41:31  blacklite
 * Added HASH data type, yield keyword, MEMORY_TRACE, vfscanf(),
 * extra myrealloc() and memcpy() tricks for lists, Valgrind
 * support for str_intern.c, etc. See ChangeLog.txt.
 *
 * Revision 1.5  2008/08/24 05:06:13  blacklite
 * Add -r option and more fixes to the recovery process.
 *
 * Revision 1.4  2007/09/12 07:33:29  spunky
 * This is a working version of the current HellMOO server
 *
 * Revision 1.5  1998/12/29 06:56:32  nop
 * Fixed leak in onc().
 *
 * Revision 1.4  1998/12/14 13:18:57  nop
 * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
 *
 * Revision 1.3  1997/03/03 05:03:52  nop
 * steak2: move protectedness into builtin struct, load_server_options()
 * now required for $server_options updates.
 *
 * Revision 1.2  1997/03/03 04:19:24  nop
 * GNU Indent normalization
 *
 * Revision 1.1.1.1  1997/03/03 03:45:01  nop
 * LambdaMOO 1.8.0p5
 *
 * 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.
 */
