/****************************************************************************** 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 "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 "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 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; /* 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); /* 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: #%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); } } } } 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]; } 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"); } } 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; 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); } 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 (!db_load()) exit(1); 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(); } 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); free_var(arglist); return no_var_pack(); } 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)) 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_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_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("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); } char rcsid_server[] = "$Id: server.c,v 2.11 1996/05/12 21:26:10 pavel Exp $"; /* $Log: server.c,v $ * 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. */