2013 lines
54 KiB
C
2013 lines
54 KiB
C
#define SUPPRESS_COMPILER_INLINES
|
|
#include "std.h"
|
|
#include "lpc_incl.h"
|
|
#include "file_incl.h"
|
|
#include "backend.h"
|
|
#include "simul_efun.h"
|
|
#include "compiler.h"
|
|
#include "otable.h"
|
|
#include "comm.h"
|
|
#include "socket_efuns.h"
|
|
#include "md.h"
|
|
#include "eoperators.h"
|
|
#include "ed.h"
|
|
#include "file.h"
|
|
#include "packages/db.h"
|
|
#include "packages/parser.h"
|
|
#include "packages/async.h"
|
|
#include "master.h"
|
|
#include "add_action.h"
|
|
#include "object.h"
|
|
#include "eval.h"
|
|
#ifdef DTRACE
|
|
#include <sys/sdt.h>
|
|
#else
|
|
#define DTRACE_PROBE1(x,y,z)
|
|
#endif
|
|
|
|
/*
|
|
* 'inherit_file' is used as a flag. If it is set to a string
|
|
* after yyparse(), this string should be loaded as an object,
|
|
* and the original object must be loaded again.
|
|
*/
|
|
char *inherit_file;
|
|
|
|
/* prevents infinite inherit loops.
|
|
No, mark-and-sweep solution won't work. Exercise for reader. */
|
|
static int num_objects_this_thread = 0;
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
static object_t *restrict_destruct;
|
|
#endif
|
|
|
|
object_t *obj_list, *obj_list_destruct;
|
|
#ifdef DEBUG
|
|
int tot_dangling_object = 0;
|
|
object_t *obj_list_dangling = 0;
|
|
#endif
|
|
object_t *current_object; /* The object interpreting a function. */
|
|
object_t *command_giver; /* Where the current command came from. */
|
|
object_t *current_interactive; /* The user who caused this execution */
|
|
|
|
#ifdef PRIVS
|
|
static void init_privs_for_object (object_t *);
|
|
#endif
|
|
#ifdef PACKAGE_UIDS
|
|
static int give_uid_to_object (object_t *);
|
|
#endif
|
|
static int init_object (object_t *);
|
|
static object_t *load_virtual_object (const char *, int);
|
|
static char *make_new_name (const char *);
|
|
#ifndef NO_ENVIRONMENT
|
|
static void send_say (object_t *, const char *, array_t *);
|
|
#endif
|
|
|
|
INLINE void check_legal_string (const char * s)
|
|
{
|
|
if (strlen(s) > LARGEST_PRINTABLE_STRING) {
|
|
error("Printable strings limited to length of %d.\n",
|
|
LARGEST_PRINTABLE_STRING);
|
|
}
|
|
}
|
|
|
|
/* equivalent to strcpy(x, y); return x + strlen(y), but faster and safer */
|
|
/* Code like:
|
|
*
|
|
* char buf[256];
|
|
* strcpy(buf, ...);
|
|
* strcat(buf, ...);
|
|
* strcat(buf, ...);
|
|
*
|
|
* Should be replaced with:
|
|
*
|
|
* char buf[256];
|
|
* char *p, *end = EndOf(buf);
|
|
* p = strput(buf, end, ...);
|
|
* p = strput(p, end, ...);
|
|
* p = strput(p, end, ...);
|
|
*/
|
|
char *strput (char * x, char * limit, const char * y) {
|
|
while ((*x++ = *y++)) {
|
|
if (x == limit) {
|
|
*(x-1) = 0;
|
|
break;
|
|
}
|
|
}
|
|
return x - 1;
|
|
}
|
|
|
|
char *strput_int (char * x, char * limit, int num) {
|
|
char buf[20];
|
|
sprintf(buf, "%d", num);
|
|
return strput(x, limit, buf);
|
|
}
|
|
|
|
#ifdef PRIVS
|
|
static void
|
|
init_privs_for_object (object_t * ob)
|
|
{
|
|
svalue_t *value;
|
|
|
|
if (!current_object
|
|
#ifdef PACKAGE_UIDS
|
|
|| !current_object->uid
|
|
#endif
|
|
) {
|
|
ob->privs = NULL;
|
|
return;
|
|
}
|
|
push_malloced_string(add_slash(ob->obname));
|
|
|
|
if (master_ob)
|
|
value = apply_master_ob(APPLY_PRIVS_FILE, 1);
|
|
else
|
|
value = apply(applies_table[APPLY_PRIVS_FILE], ob, 1, ORIGIN_DRIVER);
|
|
|
|
if (value == NULL || value->type != T_STRING)
|
|
ob->privs = NULL;
|
|
else
|
|
ob->privs = make_shared_string(value->u.string);
|
|
}
|
|
#endif /* PRIVS */
|
|
|
|
/*
|
|
* Give the correct uid and euid to a created object.
|
|
*/
|
|
#ifdef PACKAGE_UIDS
|
|
static int give_uid_to_object (object_t * ob)
|
|
{
|
|
svalue_t *ret;
|
|
const char *creator_name;
|
|
|
|
if (!master_ob) {
|
|
ob->uid = add_uid("NONAME");
|
|
#ifdef AUTO_SETEUID
|
|
ob->euid = ob->uid;
|
|
#else
|
|
ob->euid = NULL;
|
|
#endif
|
|
return 1;
|
|
}
|
|
/*
|
|
* Ask master object who the creator of this object is.
|
|
*/
|
|
push_malloced_string(add_slash(ob->obname));
|
|
|
|
ret = apply_master_ob(APPLY_CREATOR_FILE, 1);
|
|
if (!ret)
|
|
error("master object: No function %s() defined!\n",
|
|
applies_table[APPLY_CREATOR_FILE]);
|
|
if (!ret || ret == (svalue_t *)-1 || ret->type != T_STRING) {
|
|
destruct_object(ob);
|
|
if (!ret) error("Master object has no function %s().\n", applies_table[APPLY_CREATOR_FILE]);
|
|
if (ret == (svalue_t *)-1) error("Can't load objects without a master object.");
|
|
error("Illegal object to load: return value of master::%s() was not a string.\n", applies_table[APPLY_CREATOR_FILE]);
|
|
}
|
|
creator_name = ret->u.string;
|
|
/*
|
|
* Now we are sure that we have a creator name. Do not call apply()
|
|
* again, because creator_name will be lost !
|
|
*/
|
|
if (strcmp(current_object->uid->name, creator_name) == 0) {
|
|
/*
|
|
* The loaded object has the same uid as the loader.
|
|
*/
|
|
#ifndef COMPAT_32
|
|
ob->uid = current_object->uid;
|
|
#else
|
|
ob->uid = current_object->euid;
|
|
#endif
|
|
ob->euid = current_object->euid;
|
|
return 1;
|
|
}
|
|
#ifdef AUTO_TRUST_BACKBONE
|
|
if (strcmp(backbone_uid->name, creator_name) == 0) {
|
|
/*
|
|
* The object is loaded from backbone. This is trusted, so we let it
|
|
* inherit the value of eff_user.
|
|
*/
|
|
ob->uid = current_object->euid;
|
|
ob->euid = current_object->euid;
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The object is not loaded from backbone, nor from from the loading
|
|
* objects path. That should be an object defined by another wizard. It
|
|
* can't be trusted, so we give it the same uid as the creator. Also give
|
|
* it eff_user 0, which means that user 'a' can't use objects from user
|
|
* 'b' to load new objects nor modify files owned by user 'b'.
|
|
*
|
|
* If this effect is wanted, user 'b' must let his object do 'seteuid()' to
|
|
* himself. That is the case for most rooms.
|
|
*/
|
|
ob->uid = add_uid(creator_name);
|
|
#ifdef AUTO_SETEUID
|
|
ob->euid = ob->uid;
|
|
#else
|
|
ob->euid = NULL;
|
|
#endif
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static int init_object (object_t * ob)
|
|
{
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
init_stats_for_object(ob);
|
|
#endif
|
|
#ifdef PRIVS
|
|
init_privs_for_object(ob);
|
|
#endif /* PRIVS */
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
add_objects(&ob->stats, 1);
|
|
#endif
|
|
#ifdef NO_ADD_ACTION
|
|
if (function_exists(APPLY_CATCH_TELL, ob, 1) ||
|
|
function_exists(APPLY_RECEIVE_MESSAGE, ob, 1))
|
|
ob->flags |= O_LISTENER;
|
|
#endif
|
|
#ifdef PACKAGE_UIDS
|
|
return give_uid_to_object(ob);
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
|
|
static object_t *load_virtual_object (const char * name, int clone)
|
|
{
|
|
int argc = 2;
|
|
char *new_name;
|
|
object_t *new_ob, *ob;
|
|
array_t *args = 0;
|
|
svalue_t *v;
|
|
|
|
if (!master_ob) {
|
|
if (clone)
|
|
pop_n_elems(clone - 1);
|
|
return 0;
|
|
}
|
|
|
|
if (clone > 1) {
|
|
args = allocate_empty_array(clone - 1);
|
|
while (clone-- > 1)
|
|
args->item[clone - 1] = *sp--;
|
|
argc++;
|
|
clone = 1;
|
|
}
|
|
push_malloced_string(add_slash(name));
|
|
push_number(clone);
|
|
if (args)
|
|
push_refed_array(args);
|
|
v = apply_master_ob(APPLY_COMPILE_OBJECT, argc);
|
|
if (!v || (v->type != T_OBJECT))
|
|
return 0;
|
|
new_ob = v->u.ob;
|
|
|
|
if (!clone) {
|
|
ob = lookup_object_hash(name);
|
|
if (ob && ob != new_ob) {
|
|
/*
|
|
* If we rename, we're going to have a duplicate name here. Don't
|
|
* allow this. Destruct the new object and raise an error.
|
|
*/
|
|
destruct_object(new_ob);
|
|
error("Virtual object name duplicates an existing object name.\n");
|
|
}
|
|
/* Make sure O_CLONE is NOT set */
|
|
new_ob->flags &= ~O_CLONE;
|
|
new_name = alloc_cstring(name, "load_virtual_object");
|
|
SET_TAG(new_name, TAG_OBJ_NAME);
|
|
} else {
|
|
/* Make sure O_CLONE is set */
|
|
new_ob->flags |= O_CLONE;
|
|
new_name = make_new_name(name);
|
|
}
|
|
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
add_objects(&new_ob->stats, -1);
|
|
#endif
|
|
|
|
/* perform the object rename */
|
|
remove_object_hash(new_ob);
|
|
if (new_ob->obname)
|
|
FREE((char *)new_ob->obname);
|
|
SETOBNAME(new_ob, new_name);
|
|
enter_object_hash(new_ob);
|
|
|
|
/* finish initialization */
|
|
new_ob->flags |= O_VIRTUAL;
|
|
new_ob->load_time = current_time;
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
init_stats_for_object(new_ob);
|
|
add_objects(&new_ob->stats, 1);
|
|
#endif
|
|
#ifdef PRIVS
|
|
if (new_ob->privs)
|
|
free_string(new_ob->privs);
|
|
init_privs_for_object(new_ob);
|
|
#endif
|
|
#ifdef PACKAGE_UIDS
|
|
/* reassign uid */
|
|
give_uid_to_object(new_ob);
|
|
#endif
|
|
return new_ob;
|
|
}
|
|
|
|
int strip_name (const char * src, char * dest, int size) {
|
|
char last_c = 0;
|
|
char *p = dest;
|
|
char *end = dest + size - 1;
|
|
|
|
while (*src == '/') src++;
|
|
|
|
while (*src && p < end) {
|
|
if (last_c == '/' && *src == '/') src++;
|
|
else last_c = (*p++ = *src++);
|
|
}
|
|
|
|
/* In some cases, (for example, object loading) this currently gets
|
|
* run twice, once in find_object, and once in load object. The
|
|
* net effect of this is:
|
|
* /foo.c -> /foo [no such exists, try to load] -> /foo created
|
|
* /foo.c.c -> /foo.c [no such exists, try to load] -> /foo created
|
|
*
|
|
* causing a duplicate object crash. There are two ways to fix this:
|
|
* (1) strip multiple .c's so that the output of this routine is something
|
|
* that doesn't change if this is run again.
|
|
* (2) make sure this routine is only called once on any name.
|
|
*
|
|
* The first solution is the one currently in use.
|
|
*/
|
|
while (p - dest > 2 && p[-1] == 'c' && p[-2] == '.')
|
|
p -= 2;
|
|
|
|
*p = 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Load an object definition from file. If the object wants to inherit
|
|
* from an object that is not loaded, discard all, load the inherited object,
|
|
* and reload again.
|
|
*
|
|
* In mudlib3.0 when loading inherited objects, their reset() is not called.
|
|
* - why is this?? it makes no sense and causes a problem when a developer
|
|
* inherits code from a real used item. Say a room for example. In this case
|
|
* the room is loaded but is never set up properly, so when someone enters it
|
|
* it's all messed up. Realistically, I know that it's pretty bad style to
|
|
* inherit from an object that's actually being used and isn't just a building
|
|
* block, but I see no reason for this limitation. It happens, and when it
|
|
* does occur, produces mysterious results than can be hard to track down.
|
|
* for now, I've reenabled resetting. We'll see if anything breaks. -WF
|
|
*
|
|
* Save the command_giver, because reset() in the new object might change
|
|
* it.
|
|
*
|
|
*/
|
|
object_t *int_load_object (const char * lname)
|
|
{
|
|
int f;
|
|
program_t *prog;
|
|
object_t *ob;
|
|
svalue_t *mret;
|
|
struct stat c_st;
|
|
char real_name[400], name[400], actualname[400], obname[400];
|
|
|
|
const char *pname = check_valid_path(lname, master_ob, "load_object", 0);
|
|
if(!pname)
|
|
error("Read access denied.\n");
|
|
if (++num_objects_this_thread > INHERIT_CHAIN_SIZE)
|
|
error("Inherit chain too deep: > %d when trying to load '%s'.\n", INHERIT_CHAIN_SIZE, lname);
|
|
#ifdef PACKAGE_UIDS
|
|
if (current_object && current_object->euid == NULL)
|
|
error("Can't load objects when no effective user.\n");
|
|
#endif
|
|
if (strrchr(pname, '#'))
|
|
error("Cannot load a clone.\n");
|
|
if (!strip_name(lname, name, sizeof name))
|
|
error("Filenames with consecutive /'s in them aren't allowed (%s).\n",
|
|
lname);
|
|
if (!strip_name(pname, actualname, sizeof actualname))
|
|
error("Filenames with consecutive /'s in them aren't allowed (%s).\n",
|
|
pname);
|
|
|
|
/*
|
|
* First check that the c-file exists.
|
|
*/
|
|
(void) strcpy(real_name, actualname);
|
|
(void) strcat(real_name, ".c");
|
|
|
|
(void) strcpy(obname, name);
|
|
(void) strcat(obname, ".c");
|
|
|
|
if (stat(real_name, &c_st) == -1 || S_ISDIR(c_st.st_mode)) {
|
|
save_command_giver(command_giver);
|
|
ob = load_virtual_object(actualname, 0);
|
|
restore_command_giver();
|
|
num_objects_this_thread--;
|
|
return ob;
|
|
}
|
|
/*
|
|
* Check if it's a legal name.
|
|
*/
|
|
if (!legal_path(real_name)) {
|
|
debug_message("Illegal pathname: /%s\n", real_name);
|
|
error("Illegal path name '/%s'.\n", real_name);
|
|
}
|
|
|
|
/* maybe move this section into compile_file? */
|
|
if (comp_flag) {
|
|
debug_message(" compiling /%s ...", real_name);
|
|
}
|
|
f = open(real_name, O_RDONLY);
|
|
if (f == -1) {
|
|
debug_perror("compile_file", real_name);
|
|
error("Could not read the file '/%s'.\n", real_name);
|
|
}
|
|
save_command_giver(command_giver);
|
|
prog = compile_file(f, obname);
|
|
restore_command_giver();
|
|
if (comp_flag)
|
|
debug_message(" done\n");
|
|
update_compile_av(total_lines);
|
|
total_lines = 0;
|
|
close(f);
|
|
|
|
|
|
/* Sorry, can't handle objects without programs yet. */
|
|
if (inherit_file == 0 && (num_parse_error > 0 || prog == 0)) {
|
|
if (num_parse_error == 0 && prog == 0)
|
|
error("No program in object '/%s'!\n", name);
|
|
|
|
if (prog) {
|
|
free_prog(&prog);
|
|
}
|
|
error("Error in loading object '/%s'\n", name);
|
|
}
|
|
/*
|
|
* This is an iterative process. If this object wants to inherit an
|
|
* unloaded object, then discard current object, load the object to be
|
|
* inherited and reload the current object again. The global variable
|
|
* "inherit_file" will be set by lang.y to point to a file name.
|
|
*/
|
|
if (inherit_file) {
|
|
object_t *inh_obj;
|
|
char inhbuf[MAX_OBJECT_NAME_SIZE];
|
|
|
|
if (!strip_name(inherit_file, inhbuf, sizeof inhbuf))
|
|
strcpy(inhbuf, inherit_file);
|
|
FREE(inherit_file);
|
|
inherit_file = 0;
|
|
|
|
if (prog) {
|
|
free_prog(&prog);
|
|
prog = 0;
|
|
}
|
|
if (strcmp(inhbuf, name) == 0) {
|
|
error("Illegal to inherit self.\n");
|
|
}
|
|
|
|
if ((inh_obj = lookup_object_hash(inhbuf))) {
|
|
IF_DEBUG(fatal("Inherited object is already loaded!"));
|
|
} else {
|
|
inh_obj = load_object(inhbuf, 0);
|
|
}
|
|
if (!inh_obj) error("Inherited file '/%s' does not exist!\n",
|
|
inhbuf);
|
|
|
|
/*
|
|
* Yes, the following is necessary. It is possible that when we
|
|
* loaded the inherited object, it loaded this object from it's
|
|
* create function. Without this check, that would crash the driver.
|
|
* -Beek
|
|
*/
|
|
if (!(ob = lookup_object_hash(name))) {
|
|
ob = load_object(name, 0);
|
|
/* sigh, loading the inherited file removed us */
|
|
if (!ob) {
|
|
num_objects_this_thread--;
|
|
return 0;
|
|
}
|
|
ob->load_time = current_time;
|
|
}
|
|
num_objects_this_thread--;
|
|
return ob;
|
|
}
|
|
ob = get_empty_object(prog->num_variables_total);
|
|
/* Shared string is no good here */
|
|
SETOBNAME(ob, alloc_cstring(name, "load_object"));
|
|
SET_TAG(ob->obname, TAG_OBJ_NAME);
|
|
ob->prog = prog;
|
|
ob->flags |= O_WILL_RESET; /* must be before reset is first called */
|
|
ob->next_all = obj_list;
|
|
ob->prev_all = 0;
|
|
if(obj_list)
|
|
obj_list->prev_all = ob;
|
|
obj_list = ob;
|
|
enter_object_hash(ob); /* add name to fast object lookup table */
|
|
save_command_giver(command_giver);
|
|
push_object(ob);
|
|
mret = apply_master_ob(APPLY_VALID_OBJECT, 1);
|
|
if (mret && !MASTER_APPROVED(mret)) {
|
|
destruct_object(ob);
|
|
error("master object: %s() denied permission to load '/%s'.\n", applies_table[APPLY_VALID_OBJECT], name);
|
|
}
|
|
|
|
if (init_object(ob))
|
|
call_create(ob, 0);
|
|
if (!(ob->flags & O_DESTRUCTED) &&
|
|
function_exists(APPLY_CLEAN_UP, ob, 1)) {
|
|
ob->flags |= O_WILL_CLEAN_UP;
|
|
}
|
|
restore_command_giver();
|
|
|
|
if (ob)
|
|
debug(d_flag, ("--/%s loaded", ob->obname));
|
|
|
|
ob->load_time = current_time;
|
|
num_objects_this_thread--;
|
|
|
|
return ob;
|
|
}
|
|
|
|
static char *make_new_name (const char * str)
|
|
{
|
|
static unsigned int i;
|
|
char *p = (char *)DXALLOC(strlen(str) + 12, TAG_OBJ_NAME, "make_new_name");
|
|
|
|
(void) sprintf(p, "%s#%u", str, i);
|
|
i++;
|
|
return p;
|
|
}
|
|
|
|
|
|
/*
|
|
* Save the command_giver, because reset() in the new object might change
|
|
* it.
|
|
*/
|
|
object_t *clone_object (const char * str1, int num_arg)
|
|
{
|
|
object_t *ob, *new_ob;
|
|
|
|
#ifdef PACKAGE_UIDS
|
|
if (current_object && current_object->euid == 0) {
|
|
error("Object must call seteuid() prior to calling clone_object().\n");
|
|
}
|
|
#endif
|
|
|
|
save_command_giver(command_giver);
|
|
|
|
num_objects_this_thread = 0;
|
|
ob = find_object(str1);
|
|
if (ob && !object_visible(ob))
|
|
ob = 0;
|
|
/*
|
|
* If the object self-destructed...
|
|
*/
|
|
if (ob == 0) { /* fix from 3.1.1 */
|
|
restore_command_giver();
|
|
pop_n_elems(num_arg);
|
|
return (0);
|
|
}
|
|
|
|
if (ob->flags & O_CLONE)
|
|
error("Cannot clone from a clone\n");
|
|
|
|
if(ob->flags & O_VIRTUAL) {
|
|
new_ob = load_virtual_object(ob->obname, 1 + num_arg);
|
|
restore_command_giver();
|
|
return new_ob;
|
|
/*
|
|
* we can skip all of the stuff below since we were already
|
|
* cloned once to have gotten to this stage.
|
|
*/
|
|
}
|
|
|
|
/* We do not want the heart beat to be running for unused copied objects */
|
|
if (ob->flags & O_HEART_BEAT)
|
|
(void) set_heart_beat(ob, 0);
|
|
new_ob = get_empty_object(ob->prog->num_variables_total);
|
|
SETOBNAME(new_ob, make_new_name(ob->obname));
|
|
new_ob->flags |= (O_CLONE | (ob->flags & (O_WILL_CLEAN_UP | O_WILL_RESET)));
|
|
new_ob->load_time = ob->load_time;
|
|
new_ob->prog = ob->prog;
|
|
reference_prog(ob->prog, "clone_object");
|
|
DEBUG_CHECK(!current_object, "clone_object() from no current_object !\n");
|
|
|
|
new_ob->next_all = obj_list;
|
|
obj_list->prev_all = new_ob;
|
|
new_ob->prev_all = 0;
|
|
obj_list = new_ob;
|
|
enter_object_hash(new_ob); /* Add name to fast object lookup table */
|
|
|
|
init_object(new_ob);
|
|
|
|
call_create(new_ob, num_arg);
|
|
restore_command_giver();
|
|
/* Never know what can happen ! :-( */
|
|
if (new_ob->flags & O_DESTRUCTED)
|
|
return (0);
|
|
return (new_ob);
|
|
}
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
object_t *environment (svalue_t * arg)
|
|
{
|
|
object_t *ob = current_object;
|
|
|
|
if (arg && arg->type == T_OBJECT)
|
|
ob = arg->u.ob;
|
|
if (ob == 0 || ob->super == 0 || (ob->flags & O_DESTRUCTED))
|
|
return 0;
|
|
if (ob->flags & O_DESTRUCTED)
|
|
error("environment() of destructed object.\n");
|
|
return ob->super;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* With no argument, present() looks in the inventory of the current_object,
|
|
* the inventory of our super, and our super.
|
|
* If the second argument is nonzero, only the inventory of that object
|
|
* is searched.
|
|
*/
|
|
|
|
|
|
#ifdef F_PRESENT
|
|
static object_t *object_present2 (const char *, object_t *);
|
|
|
|
object_t *object_present (svalue_t * v, object_t * ob)
|
|
{
|
|
svalue_t *ret;
|
|
object_t *ret_ob;
|
|
int specific = 0;
|
|
|
|
if (ob == 0)
|
|
ob = current_object;
|
|
else
|
|
specific = 1;
|
|
if (ob->flags & O_DESTRUCTED)
|
|
return 0;
|
|
if (v->type == T_OBJECT) {
|
|
if (specific) {
|
|
if (v->u.ob->super == ob)
|
|
return v->u.ob;
|
|
else
|
|
return 0;
|
|
}
|
|
if (v->u.ob->super == ob ||
|
|
(v->u.ob->super == ob->super && ob->super != 0))
|
|
return v->u.ob->super;
|
|
return 0;
|
|
}
|
|
ret_ob = object_present2(v->u.string, ob->contains);
|
|
if (ret_ob)
|
|
return ret_ob;
|
|
if (specific)
|
|
return 0;
|
|
if (ob->super) {
|
|
push_svalue(v);
|
|
ret = apply(APPLY_ID, ob->super, 1, ORIGIN_DRIVER);
|
|
if (ob->super->flags & O_DESTRUCTED)
|
|
return 0;
|
|
if (!IS_ZERO(ret)) {
|
|
return ob->super;
|
|
}
|
|
return object_present2(v->u.string, ob->super->contains);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static object_t *object_present2 (const char * str, object_t * ob)
|
|
{
|
|
svalue_t *ret;
|
|
const char *p;
|
|
int count = 0, length;
|
|
|
|
length = strlen(str);
|
|
|
|
if (length) {
|
|
p = str + length - 1;
|
|
if (uisdigit(*p)) {
|
|
do {
|
|
p--;
|
|
} while (p > str && uisdigit(*p));
|
|
|
|
if (*p == ' ') {
|
|
count = atoi(p + 1) - 1;
|
|
length = p - str;
|
|
}
|
|
}
|
|
}
|
|
for (; ob; ob = ob->next_inv) {
|
|
char *np;
|
|
np = new_string(length, "object_present2");
|
|
memcpy(np, str, length);
|
|
np[length] = 0;
|
|
push_malloced_string(np);
|
|
ret = apply(APPLY_ID, ob, 1, ORIGIN_DRIVER);
|
|
if (ob->flags & O_DESTRUCTED)
|
|
return 0;
|
|
if (IS_ZERO(ret))
|
|
continue;
|
|
if (count-- > 0)
|
|
continue;
|
|
return ob;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const char *saved_master_name;
|
|
static const char *saved_simul_name;
|
|
|
|
static void fix_object_names() {
|
|
SETOBNAME(master_ob, saved_master_name);
|
|
SETOBNAME(simul_efun_ob, saved_simul_name);
|
|
}
|
|
|
|
/*
|
|
* Remove an object. It is first moved into the destruct list, and
|
|
* not really destructed until later. (see destruct2()).
|
|
*/
|
|
void destruct_object (object_t * ob)
|
|
{
|
|
object_t **pp;
|
|
//int removed;
|
|
#ifndef NO_ENVIRONMENT
|
|
object_t *super;
|
|
object_t *save_restrict_destruct = restrict_destruct;
|
|
|
|
if (restrict_destruct && restrict_destruct != ob)
|
|
error("Only this_object() can be destructed from move_or_destruct.\n");
|
|
#endif
|
|
|
|
#if defined(PACKAGE_SOCKETS) || defined(PACKAGE_EXTERNAL)
|
|
/*
|
|
* check if object has an efun socket referencing it for a callback. if
|
|
* so, close the efun socket.
|
|
*/
|
|
if (ob->flags & O_EFUN_SOCKET) {
|
|
close_referencing_sockets(ob);
|
|
}
|
|
#endif
|
|
#ifdef PACKAGE_PARSER
|
|
if (ob->pinfo) {
|
|
parse_free(ob->pinfo);
|
|
ob->pinfo = 0;
|
|
}
|
|
#endif
|
|
|
|
if (ob->flags & O_DESTRUCTED) {
|
|
return;
|
|
}
|
|
remove_object_from_stack(ob);
|
|
/*
|
|
* If this is the first object being shadowed by another object, then
|
|
* destruct the whole list of shadows.
|
|
*/
|
|
#ifndef NO_SHADOWS
|
|
if (ob->shadowed && !ob->shadowing) {
|
|
object_t *otmp;
|
|
object_t *ob2;
|
|
|
|
/*
|
|
* move from bottom to top of shadow chain
|
|
*/
|
|
for (ob2 = ob->shadowed; ob2->shadowed; ob2 = ob2->shadowed);
|
|
/*
|
|
* remove shadows top...down being careful to unlink shadow being
|
|
* destructed from chain
|
|
*/
|
|
for (; ob2;) {
|
|
otmp = ob2;
|
|
ob2 = ob2->shadowing;
|
|
if (ob2)
|
|
ob2->shadowed = 0;
|
|
otmp->shadowing = 0;
|
|
destruct_object(otmp);
|
|
}
|
|
return;
|
|
}
|
|
/*
|
|
* The chain of shadows is a double linked list. Take care to update it
|
|
* correctly.
|
|
*/
|
|
if (ob->shadowing)
|
|
ob->shadowing->shadowed = ob->shadowed;
|
|
if (ob->shadowed)
|
|
ob->shadowed->shadowing = ob->shadowing;
|
|
ob->shadowing = 0;
|
|
ob->shadowed = 0;
|
|
#endif
|
|
|
|
debug(d_flag, ("Deobject_t /%s (ref %d)", ob->obname, ob->ref));
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
/* try to move our contents somewhere */
|
|
super = ob->super;
|
|
|
|
while (ob->contains) {
|
|
object_t *otmp;
|
|
|
|
otmp = ob->contains;
|
|
/*
|
|
* An error here will not leave destruct() in an inconsistent
|
|
* stage.
|
|
*/
|
|
push_object(super);
|
|
|
|
restrict_destruct = ob->contains;
|
|
(void)apply(APPLY_MOVE, ob->contains, 1, ORIGIN_DRIVER);
|
|
restrict_destruct = save_restrict_destruct;
|
|
/* OUCH! we could be dested by this. -Beek */
|
|
if (ob->flags & O_DESTRUCTED) return;
|
|
if (otmp == ob->contains)
|
|
destruct_object(otmp);
|
|
}
|
|
#endif
|
|
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
add_objects(&ob->stats, -1);
|
|
#endif
|
|
#ifdef OLD_ED
|
|
if (ob->interactive && ob->interactive->ed_buffer)
|
|
save_ed_buffer(ob);
|
|
#else
|
|
if (ob->flags & O_IN_EDIT) {
|
|
object_save_ed_buffer(ob);
|
|
ob->flags &= ~O_IN_EDIT;
|
|
}
|
|
#endif
|
|
#ifndef NO_SNOOP
|
|
if (ob->flags & O_SNOOP) {
|
|
int i;
|
|
for (i = 0; i < max_users; i++) {
|
|
if (all_users[i] && all_users[i]->snooped_by == ob)
|
|
all_users[i]->snooped_by = 0;
|
|
}
|
|
ob->flags &= ~O_SNOOP;
|
|
}
|
|
#endif
|
|
#ifndef NO_ENVIRONMENT
|
|
/*
|
|
* Remove us out of this current room (if any). Remove all sentences
|
|
* defined by this object from all objects here.
|
|
*/
|
|
if (ob->super) {
|
|
#ifndef NO_LIGHT
|
|
add_light(ob->super, -ob->total_light);
|
|
#endif
|
|
remove_sent(ob->super, ob);
|
|
remove_sent(ob, ob->super);
|
|
for (pp = &ob->super->contains; *pp;) {
|
|
remove_sent(*pp, ob);
|
|
if (*pp != ob) {
|
|
remove_sent(ob, *pp);
|
|
pp = &(*pp)->next_inv;
|
|
} else {
|
|
*pp = (*pp)->next_inv;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* At this point, we can still back out, but this is the very last
|
|
* minute we can do so. Make sure we have a new object to replace
|
|
* us if this is a vital object.
|
|
*/
|
|
if (ob == master_ob || ob == simul_efun_ob) {
|
|
object_t *new_ob, *tmp_ob;
|
|
const char *tmp = ob->obname;
|
|
|
|
STACK_INC;
|
|
sp->type = T_ERROR_HANDLER;
|
|
sp->u.error_handler = fix_object_names;
|
|
saved_master_name = master_ob->obname;
|
|
saved_simul_name = simul_efun_ob->obname;
|
|
|
|
/* hack to make sure we don't find ourselves at several points
|
|
in the following process */
|
|
SETOBNAME(ob, "");
|
|
|
|
/* handle these two carefully, since they are rather vital */
|
|
new_ob = load_object(tmp, compiled_version);
|
|
if (!new_ob) {
|
|
SETOBNAME(ob, tmp);
|
|
sp--;
|
|
error("Destruct on vital object failed: new copy failed to reload.");
|
|
}
|
|
|
|
if (ob == master_ob)
|
|
set_master(new_ob);
|
|
if (ob == simul_efun_ob)
|
|
set_simul_efun(new_ob);
|
|
|
|
/* Set the name back so we can remove it from the hash table.
|
|
Also be careful not to remove the new object, which has
|
|
the same name. */
|
|
sp--; /* error handler */
|
|
SETOBNAME(ob, tmp);
|
|
tmp = new_ob->obname;
|
|
SETOBNAME(new_ob, "");
|
|
remove_object_hash(ob);
|
|
SETOBNAME(new_ob, tmp);
|
|
tmp_ob = ob;
|
|
free_object(&tmp_ob, "vital object reference");
|
|
// still need ob below!
|
|
} else
|
|
remove_object_hash(ob);
|
|
|
|
/*
|
|
* Now remove us out of the list of all objects. This must be done last,
|
|
* because an error in the above code would halt execution.
|
|
*/
|
|
//removed = 0;
|
|
if(ob->prev_all){
|
|
ob->prev_all->next_all = ob->next_all;
|
|
if(ob->next_all)
|
|
ob->next_all->prev_all = ob->prev_all;
|
|
}else{
|
|
obj_list = ob->next_all;
|
|
obj_list->prev_all = 0;
|
|
}
|
|
/*
|
|
for (pp = &obj_list; *pp; pp = &(*pp)->next_all) {
|
|
if (*pp != ob)
|
|
continue;
|
|
*pp = (*pp)->next_all;
|
|
removed = 1;
|
|
break;
|
|
}
|
|
DEBUG_CHECK(!removed, "Failed to delete object.\n");//*/
|
|
|
|
remove_living_name(ob);
|
|
#ifndef NO_ENVIRONMENT
|
|
ob->super = 0;
|
|
ob->next_inv = 0;
|
|
ob->contains = 0;
|
|
#endif
|
|
ob->next_all = obj_list_destruct;
|
|
if(obj_list_destruct)
|
|
obj_list_destruct->prev_all = ob;
|
|
ob->prev_all = 0;
|
|
obj_list_destruct = ob;
|
|
set_heart_beat(ob, 0);
|
|
ob->flags |= O_DESTRUCTED;
|
|
/* moved this here from destruct2() -- see comments in destruct2() */
|
|
if (ob->interactive)
|
|
remove_interactive(ob, 1);
|
|
#ifdef F_SET_HIDE
|
|
if (ob->flags & O_HIDDEN)
|
|
num_hidden--;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* This one is called when no program is executing from the main loop.
|
|
*/
|
|
void destruct2 (object_t * ob)
|
|
{
|
|
#ifndef NO_ADD_ACTION
|
|
sentence_t *s;
|
|
#endif
|
|
|
|
debug(d_flag, ("Destruct-2 object /%s (ref %d)", ob->obname, ob->ref));
|
|
|
|
/*
|
|
* We must deallocate variables here, not in 'free_object()'. That is
|
|
* because one of the local variables may point to this object, and
|
|
* deallocation of this pointer will also decrease the reference count of
|
|
* this object. Otherwise, an object with a variable pointing to itself,
|
|
* would never be freed. Just in case the program in this object would
|
|
* continue to execute, change string and object variables into the
|
|
* number 0.
|
|
*/
|
|
if (ob->prog->num_variables_total > 0) {
|
|
/*
|
|
* Deallocate variables in this object. The space of the variables
|
|
* are not deallocated until the object structure is freed in
|
|
* free_object().
|
|
*/
|
|
int i;
|
|
|
|
for (i = 0; i < ob->prog->num_variables_total; i++) {
|
|
free_svalue(&ob->variables[i], "destruct2");
|
|
ob->variables[i] = const0u;
|
|
}
|
|
}
|
|
|
|
#ifndef NO_ADD_ACTION
|
|
/*
|
|
* For much the same reason as described above, we must remove sentences
|
|
* for this object. The reason is because the sentence callback could be
|
|
* a function pointer that is bound to this object and thus holds a
|
|
* reference to this object.
|
|
*/
|
|
for (s = ob->sent; s;) {
|
|
sentence_t *next;
|
|
|
|
next = s->next;
|
|
free_sentence(s);
|
|
s = next;
|
|
}
|
|
ob->sent = 0;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
tot_dangling_object++;
|
|
ob->next_all = obj_list_dangling;
|
|
if(obj_list_dangling)
|
|
obj_list_dangling->prev_all = ob;
|
|
obj_list_dangling = ob;
|
|
ob->prev_all = 0;
|
|
#endif
|
|
|
|
free_object(&ob, "destruct_object");
|
|
}
|
|
|
|
/*
|
|
* say() efun - send a message to:
|
|
* all objects in the inventory of the source,
|
|
* all objects in the same environment as the source,
|
|
* and the object surrounding the source.
|
|
*
|
|
* when there is no command_giver, current_object is used as the source,
|
|
* otherwise, command_giver is used.
|
|
*
|
|
* message never goes to objects in the avoid array, or the source itself.
|
|
*
|
|
* rewritten, bobf@metronet.com (Blackthorn) 9/6/93
|
|
*/
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
static void send_say (object_t * ob, const char * text, array_t * avoid)
|
|
{
|
|
int valid, j;
|
|
|
|
for (valid = 1, j = 0; j < avoid->size; j++) {
|
|
if (avoid->item[j].type != T_OBJECT)
|
|
continue;
|
|
if (avoid->item[j].u.ob == ob) {
|
|
valid = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
return;
|
|
|
|
tell_object(ob, text, strlen(text));
|
|
}
|
|
|
|
void say (svalue_t * v, array_t * avoid)
|
|
{
|
|
object_t *ob, *origin;
|
|
const char *buff;
|
|
|
|
check_legal_string(v->u.string);
|
|
buff = v->u.string;
|
|
|
|
if (current_object->flags & O_LISTENER || current_object->interactive)
|
|
save_command_giver(current_object);
|
|
else
|
|
save_command_giver(command_giver);
|
|
if (command_giver)
|
|
origin = command_giver;
|
|
else
|
|
origin = current_object;
|
|
|
|
/* To our surrounding object... */
|
|
if ((ob = origin->super)) {
|
|
if (ob->flags & O_LISTENER || ob->interactive)
|
|
send_say(ob, buff, avoid);
|
|
|
|
/* And its inventory... */
|
|
for (ob = origin->super->contains; ob; ob = ob->next_inv) {
|
|
if (ob != origin && (ob->flags & O_LISTENER || ob->interactive)) {
|
|
send_say(ob, buff, avoid);
|
|
if (ob->flags & O_DESTRUCTED)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Our inventory... */
|
|
for (ob = origin->contains; ob; ob = ob->next_inv) {
|
|
if (ob->flags & O_LISTENER || ob->interactive) {
|
|
send_say(ob, buff, avoid);
|
|
if (ob->flags & O_DESTRUCTED)
|
|
break;
|
|
}
|
|
}
|
|
|
|
restore_command_giver();
|
|
}
|
|
|
|
/*
|
|
* Sends a string to all objects inside of a specific object.
|
|
* Revised, bobf@metronet.com 9/6/93
|
|
*/
|
|
#ifdef F_TELL_ROOM
|
|
void tell_room (object_t * room, svalue_t * v, array_t * avoid)
|
|
{
|
|
object_t *ob;
|
|
const char *buff;
|
|
int valid, j;
|
|
char txt_buf[LARGEST_PRINTABLE_STRING + 1];
|
|
|
|
switch (v->type) {
|
|
case T_STRING:
|
|
check_legal_string(v->u.string);
|
|
buff = v->u.string;
|
|
break;
|
|
case T_OBJECT:
|
|
buff = v->u.ob->obname;
|
|
break;
|
|
case T_NUMBER:
|
|
buff = txt_buf;
|
|
sprintf(txt_buf, "%ld", v->u.number);
|
|
break;
|
|
case T_REAL:
|
|
buff = txt_buf;
|
|
sprintf(txt_buf, "%f", v->u.real);
|
|
break;
|
|
default:
|
|
bad_argument(v, T_OBJECT | T_NUMBER | T_REAL | T_STRING,
|
|
2, F_TELL_ROOM);
|
|
IF_DEBUG(buff = 0);
|
|
}
|
|
|
|
for (ob = room->contains; ob; ob = ob->next_inv) {
|
|
if (!ob->interactive && !(ob->flags & O_LISTENER))
|
|
continue;
|
|
|
|
for (valid = 1, j = 0; j < avoid->size; j++) {
|
|
if (avoid->item[j].type != T_OBJECT)
|
|
continue;
|
|
if (avoid->item[j].u.ob == ob) {
|
|
valid = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
continue;
|
|
|
|
if (!ob->interactive) {
|
|
tell_npc(ob, buff);
|
|
if (ob->flags & O_DESTRUCTED)
|
|
break;
|
|
} else {
|
|
tell_object(ob, buff, strlen(buff));
|
|
if (ob->flags & O_DESTRUCTED)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
void shout_string (const char * str)
|
|
{
|
|
object_t *ob;
|
|
|
|
check_legal_string(str);
|
|
|
|
for (ob = obj_list; ob; ob = ob->next_all) {
|
|
if (!(ob->flags & O_LISTENER) || (ob == command_giver)
|
|
#ifndef NO_ENVIRONMENT
|
|
|| !ob->super
|
|
#endif
|
|
)
|
|
continue;
|
|
tell_object(ob, str, strlen(str));
|
|
}
|
|
}
|
|
|
|
#ifdef F_INPUT_TO
|
|
/*
|
|
* Set up a function in this object to be called with the next
|
|
* user input string.
|
|
*/
|
|
int input_to (svalue_t * fun, int flag, int num_arg, svalue_t * args)
|
|
{
|
|
sentence_t *s;
|
|
svalue_t *x;
|
|
int i;
|
|
|
|
if (!command_giver || command_giver->flags & O_DESTRUCTED)
|
|
return 0;
|
|
s = alloc_sentence();
|
|
if (set_call(command_giver, s, flag & ~I_SINGLE_CHAR)) {
|
|
/*
|
|
* If we have args, we copy them, and adjust the stack automatically
|
|
* (elsewhere) to avoid double free_svalue()'s
|
|
*/
|
|
if (num_arg) {
|
|
i = num_arg * sizeof(svalue_t);
|
|
if ((x = (svalue_t *)
|
|
DMALLOC(i, TAG_INPUT_TO, "input_to: 1")) == NULL)
|
|
fatal("Out of memory!\n");
|
|
memcpy(x, args, i);
|
|
} else
|
|
x = NULL;
|
|
|
|
command_giver->interactive->carryover = x;
|
|
command_giver->interactive->num_carry = num_arg;
|
|
if (fun->type == T_STRING) {
|
|
s->function.s = make_shared_string(fun->u.string);
|
|
s->flags = 0;
|
|
} else {
|
|
s->function.f = fun->u.fp;
|
|
fun->u.fp->hdr.ref++;
|
|
s->flags = V_FUNCTION;
|
|
}
|
|
s->ob = current_object;
|
|
add_ref(current_object, "input_to");
|
|
return 1;
|
|
}
|
|
free_sentence(s);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Set up a function in this object to be called with the next
|
|
* user input character.
|
|
*/
|
|
#ifdef F_GET_CHAR
|
|
int get_char (svalue_t * fun, int flag, int num_arg, svalue_t * args)
|
|
{
|
|
sentence_t *s;
|
|
svalue_t *x;
|
|
int i;
|
|
|
|
if (!command_giver || command_giver->flags & O_DESTRUCTED)
|
|
return 0;
|
|
s = alloc_sentence();
|
|
if (set_call(command_giver, s, flag | I_SINGLE_CHAR)) {
|
|
/*
|
|
* If we have args, we copy them, and adjust the stack automatically
|
|
* (elsewhere) to avoid double free_svalue()'s
|
|
*/
|
|
if (num_arg) {
|
|
i = num_arg * sizeof(svalue_t);
|
|
if ((x = (svalue_t *)
|
|
DMALLOC(i, TAG_TEMPORARY, "get_char: 1")) == NULL)
|
|
fatal("Out of memory!\n");
|
|
memcpy(x, args, i);
|
|
} else
|
|
x = NULL;
|
|
|
|
command_giver->interactive->carryover = x;
|
|
command_giver->interactive->num_carry = num_arg;
|
|
if (fun->type == T_STRING) {
|
|
s->function.s = make_shared_string(fun->u.string);
|
|
s->flags = 0;
|
|
} else {
|
|
s->function.f = fun->u.fp;
|
|
fun->u.fp->hdr.ref++;
|
|
s->flags = V_FUNCTION;
|
|
}
|
|
s->ob = current_object;
|
|
add_ref(current_object, "get_char");
|
|
return 1;
|
|
}
|
|
free_sentence(s);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void print_svalue (svalue_t * arg)
|
|
{
|
|
char tbuf[2048];
|
|
int len;
|
|
|
|
if (arg == 0) {
|
|
tell_object(command_giver, "<NULL>", 6);
|
|
} else
|
|
switch (arg->type) {
|
|
case T_STRING:
|
|
len = SVALUE_STRLEN(arg);
|
|
if (len > LARGEST_PRINTABLE_STRING) {
|
|
error("Printable strings limited to length of %d.\n",
|
|
LARGEST_PRINTABLE_STRING);
|
|
}
|
|
|
|
tell_object(command_giver, arg->u.string, len);
|
|
break;
|
|
case T_OBJECT:
|
|
sprintf(tbuf, "OBJ(/%s)", arg->u.ob->obname);
|
|
tell_object(command_giver, tbuf, strlen(tbuf));
|
|
break;
|
|
case T_NUMBER:
|
|
sprintf(tbuf, "%ld", arg->u.number);
|
|
tell_object(command_giver, tbuf, strlen(tbuf));
|
|
break;
|
|
case T_REAL:
|
|
sprintf(tbuf, "%f", arg->u.real);
|
|
tell_object(command_giver, tbuf, strlen(tbuf));
|
|
break;
|
|
case T_ARRAY:
|
|
tell_object(command_giver, "<ARRAY>", strlen("<ARRAY>"));
|
|
break;
|
|
case T_MAPPING:
|
|
tell_object(command_giver, "<MAPPING>", strlen("<MAPPING>"));
|
|
break;
|
|
case T_FUNCTION:
|
|
tell_object(command_giver, "<FUNCTION>", strlen("<FUNCTION>"));
|
|
break;
|
|
#ifndef NO_BUFFER_TYPE
|
|
case T_BUFFER:
|
|
tell_object(command_giver, "<BUFFER>", strlen("<BUFFER>"));
|
|
break;
|
|
#endif
|
|
default:
|
|
tell_object(command_giver, "<UNKNOWN>", strlen("<UNKNOWN>"));
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
void do_write (svalue_t * arg)
|
|
{
|
|
object_t *ob = command_giver;
|
|
|
|
#ifndef NO_SHADOWS
|
|
if (ob == 0 && current_object->shadowing)
|
|
ob = current_object;
|
|
if (ob) {
|
|
/* Send the message to the first object in the shadow list */
|
|
while (ob->shadowing)
|
|
ob = ob->shadowing;
|
|
}
|
|
#else
|
|
if (!ob)
|
|
ob = current_object;
|
|
#endif /* NO_SHADOWS */
|
|
save_command_giver(ob);
|
|
print_svalue(arg);
|
|
restore_command_giver();
|
|
}
|
|
|
|
/* Find an object. If not loaded, load it !
|
|
* The object may selfdestruct, which is the only case when 0 will be
|
|
* returned.
|
|
*/
|
|
|
|
object_t *find_object (const char * str)
|
|
{
|
|
object_t *ob;
|
|
char tmpbuf[MAX_OBJECT_NAME_SIZE];
|
|
|
|
if (!strip_name(str, tmpbuf, sizeof tmpbuf))
|
|
return 0;
|
|
|
|
if ((ob = lookup_object_hash(tmpbuf))) {
|
|
return ob;
|
|
}
|
|
ob = load_object(tmpbuf, 0);
|
|
if (!ob || (ob->flags & O_DESTRUCTED)) /* *sigh* */
|
|
return 0;
|
|
return ob;
|
|
}
|
|
|
|
/* Look for a loaded object. Return 0 if non found. */
|
|
object_t *find_object2 (const char * str)
|
|
{
|
|
register object_t *ob;
|
|
char p[MAX_OBJECT_NAME_SIZE];
|
|
|
|
if (!strip_name(str, p, sizeof p))
|
|
return 0;
|
|
|
|
if ((ob = lookup_object_hash(p))) {
|
|
return ob;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
/*
|
|
* Transfer an object.
|
|
* The object has to be taken from one inventory list and added to another.
|
|
* The main work is to update all command definitions, depending on what is
|
|
* living or not. Note that all objects in the same inventory are affected.
|
|
*/
|
|
void move_object (object_t * item, object_t * dest)
|
|
{
|
|
object_t **pp, *ob;
|
|
|
|
save_command_giver(command_giver);
|
|
|
|
/* Recursive moves are not allowed. */
|
|
for (ob = dest; ob; ob = ob->super)
|
|
if (ob == item)
|
|
error("Can't move object inside itself.\n");
|
|
#ifndef NO_SHADOWS
|
|
if (item->shadowing)
|
|
error("Can't move an object that is shadowing.\n");
|
|
#endif
|
|
|
|
#if !defined(NO_RESETS) && defined(LAZY_RESETS)
|
|
try_reset(dest);
|
|
#endif
|
|
#ifndef NO_LIGHT
|
|
add_light(dest, item->total_light);
|
|
#endif
|
|
if (item->super) {
|
|
int okay = 0;
|
|
|
|
remove_sent(item->super, item);
|
|
remove_sent(item, item->super);
|
|
#ifndef NO_LIGHT
|
|
add_light(item->super, -item->total_light);
|
|
#endif
|
|
for (pp = &item->super->contains; *pp;) {
|
|
if (*pp != item) {
|
|
remove_sent(item, *pp);
|
|
remove_sent(*pp, item);
|
|
pp = &(*pp)->next_inv;
|
|
continue;
|
|
}
|
|
/*
|
|
* unlink object from original inventory list
|
|
*/
|
|
*pp = item->next_inv;
|
|
okay = 1;
|
|
}
|
|
#ifdef DEBUG
|
|
if (!okay)
|
|
fatal("Failed to find object /%s in super list of /%s.\n",
|
|
item->obname, item->super->obname);
|
|
#endif
|
|
}
|
|
/*
|
|
* link object into target's inventory list
|
|
*/
|
|
item->next_inv = dest->contains;
|
|
dest->contains = item;
|
|
item->super = dest;
|
|
|
|
setup_new_commands(dest, item);
|
|
restore_command_giver();
|
|
}
|
|
#endif
|
|
|
|
#ifndef NO_LIGHT
|
|
/*
|
|
* Every object has a count of the number of light sources it contains.
|
|
* Update this.
|
|
*/
|
|
|
|
void add_light (object_t * p, int n)
|
|
{
|
|
if (n == 0)
|
|
return;
|
|
p->total_light += n;
|
|
#ifndef NO_ENVIRONMENT
|
|
while ((p = p->super))
|
|
p->total_light += n;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static sentence_t *sent_free = 0;
|
|
int tot_alloc_sentence;
|
|
|
|
sentence_t *alloc_sentence()
|
|
{
|
|
sentence_t *p;
|
|
|
|
if (sent_free == 0) {
|
|
p = ALLOCATE(sentence_t, TAG_SENTENCE, "alloc_sentence");
|
|
tot_alloc_sentence++;
|
|
} else {
|
|
p = sent_free;
|
|
sent_free = sent_free->next;
|
|
}
|
|
#ifndef NO_ADD_ACTION
|
|
p->verb = 0;
|
|
#endif
|
|
p->function.s = 0;
|
|
p->next = 0;
|
|
p->ob = 0;
|
|
p->flags = 0;
|
|
return p;
|
|
}
|
|
|
|
#ifdef DEBUGMALLOC_EXTENSIONS
|
|
void mark_free_sentences() {
|
|
sentence_t *sent = sent_free;
|
|
|
|
while (sent) {
|
|
DO_MARK(sent, TAG_SENTENCE);
|
|
/* Freed sentences should have been freed. right?
|
|
if (sent->function)
|
|
EXTRA_REF(BLOCK(sent->function))++;
|
|
if (sent->verb)
|
|
EXTRA_REF(BLOCK(sent->verb))++;
|
|
*/
|
|
sent = sent->next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void free_sentence (sentence_t * p)
|
|
{
|
|
if (p->flags & V_FUNCTION) {
|
|
if (p->function.f)
|
|
free_funp(p->function.f);
|
|
else p->function.f = 0;
|
|
} else {
|
|
if (p->function.s)
|
|
free_string(p->function.s);
|
|
else p->function.s = 0;
|
|
}
|
|
#ifndef NO_ADD_ACTION
|
|
if (p->verb)
|
|
free_string(p->verb);
|
|
p->verb = 0;
|
|
#endif
|
|
p->next = sent_free;
|
|
sent_free = p;
|
|
}
|
|
|
|
void fatal (const char *fmt, ...)
|
|
{
|
|
static int in_fatal = 0;
|
|
char msg_buf[2049];
|
|
va_list args;
|
|
|
|
switch (in_fatal) {
|
|
default:
|
|
debug_message("Fatal error while shutting down. Aborting.\n");
|
|
break;
|
|
case 0:
|
|
in_fatal = 1;
|
|
V_START(args, fmt);
|
|
V_VAR(char *, fmt, args);
|
|
vsnprintf(msg_buf, 2048, fmt, args);
|
|
va_end(args);
|
|
debug_message("******** FATAL ERROR: %s\nFluffOS driver attempting to exit gracefully.\n", msg_buf);
|
|
if (current_file)
|
|
debug_message("(occured during compilation of %s at line %d)\n", current_file, current_line);
|
|
if (current_object)
|
|
debug_message("(current object was /%s)\n", current_object->obname);
|
|
|
|
dump_trace(1);
|
|
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
save_stat_files();
|
|
#endif
|
|
case 1:
|
|
in_fatal = 2;
|
|
copy_and_push_string(msg_buf);
|
|
push_object(command_giver);
|
|
push_object(current_object);
|
|
safe_apply_master_ob(APPLY_CRASH, 3);
|
|
debug_message("crash() in master called successfully. Aborting.\n");
|
|
}
|
|
/* Make sure we don't trap our abort() */
|
|
#ifdef SIGABRT
|
|
signal(SIGABRT, SIG_DFL);
|
|
#endif
|
|
#ifdef SIGILL
|
|
signal(SIGILL, SIG_DFL);
|
|
#endif
|
|
#ifdef SIGIOT
|
|
signal(SIGIOT, SIG_DFL);
|
|
#endif
|
|
|
|
#if !defined(DEBUG_NON_FATAL) || !defined(DEBUG)
|
|
abort();
|
|
#endif
|
|
in_fatal = 0;
|
|
}
|
|
|
|
static int num_error = 0;
|
|
|
|
#ifdef MUDLIB_ERROR_HANDLER
|
|
static int num_mudlib_error = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Error() has been "fixed" so that users can catch and throw them.
|
|
* To catch them nicely, we really have to provide decent error information.
|
|
* Hence, all errors that are to be caught
|
|
* (error_recovery_context_exists == 2) construct a string containing
|
|
* the error message, which is returned as the
|
|
* thrown value. Users can throw their own error values however they choose.
|
|
*/
|
|
|
|
/*
|
|
* This is here because throw constructs its own return value; we dont
|
|
* want to replace it with the system's error string.
|
|
*/
|
|
|
|
void throw_error()
|
|
{
|
|
if (((current_error_context->save_csp + 1)->framekind & FRAME_MASK) == FRAME_CATCH) {
|
|
LONGJMP(current_error_context->context, 1);
|
|
fatal("Throw_error failed!");
|
|
}
|
|
error("Throw with no catch.\n");
|
|
}
|
|
|
|
static void debug_message_with_location (char * err) {
|
|
if (current_object && current_prog) {
|
|
debug_message("%sprogram: /%s, object: /%s, file: %s\n",
|
|
err,
|
|
current_prog->filename,
|
|
current_object->obname,
|
|
get_line_number(pc, current_prog));
|
|
} else if (current_object) {
|
|
debug_message("%sprogram: (none), object: /%s, file: (none)\n",
|
|
err,
|
|
current_object->obname);
|
|
} else {
|
|
debug_message("%sprogram: (none), object: (none), file: (none)\n",
|
|
err);
|
|
}
|
|
}
|
|
|
|
static void add_message_with_location (char * err) {
|
|
if (current_object && current_prog) {
|
|
add_vmessage(command_giver, "%sprogram: /%s, object: /%s, file: %s\n",
|
|
err,
|
|
current_prog->filename,
|
|
current_object->obname,
|
|
get_line_number(pc, current_prog));
|
|
} else if (current_object) {
|
|
add_vmessage(command_giver, "%sprogram: (none), object: /%s, file: (none)\n",
|
|
err,
|
|
current_object->obname);
|
|
} else {
|
|
add_vmessage(command_giver, "%sprogram: (none), object: (none), file: (none)\n",
|
|
err);
|
|
}
|
|
}
|
|
|
|
#ifdef MUDLIB_ERROR_HANDLER
|
|
static void mudlib_error_handler (char * err, int katch) {
|
|
mapping_t *m;
|
|
const char *file = NULL;
|
|
int line = 0;
|
|
svalue_t *mret;
|
|
|
|
m = allocate_mapping(6);
|
|
add_mapping_string(m, "error", err);
|
|
if (current_prog)
|
|
add_mapping_malloced_string(m, "program", add_slash(current_prog->filename));
|
|
if (current_object)
|
|
add_mapping_object(m, "object", current_object);
|
|
add_mapping_array(m, "trace", get_svalue_trace());
|
|
if (current_prog)
|
|
get_line_number_info(&file, &line);
|
|
if(file)
|
|
add_mapping_malloced_string(m, "file", add_slash(file));
|
|
if(line)
|
|
add_mapping_pair(m, "line", line);
|
|
|
|
push_refed_mapping(m);
|
|
if (katch) {
|
|
STACK_INC;
|
|
*sp = const1;
|
|
mret = apply_master_ob(APPLY_ERROR_HANDLER,2);
|
|
} else {
|
|
mret = apply_master_ob(APPLY_ERROR_HANDLER,1);
|
|
}
|
|
if ((mret == (svalue_t *)-1) || !mret) {
|
|
debug_message("No error handler for error: ");
|
|
debug_message_with_location(err);
|
|
dump_trace(0);
|
|
} else if (mret->type == T_STRING) {
|
|
debug_message("%s", mret->u.string);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void error_handler (char * err)
|
|
{
|
|
const char *object_name;
|
|
|
|
/* in case we're going to jump out of load_object */
|
|
#ifndef NO_ENVIRONMENT
|
|
restrict_destruct = 0;
|
|
#endif
|
|
num_objects_this_thread = 0;/* reset the count */
|
|
|
|
if (((current_error_context->save_csp + 1)->framekind & FRAME_MASK) == FRAME_CATCH) {
|
|
/* user catches this error */
|
|
#ifdef LOG_CATCHES
|
|
/* This is added so that catches generate messages in the log file. */
|
|
#ifdef MUDLIB_ERROR_HANDLER
|
|
if (num_mudlib_error) {
|
|
debug_message("Error in error handler: ");
|
|
num_error++;
|
|
#endif
|
|
debug_message_with_location(err);
|
|
(void) dump_trace(0);
|
|
#ifdef MUDLIB_ERROR_HANDLER
|
|
num_error--;
|
|
num_mudlib_error = 0;
|
|
} else {
|
|
if(max_eval_error)
|
|
outoftime = 0;
|
|
|
|
if (!too_deep_error) {
|
|
num_mudlib_error++;
|
|
mudlib_error_handler(err, 1);
|
|
num_mudlib_error--;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
if(max_eval_error)
|
|
outoftime = 1;
|
|
free_svalue(&catch_value, "caught error");
|
|
catch_value.type = T_STRING;
|
|
catch_value.subtype = STRING_MALLOC;
|
|
catch_value.u.string = string_copy(err, "caught error");
|
|
LONGJMP(current_error_context->context, 1);
|
|
fatal("Catch() longjump failed");
|
|
}
|
|
|
|
if (num_error > 0) {
|
|
/* This can happen via errors in the object_name() apply. */
|
|
debug_message("Error '%s' while trying to print error trace -- trace suppressed.\n", err);
|
|
too_deep_error = max_eval_error = 0;
|
|
if (current_error_context)
|
|
LONGJMP(current_error_context->context, 1);
|
|
fatal("LONGJMP failed or no error context for error.\n");
|
|
}
|
|
|
|
num_error++;
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
if (current_object)
|
|
add_errors(¤t_object->stats, 1);
|
|
#endif
|
|
#ifdef MUDLIB_ERROR_HANDLER
|
|
if (!too_deep_error) {
|
|
if (num_mudlib_error) {
|
|
debug_message("Error in error handler: ");
|
|
debug_message_with_location(err);
|
|
(void) dump_trace(0);
|
|
num_mudlib_error = 0;
|
|
} else {
|
|
num_mudlib_error++;
|
|
num_error--;
|
|
outoftime = 0;
|
|
mudlib_error_handler(err, 0);
|
|
if(max_eval_error)
|
|
outoftime = 1;
|
|
num_mudlib_error--;
|
|
num_error++;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
debug_message_with_location(err + 1);
|
|
#if defined(DEBUG) && defined(TRACE_CODE)
|
|
object_name = dump_trace(1);
|
|
#else
|
|
object_name = dump_trace(0);
|
|
#endif
|
|
if (object_name) {
|
|
object_t *ob;
|
|
|
|
ob = find_object2(object_name);
|
|
if (!ob) {
|
|
if (command_giver)
|
|
add_vmessage(command_giver,
|
|
"error when executing program in destroyed object /%s\n",
|
|
object_name);
|
|
debug_message("error when executing program in destroyed object /%s\n",
|
|
object_name);
|
|
}
|
|
}
|
|
if (command_giver && command_giver->interactive) {
|
|
#ifndef NO_WIZARDS
|
|
if ((command_giver->flags & O_IS_WIZARD) || !strlen(DEFAULT_ERROR_MESSAGE)) {
|
|
#endif
|
|
add_message_with_location(err + 1);
|
|
#ifndef NO_WIZARDS
|
|
} else {
|
|
add_vmessage(command_giver, "%s\n", DEFAULT_ERROR_MESSAGE);
|
|
}
|
|
#endif
|
|
}
|
|
if (current_heart_beat) {
|
|
static char hb_message[] = "FluffOS driver tells you: You have no heart beat!\n";
|
|
set_heart_beat(current_heart_beat, 0);
|
|
debug_message("Heart beat in /%s turned off.\n", current_heart_beat->obname);
|
|
if (current_heart_beat->interactive)
|
|
add_message(current_heart_beat, hb_message, sizeof(hb_message)-1);
|
|
|
|
current_heart_beat = 0;
|
|
}
|
|
}
|
|
num_error--;
|
|
too_deep_error = max_eval_error = 0;
|
|
if (current_error_context)
|
|
LONGJMP(current_error_context->context, 1);
|
|
fatal("LONGJMP failed or no error context for error.\n");
|
|
}
|
|
|
|
void error_needs_free (char * s)
|
|
{
|
|
char err_buf[2048];
|
|
strncpy(err_buf + 1, s, 2047);
|
|
err_buf[0] = '*'; /* all system errors get a * at the start */
|
|
err_buf[2047] = '\0';
|
|
FREE_MSTR(s);
|
|
|
|
error_handler(err_buf);
|
|
}
|
|
|
|
void error (const char * const fmt, ...)
|
|
{
|
|
char err_buf[2048];
|
|
va_list args;
|
|
|
|
V_START(args, fmt);
|
|
V_VAR(char *, fmt, args);
|
|
vsnprintf(err_buf + 1, 2046, fmt, args);
|
|
va_end(args);
|
|
err_buf[0] = '*'; /* all system errors get a * at the start */
|
|
DTRACE_PROBE1(fluffos, error, (char *)err_buf);
|
|
error_handler(err_buf);
|
|
}
|
|
|
|
/*
|
|
* This one is called from HUP.
|
|
*/
|
|
int MudOS_is_being_shut_down;
|
|
|
|
#ifdef SIGNAL_FUNC_TAKES_INT
|
|
void startshutdownMudOS (int sig)
|
|
#else
|
|
void startshutdownMudOS()
|
|
#endif
|
|
{
|
|
MudOS_is_being_shut_down = 1;
|
|
}
|
|
|
|
/*
|
|
* This one is called from the command "shutdown".
|
|
* We don't call it directly from HUP, because it is dangerous when being
|
|
* in an interrupt.
|
|
*/
|
|
void shutdownMudOS (int exit_code)
|
|
{
|
|
int i;
|
|
|
|
shout_string("FluffOS driver shouts: shutting down immediately.\n");
|
|
#ifdef PACKAGE_MUDLIB_STATS
|
|
save_stat_files();
|
|
#endif
|
|
#ifdef PACKAGE_ASYNC
|
|
complete_all_asyncio();
|
|
#endif
|
|
|
|
#ifdef PACKAGE_DB
|
|
db_cleanup();
|
|
#endif
|
|
ipc_remove();
|
|
#if defined(PACKAGE_SOCKETS) || defined(PACKAGE_EXTERNAL)
|
|
for (i = 0; i < max_lpc_socks; i++) {
|
|
if (lpc_socks[i].state == STATE_CLOSED) continue;
|
|
while (OS_socket_close(lpc_socks[i].fd) == -1 && errno == EINTR)
|
|
;
|
|
}
|
|
#endif
|
|
for (i = 0; i < max_users; i++) {
|
|
if (all_users[i] && !(all_users[i]->iflags & CLOSING))
|
|
flush_message(all_users[i]);
|
|
}
|
|
#ifdef PROFILING
|
|
monitor(0, 0, 0, 0, 0); /* cause gmon.out to be written */
|
|
#endif
|
|
exit(exit_code);
|
|
}
|
|
|
|
/*
|
|
* Call this one when there is only little memory left. It will start
|
|
* Armageddon.
|
|
*/
|
|
void slow_shut_down (int minutes)
|
|
{
|
|
/*
|
|
*free some memory.
|
|
*/
|
|
svalue_t *amo;
|
|
|
|
push_number(minutes);
|
|
amo = apply_master_ob(APPLY_SLOW_SHUTDOWN, 1);
|
|
/* in this case, approved means the mudlib will handle it */
|
|
if (!MASTER_APPROVED(amo))
|
|
{
|
|
object_t *save_current = current_object;
|
|
|
|
current_object = 0;
|
|
save_command_giver(0);
|
|
shout_string("FluffOS driver shouts: Out of memory.\n");
|
|
restore_command_giver();
|
|
current_object = save_current;
|
|
#ifdef SIGNAL_FUNC_TAKES_INT
|
|
startshutdownMudOS(1);
|
|
#else
|
|
startshutdownMudOS();
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
void do_message (svalue_t * lclass, svalue_t * msg, array_t * scope, array_t * exclude, int recurse)
|
|
{
|
|
int i, j, valid;
|
|
object_t *ob;
|
|
|
|
for (i = 0; i < scope->size; i++) {
|
|
switch (scope->item[i].type) {
|
|
case T_STRING:
|
|
ob = find_object(scope->item[i].u.string);
|
|
if (!ob || !object_visible(ob))
|
|
continue;
|
|
break;
|
|
case T_OBJECT:
|
|
ob = scope->item[i].u.ob;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
if (ob->flags & O_LISTENER || ob->interactive) {
|
|
for (valid = 1, j = 0; j < exclude->size; j++) {
|
|
if (exclude->item[j].type != T_OBJECT)
|
|
continue;
|
|
if (exclude->item[j].u.ob == ob) {
|
|
valid = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (valid) {
|
|
push_svalue(lclass);
|
|
push_svalue(msg);
|
|
apply(APPLY_RECEIVE_MESSAGE, ob, 2, ORIGIN_DRIVER);
|
|
}
|
|
}
|
|
#ifndef NO_ENVIRONMENT
|
|
else if (recurse) {
|
|
array_t *tmp;
|
|
|
|
tmp = all_inventory(ob, 1);
|
|
do_message(lclass, msg, tmp, exclude, 0);
|
|
free_array(tmp);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !defined(NO_RESETS) && defined(LAZY_RESETS)
|
|
void try_reset (object_t * ob)
|
|
{
|
|
if ((ob->next_reset < current_time) && !(ob->flags & O_RESET_STATE)) {
|
|
debug(d_flag, ("(lazy) RESET /%s\n", ob->obname));
|
|
|
|
/* need to set the flag here to prevent infinite loops in apply_low */
|
|
ob->flags |= O_RESET_STATE;
|
|
reset_object(ob);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef NO_ENVIRONMENT
|
|
#ifdef F_FIRST_INVENTORY
|
|
object_t *first_inventory (svalue_t * arg)
|
|
{
|
|
object_t *ob;
|
|
|
|
if (arg->type == T_STRING) {
|
|
ob = find_object(arg->u.string);
|
|
if (ob && !object_visible(ob))
|
|
ob = 0;
|
|
} else
|
|
ob = arg->u.ob;
|
|
if (ob == 0)
|
|
bad_argument(arg, T_STRING | T_OBJECT, 1, F_FIRST_INVENTORY);
|
|
ob = ob->contains;
|
|
|
|
#ifdef F_SET_HIDE
|
|
while (ob && (ob->flags & O_HIDDEN) && !object_visible(ob))
|
|
ob = ob->next_inv;
|
|
#endif
|
|
|
|
return ob;
|
|
}
|
|
#endif
|
|
#endif
|