/* * NAME: global.c * DESCRIPTION: global object for all MOO objects */ # define DEBUG 0 inherit regexp "/std/cache"; inherit core "/std/core"; inherit "/std/string"; inherit "/std/data"; # include <objects.h> # include <moo/data.h> # include <moo/config.h> # include <moo/perms.h> # include <moo/command.h> # include <moo/verb.h> # include <moo/verbinfo.h> # include <dgd/limits.h> # include <dgd/status.h> mapping connections; /* all user connections, indexed by MOO player obj */ mapping pfds; /* all user connections, indexed by pseudo-fd */ mapping unlogged_in; /* all unlogged-in connections, indexed by #-num */ mapping tasks; /* all forked/suspended tasks, indexed by MOO taskid */ /* ({ vobj, dgd_taskid, info, start_time, trace }) */ object *players; /* all MOO player objects */ int max_object; /* max_object() */ int *recycled; /* list of recycle()'d objects */ object renumber_obj; /* an object being renumbered */ int next_task_id; /* the next MOO taskid */ int next_ulog_id; /* #-num of the next accepted connection */ string *error_names; /* list of all MOO error names */ string *error_descs; /* list of all MOO error descriptions */ string *prep_names; /* list of all MOO command prepositions */ string *prep_list; /* parsed list of MOO prepositions */ string *vs_names; /* ({ "none", "any", "this" }) */ mapping prep_map; /* preposition codes, indexed by string preposition */ mapping vs_map; /* verb spec codes, indexed by vs_names string */ int unum; /* unique number */ int cp_handle; /* checkpoint call_out() taskid */ int stack_size; /* MOO value stack size */ int max_depth; /* MOO max verb recursion depth */ string timezone; /* the current timezone */ string logfile; /* name of the current log file */ static int generate_task_id(void); /* * NAME: create() * DESCRIPTION: initialize the global object */ static void create(void) { regexp::create(); core::create(); tasks = ([ ]); connections = ([ ]); players = ({ }); unlogged_in = ([ ]); pfds = ([ ]); next_ulog_id = -2; next_task_id = generate_task_id(); max_object = -1; recycled = ({ }); error_names = ERROR_NAMES; error_descs = ERROR_DESCS; vs_names = ({ "none", "any", "this" }); vs_map = ([ "none" : VS_NONE, "any" : VS_ANY, "this" : VS_THIS ]); logfile = CONFIG->query(CF_LOG_FILE); } /* * NAME: get_max_object() * DESCRIPTION: return the highest numbered object number */ int get_max_object(void) { return max_object; } /* * NAME: generate_task_id() * DESCRIPTION: return a unique, random task id */ static int generate_task_id(void) { int task_id; do { task_id = random(INT_MAX); } while (tasks[task_id]); return task_id; } /* * NAME: take_task() * DESCRIPTION: return a task id for a new task */ int take_task(void) { int task_id; task_id = next_task_id; next_task_id = generate_task_id(); return task_id; } /* * NAME: register_task() * DESCRIPTION: recognize a new MOO task associated with a DGD call_out() */ void register_task(mixed *info, int delay, object ob, int dgd_task_id) { int start_time; if (delay == -1) start_time = -1; else if (delay == 0) start_time = time() + 1; else start_time = time() + delay; tasks[info[I_TASKID]] = ({ ob, dgd_task_id, info, start_time }); } /* * NAME: task_started() * DESCRIPTION: a DGD call_out() has begun execution */ void task_started(int moo_task_id) { tasks[moo_task_id] = 0; } /* * NAME: remove_refs() * DESCRIPTION: delete references to verbs on the MOO call stack */ void remove_refs(mixed *info) { while (info) { object ob; if (ob = info[I_VERBOBJ]) ob->del(); info = info[I_LINK]; } } /* * NAME: kill_task() * DESCRIPTION: remove a task from the task queue and abort execution of it */ int kill_task(int moo_task_id, mixed *info) { mixed *task_info; if (! (task_info = tasks[moo_task_id])) return E_INVARG; if (! task_info[0]) /* lingering destructed verb/user task */ { tasks[moo_task_id] = 0; return E_INVARG; } if (! WIZARDP(info) && PROGRAMMER(info) != PROGRAMMER(task_info[2])) return E_PERM; tasks[moo_task_id] = 0; if (task_info[3] == -1) task_info[0]->unseed(); else task_info[0]->abort_task(task_info[1]); remove_refs(task_info[2]); return E_NONE; } /* * NAME: task_info() * DESCRIPTION: retrieve the call stack for a pending task */ mixed *task_info(int moo_task_id) { mixed *info; if (! (info = tasks[moo_task_id])) return 0; if (! info[0]) /* lingering destructed verb/user task */ { tasks[moo_task_id] = 0; return 0; } return info[2]; } /* * NAME: get_queued_tasks() * DESCRIPTION: return a list of queued task information */ MOOVAL get_queued_tasks(mixed *info) { MOOVAL *qt_info; int *task_ids, i, j, sz, ticks, programmer, wizard; mixed *task_info; programmer = PROGRAMMER(info); wizard = WIZARDP(info); qt_info = allocate(sz = map_sizeof(tasks)); task_ids = map_indices(tasks); task_info = map_values(tasks); ticks = CONFIG->query(CF_BG_TICKS); for (i = 0, j = -1; i < sz; ++i) { mixed *frame; if (! task_info[i][0]) /* lingering destructed verb/user task */ { tasks[task_ids[i]] = 0; continue; } frame = task_info[i][2]; if (wizard || programmer == PROGRAMMER(frame)) { int verbloc; object host; string verbname; host = frame[I_HOSTOBJ]; verbloc = host ? OBJNUM(host) : -1; verbname = frame[I_NAME]; if (verbname == "") verbname = "Input to EVAL"; qt_info[++j] = LST( ({ NUM(task_ids[i]), /* task-id */ NUM(task_info[i][3]), /* start-time */ NUM(0), /* clock-id */ NUM(ticks), /* ticks */ OBJ(frame[I_PRGMR]), /* programmer */ OBJ(verbloc), /* verb-loc */ STR(verbname), /* verb-name */ NUM(frame[I_LINENO]), /* line */ OBJ(frame[I_THIS]), /* this */ }) ); } } return LST(qt_info[.. j]); } /* * NAME: error_name() * DESCRIPTION: return the error name for an error num */ string error_name(int err) { return error_names[err]; } /* * NAME: error_desc() * DESCRIPTION: return the error description for an error num */ string error_desc(int err) { return error_descs[err]; } /* * NAME: prep_name() * DESCRIPTION: return the (full) name of a preposition */ string prep_name(int prep) { return prep < 0 ? vs_names[-prep - 1] : prep_names[prep - 1]; } /* * NAME: prep_code() * DESCRIPTION: return the preposition code for a string, or 0 */ int prep_code(string prep) { int code; string cruft; prep = tolower(prep); if (prep == "none" || prep == "any") return vs_map[prep]; sscanf(prep, "%s/", prep); prep = implode(explode(prep, " ") - ({ "" }), " "); if ((code = prep_map[prep]) != 0 || (sscanf(prep, "%d%s", code, cruft) == 2 && strlen(cruft) == 0 && (++code, code > 0 && code <= sizeof(prep_names)))) return code; return 0; } /* * NAME: vs_name() * DESCRIPTION: return the name for a object specifier */ string vs_name(int vs) { return vs_names[-vs - 1]; } /* * NAME: vs_code() * DESCRIPTION: return the code for an object specifier */ int vs_code(string vs) { return vs_map[tolower(vs)]; } /* * NAME: match_preposition() * DESCRIPTION: used by mooconn to parse a command line into dobj, prep, iobj */ string *match_preposition(string *args) { int i, sz, start, len, slen; string try, lower; sz = sizeof(prep_list); for (start = 1, len = sizeof(args); start < len; ++start) { try = implode(args[start ..], " "); lower = tolower(try) + " "; for (i = 0; i < sz; ++i) if (sscanf(lower, prep_list[i] + " %*s") == 1) { slen = strlen(prep_list[i]); return ({ implode(args[1 .. start - 1], " "), try[.. slen - 1], (slen >= strlen(try) ? "" : try[slen + 1 ..]) }); } } return ({ implode(args[1 ..], " "), "", "" }); } /* * NAME: fetch_object() * DESCRIPTION: called by DRIVER to return a new (or renumbered) MOO object */ object fetch_object(void) { return renumber_obj ? renumber_obj : clone_object(OBJECT); } /* * NAME: create_object() * DESCRIPTION: return a new MOO object reference */ object create_object(object parent, int owner) { object ob; int id; ob = load_object(MOOOBJ_NAME(id = ++max_object)); ob->init(parent, owner == -1 ? id : owner); return ob; } /* * NAME: add_recycled() * DESCRIPTION: remember an objnum as recycled */ private void add_recycled(int num) { int i, sz; for (i = 0, sz = sizeof(recycled); i < sz; ++i) if (recycled[i] > num) break; recycled = recycled[.. i - 1] + ({ num }) + recycled[i ..]; } /* * NAME: bootstrap_max_object() * DESCRIPTION: called by dbloader to bootstrap the value of max_object */ void bootstrap_max_object(int num) { max_object = num; } /* * NAME: bootstrap_recycled() * DESCRIPTION: called by dbloader to bootstrap a recycled object */ void bootstrap_recycled(int id) { add_recycled(id); } /* * NAME: renumber() * DESCRIPTION: change the objnum of an object */ int renumber(object ob) { int oldnum, newnum, i; oldnum = OBJNUM(ob); if (! sizeof(recycled) || recycled[0] > oldnum) return oldnum; newnum = recycled[0]; recycled = recycled[1 ..]; renumber_obj = ob; load_object(MOOOBJ_NAME(newnum)); renumber_obj = 0; add_recycled(oldnum); if (ob->is_owner()) { DRIVER->log("Non-optimized renumber(#" + (string) oldnum + ") => #" + (string) newnum + "..."); for (i = max_object; i--; ) if (ob = MOOOBJ(i)) ob->renumber_owner(oldnum, newnum); DRIVER->log("Renumber complete"); } else { DRIVER->log("Optimized renumber(#" + (string) oldnum + ") => #" + (string) newnum); ob->renumber_owner(oldnum, newnum); } return newnum; } /* * NAME: reset_max_object() * DESCRIPTION: make max_object be the highest existing objnum */ void reset_max_object(void) { int i; for (i = sizeof(recycled); i-- && recycled[i] == max_object; --max_object); recycled = recycled[.. i]; } /* * NAME: log_msg() * DESCRIPTION: write a log message to the server log */ void log_msg(string msg) { write_file(logfile, ctime(time())[4 .. 18] + ": " + msg + "\n"); } /* * NAME: zero() * DESCRIPTION: return #0, which hopefully exists */ object zero(void) { object zero; if (! (zero = MOOOBJ(0))) error("Missing MOO object #0"); return zero; } /* * NAME: arrange_checkpoint() * DESCRIPTION: read #0.dump_interval and arrange a checkpoint */ private void arrange_checkpoint(void) { MOOVAL interval; int delay; interval = zero()->safe_get_property("dump_interval"); delay = NUMP(interval) ? NUMVAL(interval) : 3600; if (delay < 60 && delay >= 0) delay = 3600; if (cp_handle) remove_call_out(cp_handle); if (delay > 0) cp_handle = call_out("checkpoint", delay, JS_INIT, 0); else cp_handle = 0; } /* * NAME: checkpoint_finished() * DESCRIPTION: call #0:checkpoint_finished (called from call_out) */ void checkpoint_finished(JS_PROTO, int success) { object zero; JS_BEGIN; zero = zero(); JS_PREP(1); RET = zero->call_verb(JS_DATA(1), "checkpoint_finished", server_vars(-1, zero, "checkpoint_finished", 0, NUM(success))); JS_END; JS_END; } /* * NAME: text_dump() * DESCRIPTION: make a text dump of the database (called from call_out) */ void text_dump(JS_PROTO) { string file; JS_BEGIN; file = CONFIG->query(CF_CHECKPOINT_FILE); log_msg("CHECKPOINTING on " + file); call_out("checkpoint_finished", 1, JS_INIT, 1); rename_file(file, file + ".old"); save_db(file); JS_END; } /* * NAME: checkpoint() * DESCRIPTION: initiate a checkpoint (called from call_out) */ void checkpoint(JS_PROTO, int text) { object zero; JS_BEGIN; arrange_checkpoint(); zero = zero(); JS_PREP(1); catch(RET = zero->call_verb(JS_DATA(1), "checkpoint_started", server_vars(-1, zero, "checkpoint_started", 0, NUM(text)))); if (text) call_out("text_dump", 0, JS_INIT); else { log_msg("CHECKPOINTING on DGD binary state file"); DRIVER->checkpoint(); call_out("checkpoint_finished", 1, JS_INIT, 1); } JS_END; /* do nothing upon resuming a suspended task */ JS_END; } /* * NAME: dump_database() * DESCRIPTION: called by bfun to initiate a checkpoint */ void dump_database(int text) { if (cp_handle) remove_call_out(cp_handle); cp_handle = call_out("checkpoint", 0, JS_INIT, text); } /* * NAME: init() * DESCRIPTION: called by restart() to initialize the database */ void init(JS_PROTO) { object zero; JS_BEGIN; zero = zero(); JS_PREP(1); catch(RET = zero->call_verb(JS_DATA(1), "server_started", server_vars(-1, zero, "server_started"))); arrange_checkpoint(); JS_END; /* do nothing upon resuming a suspended task */ JS_END; } /* * NAME: init_preps() * DESCRIPTION: initialize the list of prepositions */ private void init_preps(string *preps) { int i; prep_names = preps; prep_list = explode(implode(prep_names, "/"), "/"); prep_map = ([ ]); for (i = sizeof(preps); i > 0; --i) { int j; string *split; split = explode(preps[i - 1], "/"); for (j = sizeof(split) - 1; j >= 0; --j) prep_map[split[j]] = i; } } /* * NAME: restart() * DESCRIPTION: perform actions at MOO restart */ void restart(int shutdown_time) { string str; int i; object *conns; mixed *task_list; logfile = CONFIG->query(CF_LOG_FILE); rename_file(logfile, logfile + ".old"); str = ctime(time()); write_file(logfile, str[.. 19] + CONFIG->query(CF_TIMEZONE) + " " + str[20 ..] + ": RESTARTED\n"); /* log file silliness */ log_msg("STARTING: Version " + CONFIG->query(CF_MOO_VERSION) + " of the LambdaMOO server simulation"); log_msg(" (DGD version " + status()[ST_VERSION] + ")"); log_msg(" (LPMOO version " + CONFIG->query(CF_LPMOO_VERSION) + ")"); log_msg(" (Using protocols defined by DGD)"); log_msg(" (Task timeouts measured in DGD terms)"); log_msg("LOADING: DGD binary state dump already loaded"); log_msg("VALIDATING nothing optimistically ... finished."); log_msg("LOADING: using state dump, will dump new database there also"); /* discard any old connection objects */ conns = map_values(connections) + map_values(unlogged_in); for (i = sizeof(conns); i--; ) if (conns[i]) destruct_object(conns[i]); connections = ([ ]); unlogged_in = ([ ]); next_ulog_id = -2; /* reset the regexp cache */ regexp::reset(CONFIG->query(CF_PATTERN_CACHE)); /* query configuration parameters */ stack_size = CONFIG->query(CF_STACK_SIZE); max_depth = CONFIG->query(CF_MAX_VERB_DEPTH); timezone = CONFIG->query(CF_TIMEZONE); /* initialize the preposition list */ init_preps(CONFIG->query(CF_PREPOSITIONS)); /* fix task starting times */ for (i = sizeof(task_list = map_values(tasks)); i--; ) task_list[i][3] += time() - shutdown_time; /* stop checkpoints */ if (cp_handle) { remove_call_out(cp_handle); cp_handle = 0; } /* start The Very First Task */ init(JS_INIT); } /* * NAME: user_description() * DESCRIPTION: return a description string of a user */ string user_description(int programmer) { object ob; if (! (ob = MOOOBJ(programmer))) return "#" + programmer; else return ob->get_name() + " (#" + programmer + ")"; } /* * NAME: conn_description() * DESCRIPTION: return a description string of a connection */ string conn_description(object conn, int id) { return user_description(id) + " on " + conn->get_connection_name(); } /* * NAME: recycle() * DESCRIPTION: irrevocably destroy an object (called from destroyee) */ void recycle(object obj) { int *task_ids, i; mixed *task_info; object conn; add_recycled(OBJNUM(obj)); /* clean up any dangling tasks */ task_ids = map_indices(tasks); task_info = map_values(tasks); for (i = sizeof(task_ids); i--; ) if (! task_info[i][0]) tasks[task_ids[i]] = 0; /* clean up connections and players arrays */ if (conn = connections[obj]) { log_msg("RECYCLED: " + conn_description(conn, OBJNUM(obj))); conn->boot("*** Recycled ***"); } players -= ({ obj }); } /* * NAME: all_players() * DESCRIPTION: return a list of all player objects */ object *all_players(void) { return players; } /* * NAME: connected_players() * DESCRIPTION: return a list of connected player objects */ object *connected_players(void) { return map_indices(connections); } /* * NAME: get_connection_obj() * DESCRIPTION: return a corresponding connection object for a player, or 0 */ object get_connection_obj(object player) { return connections[player]; } /* * NAME: do_boot_player() * DESCRIPTION: call verbs after booting a player */ void do_boot_player(JS_PROTO, object listener, int id) { JS_BEGIN; if (! listener) return; JS_PREP(1); RET = listener->call_verb(JS_DATA(1), "user_disconnected", server_vars(id, listener, "user_disconnected", 0, OBJ(id))); JS_END; JS_END; } /* * NAME: boot_player() * DESCRIPTION: terminate a user's connection */ void boot_player(int id) { object player, conn; if (player = MOOOBJ(id)) conn = connections[player]; else conn = unlogged_in[id]; if (player && conn) call_out("do_boot_player", 0, JS_INIT, conn->get_listener(), id); if (conn) { log_msg("DISCONNECTED: " + conn_description(conn, id)); conn->boot("*** Disconnected ***"); } } /* * NAME: set_player_flag() * DESCRIPTION: add or remove a player object */ void set_player_flag(object ob, int flag) { if (flag) { players += ({ ob }); return; } players -= ({ ob }); if (connections[ob]) boot_player(OBJNUM(ob)); } /* * NAME: accept() * DESCRIPTION: assign an unlogged_id to a new connection */ int accept(object conn, string msg) { log_msg(msg + ": #" + next_ulog_id + " on " + conn->get_connection_name()); unlogged_in[next_ulog_id] = conn; return next_ulog_id--; } /* * NAME: get_unlogged_in() * DESCRIPTION: return a connection associated with an un-logged-in user */ object get_unlogged_in(int id) { return unlogged_in[id]; } /* * NAME: assign_pfd() * DESCRIPTION: allocate a pseudo fd for a connection */ int assign_pfd(object conn) { int pfd; pfd = 6; while (pfds[pfd]) ++pfd; pfds[pfd] = conn; return pfd; } /* * NAME: connect() * DESCRIPTION: a connection was established */ void connect(object conn, int id, object player) { unlogged_in[id] = 0; id = OBJNUM(player); connections[player] = conn; log_msg("CONNECTED: " + conn_description(conn, id)); } /* * NAME: connect_lpc() * DESCRIPTION: a connection was established to an LPC object */ void connect_lpc(object conn, int id, object lpc) { log_msg("LPC CONNECTION: " + object_name(lpc) + ", " + conn_description(conn, id)); } /* * NAME: redirect() * DESCRIPTION: a connection was redirected */ void redirect(object conn, int id, object player) { unlogged_in[id] = 0; id = OBJNUM(player); log_msg("REDIRECTED: " + user_description(id) + ", was " + connections[player]->get_connection_name() + ", now " + conn->get_connection_name()); connections[player] = conn; } /* * NAME: shutdown() * DESCRIPTION: called to shutdown the server */ void shutdown(string message) { object *conns; int i; for (i = sizeof(conns = map_values(connections)); i--; ) conns[i]->boot("*** Shutting down: " + message + " ***"); log_msg("SHUTDOWN: " + message); DRIVER->do_shutdown(); } /* * NAME: unique_number() * DESCRIPTION: return an ever-increasing number */ int unique_number(void) { return ++unum; } /* * NAME: cache_miss() * DESCRIPTION: return a value for the cache */ static mixed cache_miss(string key) { int insens; string pattern; sscanf(key, "%d*%s", insens, pattern); return regexp_compile(pattern, insens); } /* * NAME: get_regexp() * DESCRIPTION: return a compiled regexp pattern */ string *get_regexp(string pattern, int insens) { return regexp::fetch((string) insens + "*" + pattern); } /* * NAME: get_stack_size() * DESCRIPTION: return the configured value stack size */ int get_stack_size(void) { return stack_size; } /* * NAME: get_max_depth() * DESCRIPTION: return the configured max verb depth */ int get_max_depth(void) { return max_depth; } /* * NAME: get_timezone() * DESCRIPTION: return the current configured timezone */ string get_timezone(void) { return timezone; } /* * NAME: traceback() * DESCRIPTION: perform a MOO traceback (called from DRIVER) */ void traceback(mixed *info, string error) { object user; int obj, this, first; string message, verb; if (! (user = connections[MOOOBJ(info[I_PLAYER])]) && ! (user = this_user())) user = find_object(DRIVER); message = ""; first = 1; while (info) { obj = info[I_HOSTOBJ] ? OBJNUM(info[I_HOSTOBJ]) : -1; this = info[I_THIS]; verb = strlen(info[I_NAME]) ? info[I_NAME] : "Input to EVAL"; message += "#" + (string) obj + ":" + verb; if (this != obj) message += " (this == #" + (string) this + ")"; message += ", line " + (string) info[I_LINENO]; if (first) message += ": " + error; user->notify(message); info = info[I_LINK]; message = "... called from "; first = 0; } user->notify("(End of traceback)"); } /* * NAME: compile_verb() * DESCRIPTION: return a verb object from an AST */ object compile_verb(mixed *ast, int save) { string name; object verb; name = "/verb/" + (string) unique_number(); remove_file(name + ".c"); compiler->main(ast, name); verb = load_object(name); if (! DEBUG) remove_file(name + ".c"); if (save) verb->set_source(unparser->main(ast)); return verb; }