#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <setjmp.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <memory.h>
#include <varargs.h>
#if defined(sun)
#include <alloca.h>
#endif
#if defined(M_UNIX) || defined(_SEQUENT_) || defined(SOLARIS) || defined(__NetBSD__)
#include <dirent.h>
#else
#include <sys/dir.h>
#endif

#include "config.h"
#include "lint.h"
#include "stdio.h"
#include "interpret.h"
#include "object.h"
#include "sent.h"
#include "exec.h"
#include "comm.h"
#include "mudstat.h"
#include "incralloc.h"
extern int errno;
extern int comp_flag;

char *inherit_file;

#if !defined(NeXT) && !defined(LINUX)
extern int lstat (const char *, struct stat *);
#else
#define lstat stat
#endif
#ifndef LINUX
extern int fchmod (int, mode_t);
#endif
char *last_verb;
extern int current_time, tot_alloc_dest_object, tot_removed_object;


extern int special_parse (char *),
    set_call (struct object *, struct sentence *, int),
    legal_path (char *);

void pre_compile (char *),
remove_interactive (struct object *, int);
int    add_action (struct svalue *, struct svalue *, int);
void    ipc_remove(),

    show_info_about (char *, char *, struct interactive *),
    print_lnode_status (int),
    remove_all_players(), end_new_file(),
    load_ob_from_swap (struct object *),
    print_svalue (struct svalue *, struct object *),
    debug_message_value(),
    destruct2();
char *dump_malloc_data();
void start_new_file (FILE *);

extern int d_flag, s_flag;

struct object *obj_list, *obj_list_destruct, *master_ob;


struct object *current_object;      /* The object interpreting a function. */
struct object *command_giver;       /* Where the current command came from. */
struct object *current_interactive; /* The user who caused this execution */

int num_parse_error;		/* Number of errors in the parser. */

void shutdowngame();

extern void flush_all_player_mess();
struct object* find_object_no_create();

int
find_status(struct program *prog, char *str, int not_type)
{
    int i, j;
    char *super_name = 0;
    char *sub_name;
    char *real_name;
    char *search;
    
    variable_index_found = variable_inherit_found = 255;
    real_name = strrchr(str, ':') + 1;
    sub_name = strchr(str, ':') + 2;
    
    if(!(real_name = (findstring((real_name == (char *)1) ? str : real_name))))
        return -1;
    if (sub_name == (char *)2)
    {
	access_program(prog);
	for (i = 0; i < (int)prog->num_variables; i++)
	{
	    if (prog->variable_names[i].name == real_name &&
		!(prog->variable_names[i].type & not_type))
		{
		    variable_index_found = i;
		    variable_inherit_found = prog->num_inherited - 1;
		    variable_type_mod_found = prog->variable_names[i].type;
		    return prog->inherit[variable_inherit_found].variable_index_offset + i;
		}
	}
    }
    else if (sub_name - str > 2)
    {
	super_name = xalloc(sub_name - str - 1);
	memcpy(super_name, str, sub_name - str - 2);
	super_name[sub_name - str - 2] = 0;
	if (!strcmp(super_name, "this"))
	    return find_status(prog, sub_name, not_type);
    }
    else
	str = sub_name;
    
    for(j =  prog->num_inherited - 2; j >= 0 ;j -= prog->inherit[j].prog->num_inherited)
    {
	int type;
	
	if (super_name &&
	    !strcmp(super_name, prog->inherit[j].name))
	    search = sub_name;
	else
	    search = str;
	if (find_status(prog->inherit[j].prog, search, not_type) != -1)
	{
	    int type = prog->inherit[j].type;
	    if (variable_type_mod_found & TYPE_MOD_PUBLIC)
		type &= ~TYPE_MOD_PRIVATE;
	    if (variable_type_mod_found & TYPE_MOD_PRIVATE)
		type &= ~TYPE_MOD_PUBLIC;
	    variable_type_mod_found |= type & TYPE_MOD_MASK;
	    if (variable_type_mod_found & not_type)
		continue;
	    
	    variable_inherit_found += j -
		(prog->inherit[j].prog->num_inherited - 1);
	    return prog->inherit[variable_inherit_found].variable_index_offset + 
		variable_index_found;
	}
    }
    return -1;
}


void
swap_objects(struct object *ob1, struct object *ob2)
{
    extern void call_out_swap_objects(struct object *, struct object *);
    extern void stack_swap_objects(struct object *, struct object *);
    struct program  *tmp_prog;
    struct svalue *tmp_var;
    
    remove_ob_from_swap(ob1, 1);
    remove_ob_from_swap(ob2, 1);
    
    
    /* swap the objects */
    call_out_swap_objects(ob1, ob2);
    stack_swap_objects(ob1, ob2);
    
    tmp_var = ob1->variables;
    ob1->variables = ob2->variables;
    ob2->variables = tmp_var;
    
    tmp_prog = ob1->prog;
    ob1->prog = ob2->prog;
    ob2->prog = tmp_prog;
    
    /* Swap the O_CREATED bit */
    ob1->flags ^= ob2->flags & O_CREATED;
    ob2->flags ^= ob1->flags & O_CREATED;
    ob1->flags ^= ob2->flags & O_CREATED;
    
}


/*
 * 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.
 *
 * Save the command_giver, because reset() in the new object might change
 * it.
 *
 * 
 */
char *current_loaded_file = 0;
#ifdef BINARIES
extern int save_binary(struct program *);
#endif

struct object *
load_object(char *lname, int dont_reset, struct object *old_ob)
{
    FILE *f;
    extern int total_lines;
    extern int approved_object;

    struct object *ob, *save_command_giver = command_giver;
    extern struct program *prog;
    extern char *current_file;
    struct stat c_st;
    int name_length;
    char real_name[200], name[200];
    extern struct object *swap_ob;
    char new_ob_name[200];
    extern int pragma_resident;
    extern void init_smart_log(), dump_smart_log();
#ifdef BINARIES
    extern int pragma_save_binary, driver_mtime;
    int errbin = 1;
    int file_mtime;
    char *binname;
    FILE *binfile;
#endif

    /* Truncate possible .c in the object name. */
    /* Remove leading '/' if any. */
    while(lname[0] == '/')
	lname++;
    strncpy(name, lname, sizeof(name) - 1);
    name[sizeof name - 1] = '\0';
    name_length = strlen(name);
    if (name_length > sizeof name - 4)
	name_length = sizeof name - 4;
    name[name_length] = '\0';
    if (name[name_length-2] == '.' && name[name_length-1] == 'c') {
	name[name_length-2] = '\0';
	name_length -= 2;
    }
    if (old_ob)
    {
	struct object *invalid_ob;
	
	strcpy(new_ob_name, name);
	strcat(new_ob_name, "#0");
	if (invalid_ob = find_object2(new_ob_name))
	{
	    destruct_object(invalid_ob);
	}
    }
    
    /*
     * First check that the c-file exists.
     */
    
    (void)strcpy(real_name, name);
    (void)strcat(real_name, ".c");

    if (stat(real_name, &c_st) == -1)
    {
	fprintf(stderr, "Could not load descr for %s\n", real_name);
	error("Failed to load file.\n");
	return 0;
    }
#ifdef BINARIES
    file_mtime = c_st.st_mtime;
#endif
    /*
     * Check if it is a legal name.
     */
    if (!legal_path(real_name))
    {
	fprintf(stderr, "Illegal pathname: %s\n", real_name);
	error("Illegal path name.\n");
	return 0;
    }

#ifdef BINARIES
    binname = (char *)xalloc(strlen(BINARIES) + 
			     strlen(real_name) + 1);
    sprintf(binname, "%s%s", BINARIES, real_name);
    
    errbin = 1;
    if (stat(binname, &c_st) != -1)
	if ((file_mtime < c_st.st_mtime))
	    if ((binfile = fopen(binname, "r")) != NULL)
	    {
		errbin = load_binary(binfile, real_name);
		
		if (!errbin && !inherit_file)
		{
		    if (comp_flag) 
			fprintf(stderr,"    %s loaded from binary: %s\n",
				real_name, binname);
		}
	    }
    free(binname);
    pragma_save_binary = 0;
    if (errbin) {
#endif /* BINARIES */
	
	if (comp_flag)
	    fprintf(stderr, " compiling %s ...", real_name);
	f = fopen(real_name, "r");
	if (s_flag)
	{
	    num_fileread++;
	    num_compile++;
	}
	
	if (f == 0)
	{
	    perror(real_name);
	    error("Could not read the file.\n");
	}
	init_smart_log();
	start_new_file(f);
	current_file = string_copy(real_name);	/* This one is freed below */
	current_loaded_file = real_name;
	compile_file();
	end_new_file();
	current_loaded_file = 0;
	if (comp_flag)
	{
	    if (inherit_file == 0)
		fprintf(stderr, " done\n");
	    else
		fprintf(stderr, " suspended, compiling inherited file(s)\n");
	}
	update_compile_av(total_lines);
	total_lines = 0;
	(void)fclose(f);
	free(current_file);
	current_file = 0;
	dump_smart_log();
	/* Sorry, can not handle objects without programs yet. */
	if (inherit_file == 0 && (num_parse_error > 0 || prog == 0))
	{
	    if (prog)
		free_prog(prog, 1);
	    if (num_parse_error == 0 && prog == 0)
		error("No program in object !\n");
	    error("Error in loading object\n");
	}
#ifdef BINARIES
    }
#endif

    /*
     * 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)
    {
	char *tmp = inherit_file;
	if (prog)
	{
	    free_prog(prog, 1);
	    prog = 0;
	}
        {
	    char *t1 = tmp, *t2 = strrchr(tmp,'\000');
	    while (*t1=='/') 
		t1++;
	    if (t2-t1 > 1) 
		if (*(--t2)=='c') 
		    if (*(--t2)=='.') 
			*t2='\000';

	    if (strcmp(t1, name) == 0)
	    {
		free(inherit_file);
		inherit_file = 0;
		error("Illegal to inherit self.\n");
	    }
	}
	/* Extreme ugliness begins. inherit_file must be 0 when we call
	   load_object, but tmp is used as a parameter, so we can not free
	   the string until after the call
	*/
	inherit_file = 0;

	load_object(tmp, 1, 0);

	free(tmp);
	/* Extreme ugliness ends */

	ob = load_object(name, dont_reset, old_ob);
	return ob;
    }
    ob = get_empty_object();
    if (!old_ob)
	ob->name = string_copy(name);	/* Shared string is no good here */
    else
    {
	ob->name = xalloc(strlen(name) + 3);
	strcpy(ob->name, name);
	strcat(ob->name, "#0");
    }
    ob->prog = prog;
#ifdef BINARIES
    if (pragma_save_binary)
    {
	save_binary(ob->prog);
	pragma_save_binary = 0;
    }
#endif /* BINARIES */
    swap_lineno(ob->prog);

    if (obj_list)
    {
	ob->next_all = obj_list;
	ob->prev_all = obj_list->prev_all;

	obj_list->prev_all->next_all = ob;
	obj_list->prev_all = ob;
	obj_list = ob;
    }
    else
	obj_list = swap_ob = ob->next_all = ob->prev_all = ob;
    
    if (current_object)
	access_object(current_object);

    /*	
	add name to fast object lookup table 
    */
    enter_object_hash(ob);	

    {
	int num_var, i;
	extern int tot_alloc_variable_size;
	extern struct svalue const0;

	num_var = ob->prog->num_variables +
	    ob->prog->inherit[ob->prog->num_inherited - 1]
		.variable_index_offset + 1;
	if (ob->variables)
	    fatal("Object allready initialized!\n");
	
	ob->variables = (struct svalue *)
	    xalloc(num_var * sizeof(struct svalue));
	tot_alloc_variable_size += num_var * sizeof(struct svalue);
	for (i= 0; i<num_var; i++)
	    ob->variables[i] = const0;
	ob->variables++;
    }
    /* 
	We ought to add a flag here marking the object as unfinished
	so it can be removed if the following code causes an LPC error
     */

    if (master_ob)
    {
	push_object(current_object);
	push_object(ob);
	apply_master_ob(M_LOADED_OBJECT, 2);
    }
    if (!(ob->flags & (O_CREATED | O_DESTRUCTED)) && !dont_reset)
	create_object(ob);
    
    if (ob->flags & O_DESTRUCTED)
	return 0;

    command_giver = save_command_giver;
    if (d_flag & DEBUG_LOAD && ob)
	debug_message("--%s loaded\n", ob->name);

    
    if (master_ob && ob->prog->flags & PRAGMA_RESIDENT)
    {
	struct svalue *ret;
	push_object(ob);
	ret = apply_master_ob(M_VALID_RESIDENT, 1);
	if (!ret || ret->u.number == 0)
	    ob->prog->flags &= ~PRAGMA_RESIDENT;
    }
	
    
    return ob;
}

char *
make_new_name(char *str)
{
    static int i = 1;
    char *p = xalloc(strlen(str) + 10);

    (void) sprintf(p, "%s#%d", str, i);
    i++;
    return p;
}
    

/*
 * Save the command_giver, because reset() in the new object might change
 * it.
 */
struct object *
clone_object(char *str1)
{
    struct object *ob, *new_ob;
    struct object *save_command_giver = command_giver;
    extern struct object *swap_ob;

    ob = find_object_no_create(str1);
    if (ob == 0 || ob->super || (ob->flags & O_CLONE) || ob->prog->flags & PRAGMA_NO_CLONE)
	error("Cloning a bad object! (No Master, Master in environment or Master is clone)\n");
    
    /* We do not want the heart beat to be running for unused copied objects */

    new_ob = get_empty_object();

    new_ob->name = make_new_name(ob->name);
    new_ob->flags |= O_CLONE;
    new_ob->prog = ob->prog;
    reference_prog (ob->prog, "clone_object");
    if (!current_object)
	fatal("clone_object() from no current_object !\n");
    
    
    if (obj_list)
    {
	new_ob->next_all = obj_list;
	new_ob->prev_all = obj_list->prev_all;
	obj_list->prev_all->next_all = new_ob;
	obj_list->prev_all = new_ob;
	obj_list = new_ob;
    }
    else
	obj_list = swap_ob = new_ob->next_all = new_ob->prev_all = new_ob;

    enter_object_hash(new_ob);	/* Add name to fast object lookup table */
    {
	int num_var, i;
	extern int tot_alloc_variable_size;
	extern struct svalue const0;

	num_var = new_ob->prog->num_variables +
	    new_ob->prog->inherit[ob->prog->num_inherited - 1]
	    .variable_index_offset + 1;
	if (new_ob->variables)
	    fatal("Object allready initialized!\n");
	
	new_ob->variables = (struct svalue *)
	    xalloc(num_var * sizeof(struct svalue));
	tot_alloc_variable_size += num_var * sizeof(struct svalue);
	for (i= 0; i<num_var; i++)
	    new_ob->variables[i] = const0;
	new_ob->variables++;
    }
    if (master_ob)
    {
	push_object(current_object);
	push_object(new_ob);
	apply_master_ob(M_CLONED_OBJECT, 2);
	if (new_ob->flags & O_DESTRUCTED)
	    return 0;
    }
    if (!(ob->flags & O_CREATED))
	create_object(new_ob);
    command_giver = save_command_giver;
    /* Never know what can happen ! :-( */
    if (new_ob->flags & O_DESTRUCTED)
	return 0;
    return new_ob;
}

struct object *
environment(struct svalue *arg)
{
    struct object *ob = current_object;

    if (arg && arg->type == T_OBJECT)
	ob = arg->u.ob;
    else if (arg && arg->type == T_STRING)
	ob = find_object2(arg->u.string);
    if (ob == 0 || ob->super == 0 || (ob->flags & O_DESTRUCTED))
	return 0;
    if (ob->flags & O_DESTRUCTED)
	error("environment() off destructed object.\n");
    return ob->super;
}

/*
 * Execute a command for an object. Copy the command into a
 * new buffer, because 'parse_command()' can modify the command.
 * If the object is not current object, static functions will not
 * be executed. This will prevent forcing players to do illegal things.
 *
 * Return cost of the command executed if success (> 0).
 * When failure, return 0.
 */
int 
command_for_object(char *str, struct object *ob)
{
    char buff[1000];
    extern int eval_cost;
    int save_eval_cost = eval_cost - 1000;

    if (strlen(str) > sizeof(buff) - 1)
	error("Too long command.\n");
    if (ob == 0)
	ob = current_object;
    else if (ob->flags & O_DESTRUCTED)
	return 0;
    strncpy(buff, str, sizeof buff);
    buff[sizeof buff - 1] = '\0';
    if (parse_command(buff, ob))
	return eval_cost - save_eval_cost;
    else
	return 0;
}

/*
 * Search the inventory of the second argument 'ob' for instances
 * of the first.
 */
static struct object *object_present2 (char *, struct object *);

struct object *
object_present(struct svalue *v, struct svalue *where)
{
    int i;
    struct object *ret;

    if (v->type == T_OBJECT) 
	if (v->u.ob->flags & O_DESTRUCTED)
	    return 0;

    switch (where->type)
    {
    case T_OBJECT:
	if (where->u.ob->flags & O_DESTRUCTED)
	    return 0;

	if (v->type == T_OBJECT) 
	{
	    if (v->u.ob->super == where->u.ob)
		return v->u.ob;
	    else
		return 0;
	}
	else
	    return object_present2(v->u.string, where->u.ob->contains);

	break;
	
    case T_POINTER:
	if (v->type == T_OBJECT) 
	{
	    for (i = 0 ; i < where->u.vec->size ; i++)
	    {
		if (where->u.vec->item[i].type != T_OBJECT)
		    continue;
		if (v->u.ob->super == where->u.vec->item[i].u.ob)
		    return v->u.ob;
	    }
	}
	else
	{
	    for (i = 0 ; i < where->u.vec->size ; i++)
	    {
		if (where->u.vec->item[i].type != T_OBJECT)
		    continue;
		if (ret = 
		    object_present2(v->u.string, 
				    where->u.vec->item[i].u.ob->contains))
		    return ret;
	    }
	}
	return 0;
	break;

    default:
	error("Strange type %d in object_present.\n", where->type);
	return 0;
	break;
    }
}

static struct object *
object_present2(char *str, struct object *ob)
{
    struct svalue *ret;
    char *p;
    int count = 0, length;
    char *item;

    item = string_copy(str);
    length = strlen(item);
    p = item + length - 1;
    if (*p >= '0' && *p <= '9')
    {
	while(p > item && *p >= '0' && *p <= '9')
	    p--;
	if (p > item && *p == ' ')
	{
	    count = atoi(p+1) - 1;
	    *p = '\0';
	    length = p - item;	/* This is never used again ! */
	}
    }
    for (; ob; ob = ob->next_inv)
    {
	push_string(item, STRING_MALLOC);
	ret = apply("id", ob, 1, 1);
	if (ob->flags & O_DESTRUCTED)
	{
	    free(item);
	    return 0;
	}
	if (ret == 0 || (ret->type == T_NUMBER && ret->u.number == 0))
	    continue;
	if (count-- > 0)
	    continue;
	free(item);
	return ob;
    }
    free(item);
    return 0;
}

/*
 * Remove an object. It is first moved into the destruct list, and
 * not really destructed until later. (see destruct2()).
 * 
 * We will simply not allow the master object to be destructed, Ha!
 */
#ifdef BINARIES
extern int save_binary(struct program *);
#endif

void
destruct_object(struct object *ob)
{
    extern struct object *swap_ob;
    extern struct object *vbfc_object;
    struct svalue *ret;
    struct object *super;
    struct object **pp;
    int removed;
    char *name = 0;
    int i;
    FILE *f;

    
     if (!ob || ob->flags & O_DESTRUCTED)
       return;
    /*
     * If we are to destruct the master ob or the simul_efun ob
     * it must be reloadable.
     */
    if ( (!(ob->flags & O_CLONE) && ob->prog->flags & PRAGMA_RESIDENT) ||
	ob == master_ob || ob == (struct object *)query_simul_efun_ob())
    {
	struct object *new_ob;
	
	new_ob = load_object(ob->name, 1, ob);
	if (!new_ob)
	    error("Can not compile the new %s, aborting destruction of the old.\n", ob->prog->name);

	/* Make sure that the master object and the simul_efun object
	 * stays loaded */
	if (ob == master_ob || ob == (struct object *)query_simul_efun_ob())
	    ob->prog->flags |= PRAGMA_RESIDENT;

	swap_objects(ob, new_ob);
	if (ob == master_ob)
	{
	    extern void master_ob_loaded(void);
	    master_ob_loaded();
	}
	if (new_ob && new_ob->flags & O_CREATED)
	    recreate_object(ob, new_ob);
	ob = new_ob;
	ob->prog->flags &= ~PRAGMA_RESIDENT;
    }

    
    push_object(ob);
    apply_master_ob(M_DESTRUCTED_OBJECT, 1);
    if (ob->flags & O_DESTRUCTED) return;
    /*
     * If this is the first object being shadowed by another object, then
     * destruct the whole list of shadows.
     */
    if (ob->shadowed && !ob->shadowing)
    {
	struct svalue svp;
	struct object *ob2;

	svp.type = T_OBJECT;
	for (ob2 = ob->shadowed; ob2; )
	{
	    svp.u.ob = ob2;
	    ob2 = ob2->shadowed;
	    if (svp.u.ob->shadowed) {
	        free_object(svp.u.ob->shadowed, "destruct_object-5");
		svp.u.ob->shadowed = 0;
	    }
	    if (svp.u.ob->shadowing) {
	        free_object(svp.u.ob->shadowing, "destruct_object-6");
		svp.u.ob->shadowing = 0;
	    }
	    destruct_object(ob2);
	    if (ob->flags & O_DESTRUCTED) return;
	}
    }
    /*
     * The chain of shadows is a double linked list. Take care to update
     * it correctly.
     */
    if (ob->shadowing) {
	change_ref(ob->shadowing->shadowed, ob->shadowed, "destruct_object-1");
	ob->shadowing->shadowed = ob->shadowed;
    }

    if (ob->shadowed) {
	change_ref(ob->shadowed->shadowing, ob->shadowing, "destruct_object-2");
	ob->shadowed->shadowing = ob->shadowing;
    }
    if (ob->shadowing) {
        free_object(ob->shadowing, "destruct_object-3");
	ob->shadowing = 0;
    }
    if (ob->shadowed) {
        free_object(ob->shadowed, "destruct_object-4");
	ob->shadowed = 0;
    }

    if (d_flag & DEBUG_DESTRUCT)
	debug_message("Destruct object %s (ref %d)\n", ob->name, ob->ref);
    super = ob->super;

    /*
     * There is nowhere to move the objects.
     */
    {
	struct svalue svp;
	svp.type = T_OBJECT;
	while(ob->contains)
	{
	    svp.u.ob = ob->contains;
	    push_object(ob->contains);
	    /* An error here will not leave destruct() in an inconsistent
	     * stage.
	     */
	    apply_master_ob(M_DESTRUCT_ENVIRONMENT_OF,1);
	    if (svp.u.ob == ob->contains)
		destruct_object(ob->contains);
	    if (ob->flags & O_DESTRUCTED) return;
	}
    } 

    if (ob->interactive)
    {
	struct object *save=command_giver;

	command_giver=ob;
	if (ob->interactive->ed_buffer)
	{
	    extern void save_ed_buffer();

	    save_ed_buffer();
	}
	flush_all_player_mess();
	command_giver=save;
	remove_interactive(ob, 0);
	if (ob->flags & O_DESTRUCTED) return;
    }

    /*
     * Remove us out of this current room (if any).
     * Remove all sentences defined by this object from all objects here.
     */
    if (ob->super) {
	if (ob->super->flags & O_ENABLE_COMMANDS)
	    remove_sent(ob, ob->super);
	for (pp = &ob->super->contains; *pp; )
	{
	    if ((*pp)->flags & O_ENABLE_COMMANDS)
		remove_sent(ob, *pp);
	    if (*pp != ob)
		pp = &(*pp)->next_inv;
	    else
		*pp = (*pp)->next_inv;
	}
    }

    /* Call destructors */
    if (ob->flags & O_CREATED)
    {
	for (i = ob->prog->num_inherited - 1; i >= 0; i--)
	    if (!(ob->prog->inherit[i].type & TYPE_MOD_SECOND) &&
		ob->prog->inherit[i].prog->dtor_index !=
		(unsigned short) -1)
	    {
		call_function(ob, i, ob->prog->inherit[i].prog->
			      dtor_index, 0);
		pop_stack();
	    if (ob->flags & O_DESTRUCTED) return;
	    }
    }

    remove_object_from_stack(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.
     */

    if (ob == swap_ob)
	swap_ob = ob->prev_all;
    if (ob == obj_list)
	obj_list = ob->next_all;
    
    ob->prev_all->next_all = ob->next_all;
    ob->next_all->prev_all = ob->prev_all;
    if (ob->next_all == ob)
	obj_list = swap_ob = 0;

    delete_all_calls(ob);
    remove_object_hash(ob);
    if (ob->living_name)
	remove_living_name(ob);
    ob->super = 0;
    ob->next_inv = 0;
    ob->contains = 0;
    ob->flags &= ~O_ENABLE_COMMANDS;
    ob->next_all = obj_list_destruct;
    ob->prev_all = 0;
    obj_list_destruct = ob;
    ob->flags |= O_DESTRUCTED;

    if (!(ob->flags & O_CLONE))
	ob->prog->flags |= P_OLD;

    tot_alloc_dest_object++;
    if (ob == vbfc_object)
    {
	free_object(vbfc_object, "destruct_object");
	vbfc_object = 0;
	ret = apply_master_ob(M_GET_VBFC_OBJECT, 0);
	if (ret && ret->type == T_OBJECT)
	{
	    vbfc_object = ret->u.ob;
	    vbfc_object->ref++;
	}
    }
}

/*
 * This one is called when no program is executing from the main loop.
 */
void
destruct2(struct object *ob)
{
    struct sentence *s;
    int num_var;
    extern int tot_alloc_variable_size;

    if (d_flag & DEBUG_DESTRUCT) 
	debug_message("Destruct-2 object %s (ref %d)\n", ob->name, ob->ref);
    
    if (ob->interactive)
	remove_interactive(ob, 0);

    remove_ob_from_swap(ob);
    
    /*
     * 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.
     */
    {
	/*
	 * Deallocate variables in this object.
	 */
	int i;
	num_var = ob->prog->num_variables +
	    ob->prog->inherit[ob->prog->num_inherited - 1].
		variable_index_offset + 1;

	ob->variables--;
	for (i=0; i < num_var; i++) {
	    free_svalue(&ob->variables[i]);
	    ob->variables[i].type = T_NUMBER;
	    ob->variables[i].u.number = 0;
	}
	free((char *)(ob->variables));
	ob->variables = 0;
	tot_alloc_variable_size -= num_var * sizeof(struct svalue);
    }

    free_prog(ob->prog, 1);
    ob->prog = 0;

    for (s = ob->sent; s;) 
    {
	struct sentence *next;
	next = s->next;
	free_sentence(s);
	s = next;
    }
    delete_all_calls(ob);
    free_object(ob, "destruct_object");
}

/*
 * This will enable an object to use commands normally only
 * accessible by interactive players.
 * Also check if the player is a wizard. Wizards must not affect the
 * value of the wizlist ranking.
 */

void
enable_commands(int num)
{
    if (current_object->flags & O_DESTRUCTED)
	return;
    if (d_flag & DEBUG_LIVING)
    {
	debug_message("Enable commands %s (ref %d)\n",
	    current_object->name, current_object->ref);
    }
    if (num)
    {
	current_object->flags |= O_ENABLE_COMMANDS;
	command_giver = current_object;
    }
    else
    {
	current_object->flags &= ~O_ENABLE_COMMANDS;
	if (command_giver == current_object)
	    command_giver = 0;
    }
}

/*
 * Set up a function in this object to be called with the next
 * user input string.
 */
int
input_to(char *fun, int flag, struct vector *v)
{
    struct sentence *s;
    struct svalue *ret;

    if (!command_giver || command_giver->flags & O_DESTRUCTED)
	return 0;

    s = alloc_sentence();
    if (set_call(command_giver, s, flag))
    {
	s->function = make_shared_string(fun);
	s->ob = current_object;
	add_ref(current_object, "input_to");
	free_vector(command_giver->interactive->carryover);
	command_giver->interactive->carryover = v;
	return 1;
    }
    else
	free_sentence(s);
    return 0;
}

#define MAX_LINES 50

/*
 * This one is used by qsort in get_dir().
 */
static int
pstrcmp(struct svalue *p1, struct svalue *p2)
{
    return strcmp(p1->u.string, p2->u.string);
}

/*
 * List files in directory. This function do same as standard list_files did,
 * but instead writing files right away to player this returns an array
 * containing those files. Actually most of code is copied from list_files()
 * function.
 * Differences with list_files:
 *
 *   - file_list("/w"); returns ({ "w" })
 *
 *   - file_list("/w/"); and file_list("/w/."); return contents of directory
 *     "/w"
 *
 *   - file_list("/");, file_list("."); and file_list("/."); return contents
 *     of directory "/"
 */
struct vector *
get_dir(char *path)
{
    struct vector *v;
    int i, count = 0;
    DIR *dirp;
    int namelen, do_match = 0;
#if defined(_AIX) || defined(M_UNIX) || defined(__osf__) || defined(_SEQUENT_) || defined(SOLARIS) || defined(__NetBSD__)
    struct dirent *de;
#else
    struct direct *de;
#endif
    struct stat st;
    char *temppath;
    char *p;
    char *regexp = 0;

    if (!path)
	return 0;

    path = check_valid_path(path, current_object, "get_dir", 0);

    if (path == 0)
	return 0;

    /*
     * We need to modify the returned path, and thus to make a
     * writeable copy.
     * The path "" needs 2 bytes to store ".\0".
     */
    temppath = (char *) alloca(strlen(path) + 2);
    if (strlen(path)<2) {
	temppath[0] = path[0] ? path[0] : '.';
	temppath[1] = '\000';
	p = temppath;
    } else {
	strcpy(temppath, path);
	/*
	 * If path ends with '/' or "/." remove it
	 */
	if ((p = strrchr(temppath, '/')) == 0)
	    p = temppath;
	if ((p[0] == '/' && p[1] == '.' && p[2] == '\0') || 
	    (p[0] == '/' && p[1] == '\0'))
	    *p = '\0';
    }

    if (stat(temppath, &st) < 0)
    {
	if (*p == '\0')
	    return 0;
	regexp = (char *)alloca(strlen(p)+2);
	if (p != temppath)
	{
	    strcpy(regexp, p + 1);
	    *p = '\0';
	}
	else
	{
	    strcpy(regexp, p);
	    strcpy(temppath, ".");
	}
	do_match = 1;
    }
    else if (*p != '\0' && strcmp(temppath, "."))
    {
	if (*p == '/' && *(p + 1) != '\0')
	    p++;
	v = allocate_array(1);
	v->item[0].type = T_STRING;
	v->item[0].string_type = STRING_MALLOC;
	v->item[0].u.string = string_copy(p);
	return v;
    }

    if ((dirp = opendir(temppath)) == 0)
	return 0;

    /*
     *  Count files
     */
    for (de = readdir(dirp); de; de = readdir(dirp))
    {
#if defined(M_UNIX) || defined(_SEQUENT_) || defined(SOLARIS)
	namelen = strlen(de->d_name);
#else
	namelen = de->d_namlen;
#endif
	if (!do_match && (strcmp(de->d_name, ".") == 0 ||
			  strcmp(de->d_name, "..") == 0))
	    continue;
	if (do_match && !match_string(regexp, de->d_name))
	    continue;
	count++;
	if ( count >= MAX_ARRAY_SIZE)
	    break;
    }
    /*
     * Make array and put files on it.
     */
    v = allocate_array(count);
    if (count == 0)
    {
	/* This is the easy case */
	closedir(dirp);
	return v;
    }
    rewinddir(dirp);
    for(i = 0, de = readdir(dirp); i < count; de = readdir(dirp))
    {
#if defined(M_UNIX) || defined(_SEQUENT_) || defined(SOLARIS)
        namelen = strlen(de->d_name);
#else
	namelen = de->d_namlen;
#endif
	if (!do_match && (strcmp(de->d_name, ".") == 0 ||
			  strcmp(de->d_name, "..") == 0))
	    continue;
	if (do_match && !match_string(regexp, de->d_name))
	    continue;
	de->d_name[namelen] = '\0';
	v->item[i].type = T_STRING;
	v->item[i].string_type = STRING_MALLOC;
	v->item[i].u.string = string_copy(de->d_name);
	i++;
    }
    closedir(dirp);
    /* Sort the names. */
    qsort((char *)v->item, count, sizeof v->item[0], pstrcmp);
    return v;
}

int 
tail(char *path)
{
    char buff[1000];
    FILE *f;
    struct stat st;
    int offset;
 
    path = check_valid_path(path, current_object, "tail", 0);

    if (path == 0)
        return 0;
    f = fopen(path, "r");
    if (s_flag)
	num_fileread++;

    if (f == 0)
	return 0;
    if (fstat(fileno(f), &st) == -1)
	fatal("Could not stat an open file.\n");
    offset = st.st_size - 54 * 20;
    if (offset < 0)
	offset = 0;
    if (fseek(f, offset, 0) == -1)
	fatal("Could not seek.\n");
    /* Throw away the first incomplete line. */
    if (offset > 0)
	(void) fgets(buff, sizeof buff, f);
    while(fgets(buff, sizeof buff, f))
	add_message("%s", buff);
    fclose(f);
    return 1;
}


int 
remove_file(char *path)
{
    path = check_valid_path(path, current_object, "remove_file", 1);

    if (path == 0)
        return 0;
    if (unlink(path) == -1)
        return 0;
    return 1;
}

void
print_svalue(struct svalue *arg, struct object *ob)
{
    if (arg == 0)
	a_msg(ob,"<NULL>");
    else if (arg->type == T_STRING)
    {
	if (strlen(arg->u.string) > 9500)	/* Not pretty */
	    error("Too long string.\n");
	/* Strings sent to monsters are now delivered */
	if (ob && (ob->flags & O_ENABLE_COMMANDS) &&
	          !ob->interactive)
	    tell_npc(ob, arg->u.string);
	else
	    a_msg(ob,"%s", arg->u.string);
    } else if (arg->type == T_OBJECT)
	a_msg(ob,"OBJ(%s)", arg->u.ob->name);
    else if (arg->type == T_NUMBER)
	a_msg(ob,"%d", arg->u.number);
    else if (arg->type == T_POINTER)
	a_msg(ob,"<ARRAY>");
    else
	a_msg(ob,"<UNKNOWN>");
}
a_msg(ob,a,b,c,d,e)
    struct object *ob;
    char *a;
    int b,c,d,e;
{
    struct object *save_cmd_giver = command_giver;
    command_giver = ob;
    add_message(a,b,c,d,e);
    command_giver = save_cmd_giver;
}

/* Find an object. If not loaded, load it !
 * The object may selfdestruct, which is the only case when 0 will be
 * returned.
 */

struct object *
find_object(char *str)
{
    struct object *ob;

    /* Remove leading '/' if any. */
    while(str[0] == '/')
	str++;
    ob = find_object2(str);
    if (ob)
	return ob;
    ob = load_object(str, 0, 0);
    if (!ob || ob->flags & O_DESTRUCTED)		/* *sigh* */
	return 0;
    return ob;
}

/* same as find_object, but don't call 'create()' if not loaded */
struct object *find_object_no_create(str)
    char *str;
{
    struct object *ob;

    /* Remove leading '/' if any. */
    while(str[0] == '/')
	str++;
    ob = find_object2(str);
    if (ob)
	return ob;
    ob = load_object(str, 1, 0);
    if (!ob || ob->flags & O_DESTRUCTED)		/* *sigh* */
	return 0;
    return ob;
}

/* Look for a loaded object. Return 0 if non found. */
struct object *
find_object2(char *str)
{
    register struct object *ob;
    register int length;

    /* Remove leading '/' if any. */
    while(str[0] == '/')
	str++;
    /* Truncate possible .c in the object name. */
    length = strlen(str);
    if (str[length-2] == '.' && str[length-1] == 'c') {
	/* A new writreable copy of the name is needed. */
	char *p;
	p = (char *)alloca(strlen(str)+1);
	strcpy(p, str);
	str = p;
	str[length-2] = '\0';
    }
    if (ob = lookup_object_hash(str)) {
	return ob;
    }
    return 0;
}

#if 0

void 
apply_command(char *com)
{
    struct value *ret;

    if (command_giver == 0)
	error("command_giver == 0 !\n");
    ret = apply(com, command_giver->super, 0, 1);
    if (ret != 0)
    {
	add_message("Result:");
	if (ret->type == T_STRING)
	    add_message("%s\n", ret->u.string);
	if (ret->type == T_NUMBER)
	    add_message("%d\n", ret->u.number);
    }
    else
	add_message("Error apply_command: function %s not found.\n", com);
}
#endif /* 0 */

/*
 * Update actions
 *
 * Update an objects set of actions in all 'nearby' living objects.
 *
 * NOTE!
 *      It removes ALL actions defined by itself, including those added
 *	to itself. As actions added to onself is normally not done in init() 
 *	this will have to be taken care of specifically. This should be of
 *	little problem as living objects seldom export actions and would 
 *	therefore have little use of this efun.
 */
void 
update_actions(struct object *aob)
{
    struct object *pp, *ob, *next_ob;
    struct object *save_cmd = command_giver, *item = aob;
    struct svalue *ret;

    /*
     * Remove actions in objects environment and from objects in the
     * same environment. (parent and siblings)
     */
    if (item->super)
    {
	if (item->super->flags & O_ENABLE_COMMANDS)
	    remove_sent(item, item->super);

	for (pp = item->super->contains; pp;)
	{
	    if (pp != item && (pp->flags & O_ENABLE_COMMANDS))
		remove_sent(item, pp);
	    pp = item->next_inv;
	}
    }

    /*
     * Remove actions from objects inside the object. (children)
     */
    for (pp = item->contains; pp;)
    {
	if (pp->flags & O_ENABLE_COMMANDS)
	    remove_sent(item, pp);
	pp = item->next_inv;
    }

    /*
     * Setup the new commands. The order is very important, as commands
     * in the room should override commands defined by the room.
     * Beware that init() may have moved 'item' !
     */

    /* Save where object is, so as to note a move in init() 
     */
    ob = item->super; 

    /*
     * Add actions in objects environment and to objects in the
     * same environment. (parent and siblings)
     */
    if (item->super)
    {
	if (item->super->flags & O_ENABLE_COMMANDS)
	{
	    command_giver = item->super;
#ifdef USE_ENCOUNTER_NOT_INIT
	    push_object(item);
	    ret = apply("encounter", item->super, 1, 1);
	    if (!ret)
		(void)apply("init", item, 0, 1);
#else
	    (void)apply("init", item, 0, 1);
#endif
	    if (ob != item->super || 
		item->flags & O_DESTRUCTED ||
		ob->flags & O_DESTRUCTED)
	    {
		command_giver = save_cmd; /* marion */
		return;
	    }
	}

	for (pp = item->super->contains; pp;)
	{
	    next_ob = pp->next_inv;
	    if (pp != item && (pp->flags & O_ENABLE_COMMANDS))
	    {
		command_giver = pp;
#ifdef USE_ENCOUNTER_NOT_INIT
		push_object(item);
		ret = apply("encounter", pp, 1, 1);
		if (!ret)
		    (void)apply("init", item, 0, 1);
#else
		(void)apply("init", item, 0, 1);
#endif
		if (ob != item->super ||
		    item->flags & O_DESTRUCTED ||
		    ob->flags & O_DESTRUCTED ||
		    next_ob->flags & O_DESTRUCTED)
		{
		    command_giver = save_cmd; /* marion */
		    return;
		}
	    }
	    pp = next_ob;
	}
    }

    /*
     * Add actions to objects inside the object. (children)
     */
    for (pp = item->contains; pp;)
    {
	next_ob = pp->next_inv;
	if (pp->flags & O_ENABLE_COMMANDS)
	{
	    command_giver = pp;
#ifdef USE_ENCOUNTER_NOT_INIT
	    push_object(item);
	    ret = apply("encounter", pp, 1, 1);
	    if (!ret)
		(void)apply("init", item, 0, 1);
#else
	    (void)apply("init", item, 0, 1);
#endif
	    if (ob != item->super ||
		item->flags & O_DESTRUCTED ||
		ob->flags & O_DESTRUCTED ||
		next_ob->flags & O_DESTRUCTED)

	    {
		command_giver = save_cmd; /* marion */
		return;
	    }
	}
	pp = next_ob;
    }
}

/*
 * 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(struct object *dest)
{
    struct object **pp, *ob, *next_ob;
    struct object *save_cmd = command_giver, *item = current_object;
    struct svalue *ret;

    if (item->flags & O_DESTRUCTED)
	return;
    /* Recursive moves are not allowed. */
    for (ob = dest; ob; ob = ob->super)
	if (ob == item)
	    error("Can't move object inside itself.\n");
    if (item->shadowing)
	error("Can't move an object that is shadowing.\n");

    /*
     * The master object approves objects. Approved and nonapproved
     * objects can not be moved into each other.
     */
    if (s_flag)
	num_move++;

    if (item->super)
    {
	int okey = 0;
		
	if (item->flags & O_ENABLE_COMMANDS) 
	    remove_sent(item->super, item);

	if (item->super->flags & O_ENABLE_COMMANDS)
	    remove_sent(item, item->super);

	for (pp = &item->super->contains; *pp;)
	{
	    if (*pp != item)
	    {
		if ((*pp)->flags & O_ENABLE_COMMANDS)
		    remove_sent(item, *pp);
		if (item->flags & O_ENABLE_COMMANDS)
		    remove_sent(*pp, item);
		pp = &(*pp)->next_inv;
		continue;
	    }
	    *pp = item->next_inv;
	    okey = 1;
	}
	if (!okey)
	    fatal("Failed to find object %s in super list of %s.\n",
		  item->name, item->super->name);
    }
    item->next_inv = dest->contains;
    dest->contains = item;
    item->super = dest;
    /*
     * Setup the new commands. The order is very important, as commands
     * in the room should override commands defined by the room.
     * Beware that init() in the room may have moved 'item' !
     *
     * The call of init() should really be done by the object itself
     * It might be too slow, though :-(
     */
    if (item->flags & O_ENABLE_COMMANDS)
    {
	command_giver = item;
#ifdef USE_ENCOUNTER_NOT_INIT
	push_object(dest);
	ret = apply("encounter", item, 1, 1);
	if (!ret)
	    (void)apply("init", dest, 0, 1);
#else
	(void)apply("init", dest, 0, 1);
#endif
	if ((dest->flags & O_DESTRUCTED) || item->super != dest)
	{
	    command_giver = save_cmd; /* marion */
	    return;
	}
    }
    /*
     * Run init of the item once for every present player, and
     * for the environment (which can be a player).
     */
    for (ob = dest->contains; ob; ob=next_ob)
    {
	next_ob = ob->next_inv;
	if (ob == item)
	    continue;
	if (ob->flags & O_DESTRUCTED)
	    error("An object was destructed at call of init()\n");
	if (ob->flags & O_ENABLE_COMMANDS)
	{
	    command_giver = ob;
#ifdef USE_ENCOUNTER_NOT_INIT
	    push_object(item);
	    ret = apply("encounter", ob, 1, 1);
	    if (!ret)
		(void)apply("init", item, 0, 1);
#else
	    (void)apply("init", item, 0, 1);
#endif
	    if (dest != item->super)
	    {
		command_giver = save_cmd; 
		return;
	    }
	}
	if (item->flags & O_DESTRUCTED) /* marion */
	    error("The object to be moved was destructed at call of init()\n");
	if (item->flags & O_ENABLE_COMMANDS)
	{
	    command_giver = item;
#ifdef USE_ENCOUNTER_NOT_INIT
	    push_object(ob);
	    ret = apply("encounter", item, 1, 1);
	    if (!ret)
		(void)apply("init", ob, 0, 1);
#else
	    (void) apply("init", ob, 0, 1);
#endif
	    if (dest != item->super)
	    {
		command_giver = save_cmd;
		return;
	    }
	}
    }
    if (dest->flags & O_DESTRUCTED) /* marion */
	error("The destination to move to was destructed at call of init()\n");
    if (dest->flags & O_ENABLE_COMMANDS)
    {
	command_giver = dest;
#ifdef USE_ENCOUNTER_NOT_INIT
	push_object(item);
	ret = apply("encounter", dest, 1, 1);
	if (!ret)
	    (void)apply("init", item, 0, 1);
#else
	(void)apply("init", item, 0, 1);
#endif
    }
    command_giver = save_cmd;
}

struct sentence *sent_free = 0;
int tot_alloc_sentence;
int tot_current_alloc_sentence;

struct sentence *
alloc_sentence() 
{
    struct sentence *p;

    p = (struct sentence *) xalloc(sizeof *p);
    tot_alloc_sentence++;
    tot_current_alloc_sentence++;
    p->verb = 0;
    p->function = 0;
    p->next = 0;
    p->ob = 0;
    
    return p;
}

#ifdef free
void
free_all_sent()
{
    struct sentence *p;
    for (;sent_free; sent_free = p)
    {
	p = sent_free->next;
	free(sent_free);
    }
}
#endif

void 
free_sentence(struct sentence *p)
{
    if (p->ob)
	free_object(p->ob, "free_sentence");
    p->ob = 0;
    if (p->function)
	free_string(p->function);
    p->function = 0;
    if (p->verb)
	free_string(p->verb);
    p->verb = 0;
    free((char *)p);
    tot_current_alloc_sentence--;
    tot_alloc_sentence--;
}

/*
 * Find the sentence for a command from the player.
 * Return success status.
 */
int 
player_parser(char *buff)
{
    struct sentence *s;
    char *p;
    int length;
    struct object *save_current_object = current_object;
    char verb_copy[100], *subst_buff = NULL;
    struct svalue *ret;
    struct object *cmd_giver = command_giver;
#ifdef DEBUG
    if (d_flag & DEBUG_COMMAND)
	debug_message("cmd [%s]: %s\n", cmd_giver->name, buff);
#endif
    /* 
     * Strip trailing spaces. 
     */
    for (p = buff + strlen(buff) - 1; p >= buff; p--)
    {
	if (*p != ' ')
	    break;
	*p = '\0';
    }
    /*
     * Strip leading spaces.
     */
    while(*buff == ' ')
	buff++;

    if (buff[0] == '\0')
	return 0;

    /*
     * Quicktyper hook.
     */
    push_string(buff, STRING_MALLOC);
    push_object(cmd_giver);
    ret = apply_master_ob(M_MODIFY_COMMAND, 2);
    
    if (ret && ret->type == T_STRING)
	subst_buff = string_copy(ret->u.string);
    
    if (subst_buff == NULL)
	return 1;

    p = strchr(subst_buff, ' ');
    if (p == 0)
	length = strlen(subst_buff);
    else
	length = p - subst_buff;
    if (!*subst_buff ||
	!cmd_giver || cmd_giver->flags & O_DESTRUCTED)
	return 1;

    clear_notify();
    for (s = cmd_giver->sent; s; s = s->next) 
    {
	struct svalue *ret;
	int len;
	struct object *command_object;
	
	if (s->verb == 0)
	    error("An 'action' did something, but returned 0 or had an undefined verb.\n");
	len = strlen(s->verb);
	if (s->short_verb)
	{
	    if (strncmp(s->verb, subst_buff, len) != 0)
		continue;
	}
	else
	{
	    if (len != length) continue;
	    if (strncmp(subst_buff, s->verb, length))
		continue;
	}
	/*
	 * Now we have found a special sentence !
	 */
#ifdef DEBUG
	if (d_flag & DEBUG_COMMAND)
	    debug_message("Local command %s on %s\n", s->function, s->ob->name);
#endif
	if (length >= sizeof verb_copy)
	    len = sizeof verb_copy - 1;
	else
	    len = length;
	strncpy(verb_copy, subst_buff, len);
	verb_copy[len] = '\0';
	last_verb = verb_copy;
	/*
	 * If the function is static and not defined by current object,
	 * then it will fail. If this is called directly from player input,
	 * then we set current_object so that static functions are allowed.
	 * current_object is reset just after the call to apply().
	 */
	if (current_object == 0)
	    current_object = s->ob;
	/*
	 * Remember the object, to update score.
	 */
	command_object = s->ob;
	command_giver = cmd_giver;

#ifdef  STATIC_ADD_ACTIONS
	if (search_for_function(s->function, s->ob->prog))
	{
	    if (function_type_mod_found & TYPE_MOD_PRIVATE
		/* && inh_offset < function_inherit_found -
		   function_prog_found->num_inherited + 1 */ )
		error("Atempted call of private function.\n");

	    if (subst_buff[length] == ' ')
	    {
		push_string(&subst_buff[length+1], STRING_MALLOC);
		call_function(s->ob, function_inherit_found,
			      function_index_found, 1);
	    }
	    else
	    {
		call_function(s->ob, function_inherit_found,
			      function_index_found, 0);
	    }
	    if (cmd_giver == 0)
	    {
		if (subst_buff) 
		    free(subst_buff);
		return 1;
	    }
	    
	    current_object = save_current_object;
	    last_verb = 0;
	    /* If we get fail from the call, it was wrong second argument. */
	    if (sp->type == T_NUMBER && sp->u.number == 0)
	    {
		/* Has the sentences been lost? */
		/* Does anyone know a better way of doing this? */
		struct sentence *s0;
		
		pop_stack();

		for (s0 = cmd_giver->sent;
		     s0 && s0 != s;
		     s0 = s0->next) ;
		
		if (!s0)
		{
		    s = 0;
		    break;
		}
		continue;
	    }
	    else
		pop_stack();
	}
	else
	    add_message("Error: function %s not found.\n", s->function);
#else
	if (subst_buff[length] == ' ')
	{
	    push_string(&subst_buff[length+1], STRING_MALLOC);
	    ret = apply(s->function, s->ob, 1, 0);
	}
	else
	{
	    ret = apply(s->function, s->ob, 0, 0);
	}
	
	if (cmd_giver == 0)
	{
	    if (subst_buff) 
		free(subst_buff);
	    return 1;
	}

	current_object = save_current_object;
	last_verb = 0;
	/* If we get fail from the call, it was wrong second argument. */
	if (ret && ret->type == T_NUMBER && ret->u.number == 0)
	{
	    /* Has the sentences been lost? */
	    /* Does anyone know a better way of doing this? */
	    struct sentence *s0;
	    
	    for (s0 = cmd_giver->sent;
		 s0 && s0 != s;
		 s0 = s0->next) ;
	    
	    if (!s0)
	    {
		s = 0;
		break;
	    }
	    continue;
	}
	
	if (ret == 0)
	    add_message("Error: function %s not found.\n", s->function);
#endif
	break;
    }
    if (subst_buff)
        free(subst_buff);
    if (s == 0)
    {
	notify_no_command();
	return 0;
    }
    return 1;
}

/*
 * Associate a command with function in this object.
 * The optional second argument is the command name. 
 *
 * The optional third argument is a flag that will state that the verb should
 * only match against leading characters.
 *
 * The object must be near the command giver, so that we ensure that the
 * sentence is removed when the command giver leaves.
 *
 * If the call is from a shadow, make it look like it is really from
 * the shadowed object.
 */
int 
add_action(struct svalue *func, struct svalue *cmd, int flag)
{
    struct sentence *p;
    struct object *ob;

    if (current_object->flags & O_DESTRUCTED)
	return -1;
    ob = current_object;
    if (command_giver == 0 || (command_giver->flags & O_DESTRUCTED))
	return -1;
    if (ob != command_giver && ob->super != command_giver &&
	ob->super != command_giver->super && ob != command_giver->super)
      error("add_action from object that was not present.\n");
#ifdef DEBUG
    if (d_flag & DEBUG_ADD_ACTION)
	debug_message("--Add action %s\n", func);
#endif
    if (func->u.string[0] == ':' || func->u.string[0] == '.')
	error("Illegal function name: %s\n", func->u.string);
    p = alloc_sentence();
    p->function = make_shared_string(func->u.string);
    p->ob = ob;
    add_ref(ob, "add_action");
    p->next = command_giver->sent;
    p->short_verb = flag;
    if (cmd)
 	p->verb = make_shared_string(cmd->u.string);
    else
	p->verb = 0;
    command_giver->sent = p;
    return 0;
	
}

/*
 * Remove all commands (sentences) defined by object 'ob' in object
 * 'player'
 */
void 
remove_sent(struct object *ob, struct object *player)
{
    struct sentence **s;

    for (s= &player->sent; *s;)
    {
	struct sentence *tmp;
	if ((*s)->ob == ob) {
	    if (d_flag & DEBUG_SENTENCE)
		debug_message("--Unlinking sentence %s\n", (*s)->function);
	    tmp = *s;
	    *s = tmp->next;
	    free_sentence(tmp);
	} else
	    s = &((*s)->next);
    }
    
}


static char debinf[8192];
int swap_out_obj_sec, swap_out_obj_min, swap_out_obj_hour;
int swap_out_prog_sec, swap_out_prog_min, swap_out_prog_hour;
int swap_in_obj_sec, swap_in_obj_min, swap_in_obj_hour;
int swap_in_prog_sec, swap_in_prog_min, swap_in_prog_hour;

char *
get_gamedriver_info(char *str)
{
    char tmp[1024];
    int tries, hits;
    float proc;
    int tot, tot_num, res, verbose = 0;
    extern char *reserved_area;
    extern int tot_alloc_sentence, tot_current_alloc_sentence,
	tot_alloc_object, tot_alloc_object_size, num_swapped,
	total_bytes_swapped, num_arrays, total_array_size, num_mappings,
	total_mapping_size;
    extern int num_strings_swapped, size_strings_swapped;
    extern int obj_swapped, obj_bytes_swapped, total_lineno_swapped;
    extern int total_num_prog_blocks, total_prog_block_size;
    extern int num_distinct_strings, bytes_distinct_strings;
    extern int overhead_bytes();
    extern int num_call, call_out_size;
    extern int globcache_tries, globcache_hits, funmap_tries, funmap_hits;
    extern struct vector null_vector;
    extern int cache_tries, cache_hits;
    extern int program_bytes_swapped, total_program_size, programs_swapped;
    extern int tot_alloc_variable_size;
    extern int allocated_swap, allocated_swap_blocks;
    extern int last_address;
    extern int num_swapped_arrays, size_swapped_arrays;
    extern int num_swapped_mappings, size_swapped_mappings;
    extern int max_swap_memory, min_swap_memory;
    extern int max_swap_time, min_swap_time;
    int meg, kilo, byte, hour, min, sec;
    extern int used_memory;
    extern struct program *swap_prog;
    extern struct object *swap_ob;
    
#ifdef COMM_STAT
    extern int add_message_calls,inet_packets,inet_volume;
#endif
    strcpy(debinf,"");

    if (strcmp(str, "status") == 0 || strcmp(str, "status tables") == 0)
    {

#ifdef COMM_STAT
	extern int add_message_calls,inet_packets,inet_volume;
#endif

	if (strcmp(str, "status tables") == 0)
	    verbose = 1;
	if (reserved_area)
	    res = RESERVED_SIZE;
	else
	    res = 0;
#ifdef COMM_STAT

	if (verbose)
	{
	    sprintf(tmp,"Calls to add_message: %d   Packets: %d   Average packet size: %f\n\n",add_message_calls,inet_packets,(float)inet_volume/inet_packets);
	    strcat(debinf, tmp);
	}
#endif
	if (!verbose)
	{
	    sprintf(tmp,"Sentences:\t\t\t%8d %8d\n", tot_alloc_sentence,
		    tot_alloc_sentence * sizeof (struct sentence));
	    strcat(debinf, tmp);

	    sprintf(tmp,"Objects:\t\t\t%8d %8d (%d dest, %d rmd)\n",
		    tot_alloc_object, tot_alloc_object_size,
		    tot_alloc_dest_object, tot_removed_object);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Variables:\t\t\t\t %8d (%d swapped, %d Kbytes)\n",
		    tot_alloc_variable_size,
		    obj_swapped, (unsigned int)obj_bytes_swapped / 1024);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Arrays:\t\t\t\t%8d %8d (%d swapped, %d Kbytes)\n",
		    num_arrays,	total_array_size,
		    num_swapped_arrays, size_swapped_arrays / 1024);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Call_outs:\t\t\t%8d %8d\n", num_call,
			call_out_size);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Mappings:\t\t\t%8d %8d (%d swapped, %d Kbytes)\n",
		    num_mappings, total_mapping_size,
		    num_swapped_mappings, size_swapped_mappings / 1024);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Strings:\t\t\t%8d %8d (%d overhead)\n",
		    num_distinct_strings, 
		    bytes_distinct_strings + overhead_bytes(), 
		    overhead_bytes());
	    strcat(debinf, tmp);
	    sprintf(tmp,"\t\t\t\t\t\t  (%d swapped, %d Kbytes)\n",
		    num_strings_swapped, size_strings_swapped >> 10);
	    strcat(debinf, tmp);
	    
	    sprintf(tmp,"Prog blocks:\t\t\t%8d %8d\n",
		    total_num_prog_blocks, total_prog_block_size);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Programs:\t\t\t\t %8d (%d swapped, %d Kbytes)\n",
		    total_program_size, programs_swapped, program_bytes_swapped / 1024);
	    strcat(debinf, tmp);

	    sprintf(tmp,"Memory reserved:\t\t\t %8d\n", res);
	    strcat(debinf, tmp);
	
	}
	if (verbose)
	{
#ifdef CACHE_STATS
	    extern int call_cache_saves;
	    extern int global_cache_saves;
	    extern int searches_needed;
	    extern int searches_done;
	    extern int global_first_saves;
	    extern int call_first_saves;
#endif

	    if (globcache_tries < 1)
		globcache_tries = 1;
	    if (funmap_tries < 1)
		funmap_tries = 1;
	    strcat(debinf, stat_living_objects());
	    strcat(debinf, "\nFunction calls:\n--------------------------\n");
	    proc = 100.0 * ((cache_hits * 1.0) / cache_tries);
	    sprintf(tmp,"Call cache,   Tries: %10d Hits: %10d Miss: %10d Rate: %3.1f%%\n",
		    cache_tries, cache_hits,
		    cache_tries - cache_hits, proc);
	    strcat(debinf, tmp);
#ifdef CACHE_STATS
	    sprintf(tmp, "searches saved       %10d %3d%%\n",
		    call_first_saves,
		    (int)(100.0 * ((call_first_saves * 1.0) /
				   (searches_needed +
				    global_first_saves
				    + call_first_saves))));
	    strcat(debinf, tmp);
#endif	    
	    
#ifdef GLOBAL_CACHE
	    proc = 100.0 * ((globcache_hits * 1.0) / globcache_tries);
	    sprintf(tmp,"Global cache, Tries: %10d Hits: %10d Miss: %10d Rate: %3.1f%%\n",
		    globcache_tries, globcache_hits,
		    globcache_tries -globcache_hits, proc);
	    strcat(debinf, tmp);
#ifdef CACHE_STATS
	    sprintf(tmp, "searches saved       %10d %3d%%\n",
		    global_first_saves,
		    (int)(100.0 * ((global_first_saves * 1.0) /
				   (searches_needed +
				    global_first_saves
				    + call_first_saves))));
	    strcat(debinf, tmp);
#endif
#endif

#ifdef CACHE_STATS
	    strcat(debinf, "Secondary hits:\n");
	    sprintf(tmp,"searches needed    : %10d\n", searches_needed);
	    strcat(debinf, tmp);
	    sprintf(tmp,"call cache saves   : %10d %3d%%\n",
		    call_cache_saves, (int)(100.0 * ((call_cache_saves * 1.0) /
						     searches_needed)));
	    strcat(debinf, tmp);
	    sprintf(tmp,"global cache saves : %10d %3d%%\n",
		    global_cache_saves,
		    (int)(100.0 * ((global_cache_saves * 1.0) /
				   searches_needed)));
	    strcat(debinf, tmp);
	    sprintf(tmp,"searches done      : %10d\n", searches_done);
	    strcat(debinf, tmp);
	    sprintf(tmp,"Total needed       : %10d %3d%% done\n",
		    searches_needed + global_first_saves + call_first_saves,
		    (int)(100.0 * ((searches_done * 1.0) /
				   (searches_needed +
				    global_first_saves
				    + call_first_saves))));
	    strcat(debinf, tmp);
#endif
#ifdef USE_SWAP
	    sprintf(tmp, "\nSwap:\n--------------------------------------\n");
	    strcat(debinf, tmp);
	    sprintf(tmp, "Allocated: %8d Kbytes in %8d blocks.\n",
		    allocated_swap >> 10,  allocated_swap_blocks);
	    strcat(debinf, tmp);
	    sprintf(tmp, "Swapped:   %8d Kbytes in %8d blocks.\n",
		    total_bytes_swapped >> 10, num_swapped);
	    strcat(debinf, tmp);
	    sprintf(tmp, "File:      %8d Kbytes.\n",
		    last_address >> 10);
	    strcat(debinf, tmp);
	    sprintf(tmp, "Time:      %8d seconds.\n", current_time -
		    (swap_prog->time_of_ref < swap_ob->time_of_ref ?
		     swap_prog->time_of_ref :
		     swap_prog->time_of_ref));
	    strcat(debinf, tmp);
	    sprintf(tmp,"Objects:   %d/%d/%d in, %d/%d/%d out.\n",
		    swap_in_obj_sec, swap_in_obj_min, swap_in_obj_hour,
		    swap_out_obj_sec, swap_out_obj_min, swap_out_obj_hour);
	    strcat(debinf, tmp);
	    sprintf(tmp,"Programs:  %d/%d/%d in, %d/%d/%d out.\n",
		    swap_in_prog_sec, swap_in_prog_min, swap_in_prog_hour,
		    swap_out_prog_sec, swap_out_prog_min, swap_out_prog_hour);
	    strcat(debinf, tmp);
#endif
	    add_string_status(debinf);

	    sprintf(tmp, "\nReferences:\n--------------------------------\n");
	    strcat(debinf, tmp);
	    sprintf(tmp, "Null vector: %d\n", null_vector.ref);
	    strcat(debinf, tmp);
        }
    
	tot =		   total_prog_block_size +
	                   total_program_size +
	                   tot_alloc_sentence * sizeof(struct sentence) +
	                   total_array_size +
                           tot_alloc_variable_size +
		           call_out_size +
		           total_mapping_size +
			   bytes_distinct_strings + overhead_bytes() +
			   tot_alloc_object_size + res;


#if 0
	+
	    show_otable_status(verbose) +
		heart_beat_status(verbose) +
			print_call_out_usage(verbose) +
			   res;
#endif


	if (!verbose) 
	{
#ifdef USE_SWAP
	    if (used_memory)
	    {
		sprintf(tmp,"Other:\t\t\t\t\t %8d\n", used_memory - tot);
		strcat(debinf, tmp);
		tot = used_memory;
	    }
#endif
	    sprintf(tmp,"\t\t\t\t\t --------\n");
	    strcat(debinf, tmp);
	    sprintf(tmp,"Total:\t\t\t\t\t %8d\n", tot);
	    strcat(debinf, tmp);
	}
    }
    return string_copy(debinf);
}

struct vector *
get_local_commands(struct object *ob)
{
    struct sentence *s;
    struct vector *ret;
    struct vector *ret2;
    int num;

    for (num = 0, s = ob->sent; s; s = s->next) 
	num++;

    ret2 = allocate_array(num);

    for (num = 0, s = ob->sent; s; s = s->next, num++)
    {
	ret2->item[num].type = T_POINTER;
	ret2->item[num].u.vec = ret = allocate_array(4);

	ret->item[0].type = T_STRING;
	ret->item[0].string_type = STRING_SHARED;
	ret->item[0].u.string = s->verb;
	increment_string_ref(s->verb);

	ret->item[1].type = T_NUMBER;
	ret->item[1].u.number = s->short_verb;

	ret->item[2].type = T_OBJECT;
	ret->item[2].u.ob = s->ob;
	add_ref(s->ob,"get_local_commands");

	ret->item[3].type = T_STRING;
	ret->item[3].string_type = STRING_SHARED;
	ret->item[3].u.string = s->function;
	increment_string_ref(s->function);
    }
    return ret2;
}

/*VARARGS1*/
void
fatal(va_alist)
va_dcl
{

    va_list argp;
    char *fmt, *f;
    char buf[512];

    static int in_fatal = 0;
    /* Prevent double fatal. */
    if (in_fatal)
	abort();
    in_fatal = 1;

    va_start(argp);
    fmt = va_arg(argp, char *);
    va_end(argp);

    vfprintf(stderr, fmt, argp);

    fflush(stderr);
    if (current_object)
	(void) fprintf(stderr, "Current object was %s\n",
		      current_object->name);
    vsprintf(buf, fmt, argp);
    debug_message("%s", buf);

    if (current_object)
	debug_message("Current object was %s\n", current_object->name);
    debug_message("Dump of variables:\n");
    (void) dump_trace(1);
    abort();
}

int num_error = 0;

/*
 * 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() 
{
    extern int error_recovery_context_exists;
    extern jmp_buf error_recovery_context;
    extern struct svalue catch_value;
    if (error_recovery_context_exists > 1)
    {
	longjmp(error_recovery_context, 1);
	fatal("Throw_error failed!");
    }
    if (catch_value.type == T_STRING)
	error(catch_value.u.string);
    else
	error("Throw with no catch.\n");
}

static char emsg_buf[2000];

/*VARARGS1*/
void
error(va_alist)
va_dcl
{
    extern int error_recovery_context_exists;
    extern jmp_buf error_recovery_context;
    extern struct object *current_heart_beat;
    extern struct svalue catch_value;
    char *object_name;

    va_list argp;
    char *fmt, *f;
    int i;

    va_start(argp);
    fmt = va_arg(argp, char *);
    vsprintf(emsg_buf + 1, fmt, argp);
    va_end(argp);

    emsg_buf[0] = '*';	/* all system errors get a * at the start */
    if (error_recovery_context_exists > 1)
    { /* user catches this error */
	struct svalue v;
	v.type = T_STRING;
	v.u.string = emsg_buf;
	v.string_type = STRING_MALLOC;	/* Always reallocate */
	assign_svalue(&catch_value, &v);
   	longjmp(error_recovery_context, 1);
   	fatal("Catch() longjump failed");
    }
    num_error++;
    if (num_error <= 1)
    {
	
	debug_message("%s", emsg_buf+1);

	object_name = dump_trace(0);
	fflush(stdout);
	if (object_name) {
	    struct object *ob;
	    ob = find_object2(object_name);
	    if (!ob) {
		debug_message("error when executing program in destroyed object %s\n",
			      object_name);
	    }
	}
	/* 
	 * The stack must be brought in a usable state. After the
	 * call to reset_machine(), all arguments to error() are invalid,
	 * and may not be used any more. The reason is that some strings
	 * may have been on the stack machine stack, and has been deallocated.
	 */
	reset_machine (0);
	push_string(emsg_buf, STRING_MALLOC);
	if (current_object)
	{
	    push_object(current_object);

	    if (current_prog)
		push_string(current_prog->name, STRING_MALLOC);
	    else
		push_constant_string("<???>");

	    push_string(get_srccode_position_if_any(), STRING_MALLOC);
	}
	else
	{
	    push_number(0);
	    push_constant_string("<???>");
	    push_constant_string("");
	}
	apply_master_ob(M_RUNTIME_ERROR, 4);
    }
    else
    {
	debug_message("Too many simultaneous errors.\n");
    }
    
    num_error--;
    if (error_recovery_context_exists)
	longjmp(error_recovery_context, 1);
    abort();
}

/*
 * Check that it is an legal path. No '..' are allowed.
 */
int 
legal_path(char *path)
{
    char *p;

    if (path == NULL || strchr(path, ' '))
	return 0;
    if (path[0] == '/')
        return 0;
    for(p = strchr(path, '.'); p; p = strchr(p+1, '.'))
    {
	if (p[1] == '.')
	    return 0;
    }
    return 1;
}

/*
 * There is an error in a specific file. Ask the game driver to log the
 * message somewhere.
 */
static struct error_msg {
    struct error_msg *next;
    char *file;
    char *msg;
} *smart_log_msg = NULL;
void
init_smart_log()
{
    struct error_msg *prev;

    while (smart_log_msg)
    {
	prev = smart_log_msg;
	smart_log_msg = smart_log_msg->next;
	free(prev->file);
	free(prev->msg);
	free((char *)prev);
    }
}

void
dump_smart_log()
{
    struct error_msg *prev;

    if (!master_ob)
    {
	init_smart_log();
	return;
    }

    while (smart_log_msg)
    {
	prev = smart_log_msg;
	smart_log_msg = smart_log_msg->next;
	push_malloced_string(prev->file);
	push_malloced_string(prev->msg);
	free((char *)prev);
	apply_master_ob(M_LOG_ERROR, 2);
    }
}

void 
smart_log(char *error_file, int line, char *what)
{
    char buff[500];
    struct error_msg **last;

    for (last = &smart_log_msg; *last; last = &(*last)->next)
    if (error_file == 0)
	return;
    if (strlen(what) + strlen(error_file) > sizeof buff - 100)
	what = "...[too long error message]...";
    if (strlen(what) + strlen(error_file) > sizeof buff - 100)
	error_file = "...[too long filename]...";
    sprintf(buff, "%s line %d:%s\n", error_file, line, what);
    *last = (struct error_msg *)xalloc(sizeof(struct error_msg));
    (*last)->next = NULL;
    (*last)->file = string_copy(error_file);
    (*last)->msg = string_copy(buff);
}

/*
 * Check that a path to a file is valid for read or write.
 * This is done by functions in the master object.
 * The path is always treated as an absolute path, and is returned without
 * a leading '/'.
 * If the path was '/', then '.' is returned.
 * The returned string may or may not be residing inside the argument 'path',
 * so don't deallocate arg 'path' until the returned result is used no longer.
 * Otherwise, the returned path is temporarily allocated by apply(), which
 * means it will be dealocated at next apply().
 */
char *
check_valid_path(char *path, struct object *ob,
                 char *call_fun, int writeflg)
{
    struct svalue *v;

    push_string(path, STRING_MALLOC);
    push_object(ob);
    push_string(call_fun, STRING_MALLOC);
    if (writeflg)
	v = apply_master_ob(M_VALID_WRITE, 3);
    else
	v = apply_master_ob(M_VALID_READ, 3);
    if (v && v->type == T_NUMBER && v->u.number == 0)
	return 0;
    if (path[0] == '/')
	path++;
    if (path[0] == '\0')
	path = ".";
    if (legal_path(path))
	return path;
    return 0;
}

/*
 * This one is called from HUP.
 */
int game_is_being_shut_down;
extern volatile int interupted;

void 
startshutdowngame(int arg) 
{
    game_is_being_shut_down = 1;
    interupted = 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 
shutdowngame() 
{
    ipc_remove();
    remove_all_players();
    unlink_swap_file();
#ifdef DEALLOCATE_MEMORY_AT_SHUTDOWN
    remove_all_objects();
    free_all_sent();
#ifndef MALLOC_sysmalloc
    dump_malloc_data();
#endif
    find_alloced_data();
#endif
#ifdef OPCPROF
    opcdump();
#endif
    exit(0);
}


/*
 * Call this one when there is only little memory left.
 * We tell master object of our troubles and hope it does something
 * intelligent, like starting Armageddon.
 */
void 
slow_shut_down(int minutes)
{
    struct object *ob;

    /*
     * Swap out objects, and free some memory.
     */
    push_number(minutes);
    (void) apply_master_ob(M_MEMORY_FAILURE, 1);
}

int 
match_string(char *match, char *str)
{
    int i;

 again:
    if (*str == '\0' && *match == '\0')
	return 1;
    switch(*match)
    {
    case '?':
	if (*str == '\0')
	    return 0;
	str++;
	match++;
	goto again;
    case '*':
	match++;
	if (*match == '\0')
	    return 1;
	for (i=0; str[i] != '\0'; i++)
	    if (match_string(match, str+i))
		return 1;
	return 0;
    case '\0':
	return 0;
    case '\\':
	match++;
	if (*match == '\0')
	    return 0;
	/* Fall through ! */
    default:
	if (*match == *str) {
	    match++;
	    str++;
	    goto again;
	}
	return 0;
    }
}

/*
 * Credits for some of the code below goes to Free Software Foundation
 * Copyright (C) 1990 Free Software Foundation, Inc.
 * See the GNU General Public License for more details.
 */
#ifndef S_ISDIR
#define	S_ISDIR(m)	(((m)&S_IFMT) == S_IFDIR)
#endif

#ifndef S_ISREG
#define	S_ISREG(m)	(((m)&S_IFMT) == S_IFREG)
#endif

int
isdir(char *path)
{
  struct stat stats;

  return stat (path, &stats) == 0 && S_ISDIR (stats.st_mode);
}

void
strip_trailing_slashes(char *path)
{
    int last;
    
    last = strlen (path) - 1;
    while (last > 0 && path[last] == '/')
	path[last--] = '\0';
}

struct stat to_stats, from_stats;

int
copy (char *from, char *to)
{
    int ifd;
    int ofd;
    char buf[1024 * 8];
    int len;			/* Number of bytes read into buf. */
  
    if (!S_ISREG (from_stats.st_mode))
    {
        error("cannot move `%s' across filesystems: Not a regular file\n", from);
	return 1;
    }
  
    if (unlink (to) && errno != ENOENT)
    {
	error ("cannot remove `%s'\n", to);
	return 1;
    }

    ifd = open (from, O_RDONLY, 0);
    if (ifd < 0)
    {
	error ("%s: open failed\n", from);
	return errno;
    }
    ofd = open (to, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (ofd < 0)
    {
	error ("%s: open failed\n", to);
	close (ifd);
	return 1;
    }
#ifndef FCHMOD_MISSING
    if (fchmod (ofd, from_stats.st_mode & 0777))
    {
	error ("%s: fchmod failed\n", to);
	close (ifd);
	close (ofd);
	unlink (to);
	return 1;
    }
#endif
  
    while ((len = read (ifd, buf, sizeof (buf))) > 0)
    {
	int wrote = 0;
	char *bp = buf;
	
	do
	{
	    wrote = write (ofd, bp, len);
	    if (wrote < 0)
	    {
		error ("%s: write failed\n", to);
		close (ifd);
		close (ofd);
		unlink (to);
		return 1;
	    }
	    bp += wrote;
	    len -= wrote;
	} while (len > 0);
    }
    if (len < 0)
    {
	error ("%s: read failed\n", from);
	close (ifd);
	close (ofd);
	unlink (to);
	return 1;
    }

    if (close (ifd) < 0)
    {
	error ("%s: close failed", from);
	close (ofd);
	return 1;
    }
    if (close (ofd) < 0)
    {
	error ("%s: close failed", to);
	return 1;
    }
  
#ifdef FCHMOD_MISSING
    if (chmod (to, from_stats.st_mode & 0777))
    {
	error ("%s: chmod failed\n", to);
	return 1;
    }
#endif

    return 0;
}

/* Move FROM onto TO.  Handles cross-filesystem moves.
   If TO is a directory, FROM must be also.
   Return 0 if successful, 1 if an error occurred.  */

int
do_move (char *from, char *to)
{
    if (lstat (from, &from_stats) != 0)
    {
	error ("%s: lstat failed\n", from);
	return 1;
    }

    if (lstat (to, &to_stats) == 0)
    {
	if (from_stats.st_dev == to_stats.st_dev
	    && from_stats.st_ino == to_stats.st_ino)
	{
	    error ("`%s' and `%s' are the same file", from, to);
	    return 1;
	}

	if (S_ISDIR (to_stats.st_mode))
	{
	    error ("%s: cannot overwrite directory", to);
	    return 1;
	}

    }
    else if (errno != ENOENT)
    {
	error ("%s: unknown error\n", to);
	return 1;
    }
#if defined(SYSV) && !defined(_SEQUENT_)
    if (isdir(from))
    {
	char cmd_buf[100];
	sprintf(cmd_buf, "/usr/lib/mv_dir %s %s", from, to);
	return system(cmd_buf);
  } else
#endif /* SYSV */      
      if (rename (from, to) == 0)
    {
	return 0;
    }

    if (errno != EXDEV)
    {
	error ("cannot move `%s' to `%s'", from, to);
	return 1;
    }

  /* rename failed on cross-filesystem link.  Copy the file instead. */

    if (copy (from, to))
	return 1;
  
    if (unlink (from))
    {
	error ("cannot remove `%s'", from);
	return 1;
    }

    return 0;
}
    
/*
 * do_rename is used by the efun rename. It is basically a combination
 * of the unix system call rename and the unix command mv. Please shoot
 * the people at ATT who made Sys V.
 *
 * WARNING!  (Thanks to Sul@Genesis, pmidden@tolkien.helios.nd.edu)
 *
 *   If you have a typical 'lpmud ftpdaemon' that allow wizards to ftp
 *   to their homedirs and if they are allowed to move subdirs from
 *   under their wizdirs to for example /open then they can use ftp to
 *   access the entire host machine.
 *
 *   It is therefore important to disallow these directory moves in
 *   your /secure/master.c
 */

int
do_rename(char * fr, char *t)
{
    char *from, *to;
    
    from = check_valid_path(fr, current_object, "do_rename_from", 1);
    if(!from)
	return 1;
    to = check_valid_path(t, current_object, "do_rename_to", 1);
    if(!to)
	return 1;
    if(!strlen(to) && !strcmp(t, "/"))
    {
	to = (char *)alloca(3);
	sprintf(to, "./");
    }
    strip_trailing_slashes (from);
    if (isdir (to))
    {
	/* Target is a directory; build full target filename. */
	char *cp;
	char *newto;
	
	cp = strrchr (from, '/');
	if (cp)
	    cp++;
	else
	    cp = from;
	
	newto = (char *) alloca (strlen (to) + 1 + strlen (cp) + 1);
	sprintf (newto, "%s/%s", to, cp);
	return do_move (from, newto);
    }
    else
	return do_move (from, to);
}