/*--------------------------------------------------------------------------- * The runtime module. * *--------------------------------------------------------------------------- * simulate is a collection of structures and functions which provide the * basic runtime functionality: * * - the object list * - loading, cloning, and destructing objects * - the runtime context stack * - error handling * - function callbacks * - management of the driver hooks * - handling of object inventories and shadows. * - a few file efuns. * * The data structures, especially the runtime stack, are described where * they are defined. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include "my-alloca.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <setjmp.h> #include <stdio.h> #include <sys/stat.h> #include <stdarg.h> #if defined(AMIGA) && !defined(__GNUC__) # include "hosts/amiga/nsignal.h" #else # include <signal.h> #endif #if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION) # include <dirent.h> # define generic_dirent dirent # define DIRENT_NLENGTH(dirent) (strlen((dirent)->d_name)) #else /* not (DIRENT or _POSIX_VERSION) */ # define generic_dirent direct # define DIRENT_NLENGTH(dirent) ((dirent)->d_namlen) # ifdef HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif /* SYSNDIR */ # ifdef HAVE_SYS_DIR_H # include <sys/dir.h> # endif /* SYSDIR */ # ifdef HAVE_NDIR_H # include <ndir.h> # endif /* NDIR */ #endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */ #if defined(CYGWIN) extern int lstat(const char *, struct stat *); #endif #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 #ifdef SunOS4 # if !defined (__GNUC__) || __GNUC__ < 2 || __GNUC__ == 2 && __GNUC_MINOR__ < 7 extern int lstat PROT((CONST char *, struct stat *)); # endif extern int fchmod PROT((int, int)); #endif #if defined(OS2) || defined(__EMX__) # define lstat stat #endif /*-------------------------------------------------------------------------*/ #include "simulate.h" #include "actions.h" #include "array.h" #include "backend.h" #include "call_out.h" #include "closure.h" #include "comm.h" #include "dumpstat.h" #include "ed.h" #include "exec.h" #include "filestat.h" #include "gcollect.h" #include "heartbeat.h" #include "interpret.h" #include "instrs.h" #include "lex.h" #include "main.h" #include "mapping.h" #include "object.h" #include "otable.h" #include "prolang.h" #include "rxcache.h" #include "sent.h" #include "simul_efun.h" #include "stdstrings.h" #include "stralloc.h" #include "strfuns.h" #include "swap.h" #include "svalue.h" #include "wiz_list.h" #include "xalloc.h" #include "../mudlib/sys/debug_info.h" #include "../mudlib/sys/driver_hook.h" #include "../mudlib/sys/files.h" #include "../mudlib/sys/rtlimits.h" /*-------------------------------------------------------------------------*/ /* --- struct limits_context_s: last runtime limits context --- * * This structure saves the runtime limits on the runtime context stack. * It is also used as a temporary when parsing limit specifications. */ struct limits_context_s { rt_context_t rt; /* the rt_context superclass */ size_t max_array; /* max array size */ size_t max_mapping; /* max mapping size */ int32 max_eval; /* max eval cost */ int32 max_byte; /* max byte xfer */ int32 max_file; /* max file xfer */ int32 max_callouts; /* max callouts */ int32 eval_cost; /* the then-current eval costs used */ }; /* --- struct give_uid_error_context --- * * A structure of this type is pushed as error handler on the * interpreter stack while a newly created object is given its uids. */ struct give_uid_error_context { svalue_t head; /* A T_ERROR_HANDLER with this struct as arg */ object_t *new_object; /* The object under processing */ }; /* --- struct namechain --- * * This structure is used by load_object() to build the current inheritence tree * in the frames on the stack. The information is used to generate * proper error messages. */ typedef struct namechain_s { struct namechain_s * prev; /* Pointer to the previous element, or NULL */ char * name; /* Pointer to the name to load */ } namechain_t; /*-------------------------------------------------------------------------*/ /* The runtime context stack. * * Runtime context informations are maintained in a linked list, with * cur_context pointing to the most recently pushed context. * From there, the links go back through the less recently pushed contexts * and end with the toplevel_context. */ struct error_recovery_info toplevel_context = { { NULL, ERROR_RECOVERY_NONE } }; rt_context_t * rt_context = (rt_context_t *)&toplevel_context; /*-------------------------------------------------------------------------*/ static p_int alloc_shadow_sent = 0; /* Statistic: how many shadow sentences have been allocated. */ static sentence_t * free_sent = NULL; /* List of allocated but unused shadow sentences. */ object_t *obj_list = NULL; /* Head of the list of all objects. The reference by this list * is counted. * The first object in the list has its .prev_all member cleared. */ object_t *obj_list_end = NULL; /* Last object in obj_list. This object also has its .next_all member * cleared. */ object_t *destructed_objs = NULL; /* List holding destructed but not yet fully dereferenced objects. * Only the name and the program pointer are guarantueed to be valid. * The reference by this list is counted. * Objects with only the list reference left are finally freed by * the function remove_destructed_objs() called from the backend. #ifdef MALLOC_smalloc * They are also freed by a GC. #endif * TODO: If this turns out to be not soon enough, modify the free_object() * TODO:: call to recognize the destructed+one-ref-left situation. * * This list is not exactly necessary, as destructed objects would be * deallcoated automatically once the last reference is gone, but it * helps mud admins to figure out where all the memory goes. */ long num_destructed = 0; /* Statistics: Number of objects in the destructed_objs list. */ object_t *newly_destructed_objs = NULL; /* List holding objects destructed in this execution thread. * They are no longer part of the obj_list, but since programs may still * be executing in them, the aren't fully destructed yet. */ long num_newly_destructed = 0; /* Statistics: Number of objects in the newly_destructed_objs list. */ object_t *master_ob = NULL; /* The master object. */ object_t *current_object; /* The object interpreting a function. */ object_t *current_interactive; /* The user who caused this execution. */ object_t *previous_ob; /* The previous object which called the current_object. */ svalue_t driver_hook[NUM_DRIVER_HOOKS]; /* The table with all driver hooks. */ #ifdef USE_FREE_CLOSURE_HOOK static svalue_t *old_hooks = NULL; /* Array of entries holding all the old driver hook closures replaced * during this and the previous execution threads. The closures are * not freed immediately on replacement in case they are still used. * Instead, the backend frees them explicitely. */ static int num_old_hooks = 0; /* The current number of entries in <old_hooks> */ static int max_old_hooks = 0; /* The allocated length of <old_hooks> */ #endif Bool game_is_being_shut_down = MY_FALSE; /* TRUE if a shutdown was requested resp. is in progress. */ Bool master_will_be_updated = MY_FALSE; /* TRUE if a master-update was requested. */ int num_error = 0; /* Number of recursive calls to error(). */ static char emsg_buf[2000]; /* The buffer for the error message to be created. */ char *current_error; char *current_error_file; char *current_error_object_name; mp_int current_error_line_number; /* When an error occured during secure_apply(), these four * variables receive allocated copies of the error message, * the name of the active program and object, and the * line number in the program. */ vector_t *uncaught_error_trace = NULL; vector_t *current_error_trace = NULL; /* When an error occured, these variables hold the call chain in the * format used by efun debug_info() for evaluation by the mudlib. * The variables are kept until the next error, or until a GC. * 'uncaught_error_trace': the most recent uncaught error * 'current_error_trace': the most recent error, caught or uncaught. */ /* --- Runtime limits --- */ /* Each of these limits comes as pair: one def_... value which holds the * limit set at startup or with the set_limits() efun, and the max_... * value which holds the limit currently in effect. Before every execution, * max_... are initialised from def_... with the RESET_LIMITS macro. * * A limit of 0 usually means 'no limit'. */ size_t def_array_size = MAX_ARRAY_SIZE; size_t max_array_size = MAX_ARRAY_SIZE; /* If != 0: the max. number of elements in an array. */ size_t def_mapping_size = MAX_MAPPING_SIZE; size_t max_mapping_size = MAX_MAPPING_SIZE; /* If != 0: the max. number of elements in a mapping. */ int32 def_eval_cost = MAX_COST; int32 max_eval_cost = MAX_COST; /* The max eval cost available for one execution thread. Stored as negative * value for easier initialisation (see eval_cost). * CLEAR_EVAL_COST uses this value to re-initialize (assigned_)eval_cost. */ int32 def_byte_xfer = MAX_BYTE_TRANSFER; int32 max_byte_xfer = MAX_BYTE_TRANSFER; /* Maximum number of bytes to read/write in one read/write_bytes() call. * If 0, it is unlimited. */ int32 def_file_xfer = READ_FILE_MAX_SIZE; int32 max_file_xfer = READ_FILE_MAX_SIZE; /* Maximum number of bytes to read/write in one read/write_file() call. */ int32 def_callouts = MAX_CALLOUTS; int32 max_callouts = MAX_CALLOUTS; /* If != 0: the max. number of callouts at one time. */ /*-------------------------------------------------------------------------*/ /* Forward declarations */ static void free_shadow_sent (shadow_t *p); /*-------------------------------------------------------------------------*/ Bool catch_instruction (bytecode_t catch_inst, uint offset , volatile svalue_t ** volatile i_sp , bytecode_p i_pc, svalue_t * i_fp) /* Implement the F_CATCH/F_CATCH_NO_LOG instruction. * * At the time of call, all important locals from eval_instruction() are * have been stored in their global locations. * * Result is TRUE on a normal exit (error or not), and FALSE if the * guarded code terminated with a 'return' itself; * * Hard experience showed that it is advantageous to have the setjmp() * have its own stackframe, and call the longjmp() from a deeper * frame. Additionally it prevents over-optimistic optimizers from * removing vital reloads of possibly clobbered local variables after * the setjmp(). */ { #define INTER_SP ((svalue_t *)(*i_sp)) Bool rc; volatile Bool old_out_of_memory = out_of_memory; bytecode_p new_pc; /* Address of first instruction after the catch() */ /* Compute address of next instruction after the CATCH statement. */ new_pc = i_pc + offset; /* Increase the eval_cost for the duration of the catch so that * there is enough time left to handle an eval-too-big error. */ if (max_eval_cost && eval_cost + CATCH_RESERVED_COST >= max_eval_cost) { error("Not enough eval time left for catch(): required %ld, available %ld\n" , (long)CATCH_RESERVED_COST, (long)(max_eval_cost - eval_cost) ); /* NOTREACHED */ return MY_TRUE; } eval_cost += CATCH_RESERVED_COST; assigned_eval_cost += CATCH_RESERVED_COST; /* 'Fake' a subroutine call from <new_pc> */ push_control_stack(INTER_SP, new_pc, i_fp); csp->ob = current_object; csp->extern_call = MY_FALSE; csp->catch_call = MY_TRUE; #ifndef DEBUG csp->num_local_variables = 0; /* No extra variables */ #else csp->num_local_variables = (csp-1)->num_local_variables; /* TODO: Marion added this, but why? For 'expected_stack'? */ #endif csp->funstart = csp[-1].funstart; /* Save some globals on the error stack that must be restored * separately after a longjmp, then set the jump. */ if ( setjmp( push_error_context(INTER_SP, catch_inst)->text ) ) { /* A throw() or error occured. We have to restore the * control and error stack manually here. * * The error value to return will be stored in * the global <catch_value>. */ svalue_t *sp; /* Remove the catch context and get the old stackpointer setting */ sp = pull_error_context(INTER_SP); /* beware of errors after set_this_object() */ current_object = csp->ob; /* catch() faked a subroutine call internally, which has to be * undone again. This will also set the pc to the proper * continuation address. */ pop_control_stack(); /* Push the catch return value */ *(++sp) = catch_value; catch_value.type = T_INVALID; *i_sp = (volatile svalue_t *)sp; /* Restore the old eval costs */ eval_cost -= CATCH_RESERVED_COST; assigned_eval_cost -= CATCH_RESERVED_COST; /* If we ran out of memory, throw a new error */ if (!old_out_of_memory && out_of_memory) { error("(catch) Out of memory detected.\n"); } rc = MY_TRUE; } else { /* Recursively call the interpreter */ rc = eval_instruction(i_pc, INTER_SP); if (rc) { /* Get rid of the code result */ pop_stack(); /* Restore the old execution context */ pop_control_stack(); pop_error_context(); /* Since no error happened, push 0 onto the stack */ push_number(0); } /* Restore the old eval costs */ eval_cost -= CATCH_RESERVED_COST; assigned_eval_cost -= CATCH_RESERVED_COST; } return rc; } /* catch_instruction() */ /*-------------------------------------------------------------------------*/ static INLINE void save_limits_context (struct limits_context_s * context) /* Save the current limits context into <context> (but don't put it * onto the context stack). */ { context->rt.type = LIMITS_CONTEXT; context->max_array = max_array_size; context->max_callouts = max_callouts; context->max_mapping = max_mapping_size; context->max_eval = max_eval_cost; context->eval_cost = eval_cost; context->max_byte = max_byte_xfer; context->max_file = max_file_xfer; } /* save_limits_context() */ /*-------------------------------------------------------------------------*/ static INLINE void restore_limits_context (struct limits_context_s * context) /* Restore the last runtime limits from <context>. * * Restoring max_eval_cost is a bit tricky since eval_cost * itself might be a bit too high for the restored limit, but * avoiding a 'eval-cost too high' was the point of the exercise * in the first place. Therefore, if we ran under a less limited * eval-cost limit, we fake an effective cost of 10 ticks. */ { assign_eval_cost(); if (!max_eval_cost || max_eval_cost > context->max_eval) { assigned_eval_cost = eval_cost = context->eval_cost+10; } max_array_size = context->max_array; max_mapping_size = context->max_mapping; max_callouts = context->max_callouts; max_eval_cost = context->max_eval; max_byte_xfer = context->max_byte; max_file_xfer = context->max_file; } /* restore_limits_context() */ /*-------------------------------------------------------------------------*/ static void unroll_context_stack (void) /* Remove entries from the rt_context stack until the last entry * is an ERROR_RECOVERY context. */ { while (!ERROR_RECOVERY_CONTEXT(rt_context->type)) { rt_context_t * context = rt_context; rt_context = rt_context->last; switch(context->type) { case COMMAND_CONTEXT: restore_command_context(context); break; case LIMITS_CONTEXT: restore_limits_context((struct limits_context_s *)context); break; default: fatal("Unimplemented context type %d.\n", context->type); /* NOTREACHED */ } } } /* unroll_context_stack() */ /*-------------------------------------------------------------------------*/ static INLINE void dump_core(void) NORETURN; static INLINE void dump_core(void) /* A wrapper around abort() to make sure that we indeed dump a core. */ { #if (defined(__GNUC__) || !defined(AMIGA)) && !defined(__BEOS__) /* we want a core dump, and abort() seems to fail for linux and sun */ (void)signal(SIGFPE, SIG_DFL); { int a = 0; /* avoids a pesky diagnostic */ *((char*)0) = 0/a; *((char*)fatal) = 0/a; } #endif abort(); } /* dump_core() */ /*-------------------------------------------------------------------------*/ void fatal (char *fmt, ...) /* A fatal error occured. Generate a message from printf-style <fmt>, including * a timestamp, dump the backtrace and abort. */ { va_list va; char *ts; static Bool in_fatal = MY_FALSE; /* Prevent double fatal. */ if (in_fatal) dump_core(); in_fatal = MY_TRUE; ts = time_stamp(); va_start(va, fmt); fflush(stdout); fprintf(stderr, "%s ", ts); vfprintf(stderr, fmt, va); fflush(stderr); if (current_object) fprintf(stderr, "%s Current object was %s\n" , ts, current_object->name ? current_object->name : "<null>"); debug_message("%s ", ts); vdebug_message(fmt, va); if (current_object) debug_message("%s Current object was %s\n" , ts, current_object->name ? current_object->name : "<null>"); debug_message("%s Dump of the call chain:\n", ts); (void)dump_trace(MY_TRUE, NULL); printf("%s LDMud aborting on fatal error.\n", time_stamp()); fflush(stdout); #if defined(__GNUC__) || !defined(AMIGA) || !defined(__SASC) sleep(1); /* let stdout settle down... abort can ignore the buffer... */ #else Delay(50); /* Call Dos.library to wait... */ #endif va_end(va); /* Before shutting down, try to inform the game about it */ push_volatile_string("Fatal Error"); callback_master(STR_SHUTDOWN, 1); /* Dump core and exit */ dump_core(); } /* fatal() */ /*-------------------------------------------------------------------------*/ char * limit_error_format (char *fixed_fmt, size_t fixed_fmt_len, char *fmt) /* Safety function for error messages: in the error message <fmt> * every '%s' spec is changed to '%.200s' to avoid buffer overflows. * The modified format string is stored in <fixed_fmt> (a caller provided * buffer of size <fixed_fmd_len>) which is also returned as result. */ { char *ffptr; ffptr = fixed_fmt; while (*fmt && ffptr - fixed_fmt < fixed_fmt_len-1) { if ((*ffptr++=*fmt++)=='%') { if (*fmt == 's') { *ffptr++ = '.'; *ffptr++ = '2'; *ffptr++ = '0'; *ffptr++ = '0'; } } } if (*fmt) { /* We reached the end of the fixed_fmt buffer before * the <fmt> string was complete: mark this error message * as truncated. * ffptr points to the last byte in the <fixed_fmt> buffer. */ ffptr[-3] = '.'; ffptr[-2] = '.'; ffptr[-1] = '.'; } *ffptr = '\0'; return fixed_fmt; } /* limit_error_format() */ /*-------------------------------------------------------------------------*/ void error (char *fmt, ...) /* A system runtime error occured: generate a message from printf-style * <fmt> with a timestamp, and handle it. * If the error is caught, just dump the trace on stderr, and jump to the * error handler, otherwise call the mudlib's error functions (this may cause * recursive calls to error()) and jump back to wherever the current error * recovery context points to. * * The runtime context stack is unrolled as far as necessary. * TODO: Add a perrorf(<prefmt>, <postfmt>,...) function which translates the * TODO:: errno into a string and calls error(<prefmt><errmsg><postfmt>, ...). */ { rt_context_t *rt; char *object_name; char *ts; svalue_t *svp; Bool do_save_error; char *file; /* program name */ char *malloced_error; /* copy of emsg_buf+1 */ char *malloced_file = NULL; /* copy of program name */ char *malloced_name = NULL; /* copy of the object name */ object_t *curobj = NULL; /* Verified current object */ char fixed_fmt[10000]; /* Note: When changing this buffer, also change the HEAP_STACK_GAP * limit in xalloc.c! */ mp_int line_number = 0; va_list va; ts = time_stamp(); /* Find the last error recovery context, but do not yet unroll * the stack: the current command context might be needed * in the runtime error apply. */ for ( rt = rt_context ; !ERROR_RECOVERY_CONTEXT(rt->type) ; rt = rt->last) NOOP; va_start(va, fmt); /* Make fmt sane */ fmt = limit_error_format(fixed_fmt, sizeof(fixed_fmt), fmt); /* Check the current object */ curobj = NULL; if (current_object != NULL && current_object != &dummy_current_object_for_loads) curobj = current_object; if (curobj) assign_eval_cost(); /* We allow recursive errors only from "sensitive" environments. */ if (num_error && rt->type <= ERROR_RECOVERY_APPLY) { static char *times_word[] = { "", "Double", "Triple", "Quadruple", }; debug_message("%s %s fault, last error was: %s" , ts, times_word[num_error] , emsg_buf + 1 ); } /* Generate the error message */ vsprintf(emsg_buf+1, fmt, va); va_end(va); emsg_buf[0] = '*'; /* all system errors get a * at the start */ if (rt->type >= ERROR_RECOVERY_CATCH) { /* User catches this error */ put_malloced_string(&catch_value, string_copy(emsg_buf)); /* always reallocate */ if (rt->type != ERROR_RECOVERY_CATCH_NOLOG) { /* Even though caught, dump the backtrace - it makes mudlib * debugging much easier. */ debug_message("%s Caught error: %s", ts, emsg_buf + 1); printf("%s Caught error: %s", ts, emsg_buf + 1); if (current_error_trace) free_array(current_error_trace); dump_trace(MY_FALSE, ¤t_error_trace); debug_message("%s ... execution continues.\n", ts); printf("%s ... execution continues.\n", ts); } unroll_context_stack(); longjmp(((struct error_recovery_info *)rt_context)->con.text, 1); fatal("Catch() longjump failed"); } /* Error not caught by the program */ num_error++; if (num_error > 3) fatal("Too many simultaneous errors.\n"); debug_message("%s ", ts); debug_message("%s", emsg_buf+1); do_save_error = MY_FALSE; /* Get a copy of the error message */ if ( NULL != (malloced_error = xalloc(strlen(emsg_buf))) ) { strcpy(malloced_error, emsg_buf+1); } /* If we have a current_object, determine the program location * of the fault. */ if (curobj) { line_number = get_line_number_if_any(&file); debug_message("%s program: %s, object: %s line %ld\n" , ts, file, curobj->name, line_number); if (current_prog && num_error < 3) { do_save_error = MY_TRUE; } if ( NULL != (malloced_file = xalloc(strlen(file) + 1)) ) { strcpy(malloced_file, file); } if ( NULL != (malloced_name = xalloc(strlen(curobj->name) + 1)) ) { strcpy(malloced_name, curobj->name); } } /* On a triple error, duplicate the error messages so far on stdout */ if (num_error == 3) { /* Error context is secure_apply() */ printf("%s error in function call: %s", ts, emsg_buf+1); if (curobj) { printf("%s program: %s, object: %s line %ld\n" , ts, file, curobj->name, line_number ); } } /* Dump the backtrace */ if (uncaught_error_trace) free_array(uncaught_error_trace); if (current_error_trace) free_array(current_error_trace); object_name = dump_trace(num_error == 3, ¤t_error_trace); uncaught_error_trace = ref_array(current_error_trace); fflush(stdout); if (rt->type == ERROR_RECOVERY_APPLY) { /* Error context is secure_apply() */ current_error = malloced_error; current_error_file = malloced_file; current_error_object_name = malloced_name; current_error_line_number = line_number; if (out_of_memory) { if (malloced_error) xfree(malloced_error); if (malloced_file) xfree(malloced_file); if (malloced_name) xfree(malloced_name); if (current_error_trace) { free_array(current_error_trace); current_error_trace = NULL; } if (uncaught_error_trace) { free_array(uncaught_error_trace); uncaught_error_trace = NULL; } } unroll_context_stack(); longjmp(((struct error_recovery_info *)rt_context)->con.text, 1); } /* Error is not caught at all. * * 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 have been deallocated. */ reset_machine(MY_FALSE); if (do_save_error) { save_error(emsg_buf, file, line_number); } if (object_name) { /* Error occured in a heart_beat() function */ object_t *ob; ob = find_object(object_name); if (!ob) { if (command_giver && num_error < 2) add_message("error when executing program in destroyed object %s\n", object_name); debug_message("%s error when executing program in destroyed object %s\n" , ts, object_name); } } if (num_error == 3) { debug_message("%s Master failure: %s", ts, emsg_buf+1); printf("%s Master failure: %s", ts, emsg_buf+1); } else if (!out_of_memory) { /* We have memory: call master:runtime(), and maybe * also master:heart_beat_error(). */ int a; object_t *save_cmd; object_t *culprit = NULL; CLEAR_EVAL_COST; RESET_LIMITS; push_volatile_string(malloced_error); a = 1; if (curobj) { push_volatile_string(malloced_file); push_volatile_string(malloced_name); push_number(line_number); a += 3; } if (current_heart_beat) { /* Heartbeat error: turn off the heartbeat in the object * and also pass it to RUNTIME_ERROR. */ culprit = current_heart_beat; current_heart_beat = NULL; set_heart_beat(culprit, 0); debug_message("%s Heart beat in %s turned off.\n" , time_stamp(), culprit->name); push_valid_ob(culprit); a++; } else { if (!curobj) { /* Pass dummy values */ push_number(0); push_number(0); push_number(0); a += 3; } /* Normal error: push -1 instead of a culprit. */ push_number(-1); a++; } save_cmd = command_giver; apply_master(STR_RUNTIME, a); command_giver = save_cmd; if (culprit) { /* TODO: Merge heart_beat_error() in to runtime_error() */ /* Heartbeat error: call the master to log it * and to see if the heartbeat shall be turned * back on for this object. */ push_valid_ob(culprit); push_volatile_string(malloced_error); a = 2; if (curobj) { push_volatile_string(malloced_file); push_volatile_string(malloced_name); push_number(line_number); a += 3; } svp = apply_master(STR_HEART_ERROR, a); command_giver = save_cmd; if (svp && (svp->type != T_NUMBER || svp->u.number) ) { debug_message("%s Heart beat in %s turned back on.\n" , time_stamp(), culprit->name); set_heart_beat(culprit, 1); } } /* Handling errors is expensive! */ assigned_eval_cost = eval_cost += MASTER_RESERVED_COST; } /* Clean up */ if (malloced_error) xfree(malloced_error); if (malloced_file) xfree(malloced_file); if (malloced_name) xfree(malloced_name); num_error--; if (current_interactive) { interactive_t *i; if (O_SET_INTERACTIVE(i, current_interactive) && i->noecho & NOECHO_STALE) { set_noecho(i, 0); } } /* Unroll the context stack and find the recovery context to jump to. */ unroll_context_stack(); if (rt_context->type != ERROR_RECOVERY_NONE) longjmp(((struct error_recovery_info *)rt_context)->con.text, 1); fatal("Can't recover from error (longjmp failed)\n"); } /* error() */ /*-------------------------------------------------------------------------*/ void warnf (char *fmt, ...) /* A system runtime warning occured: generate a message from printf-style * <fmt> with a timestamp, and print it using debug_message(). * * Note: Both 'warn' and 'warning' are already taken on some systems. * TODO: Extend this to let the mudlib handle warnings. * TODO: Add a pwarnf(<prefmt>, <postfmt>,...) function which translates the * TODO:: errno into a string and calls error(<prefmt><errmsg><postfmt>, ...). */ { char *ts; char *file; /* program name */ object_t *curobj = NULL; /* Verified current object */ char msg_buf[2000]; /* The buffer for the error message to be created. */ char fixed_fmt[10000]; /* Note: When changing this buffer, also change the HEAP_STACK_GAP * limit in xalloc.c! */ mp_int line_number = 0; va_list va; ts = time_stamp(); va_start(va, fmt); /* Make fmt sane */ fmt = limit_error_format(fixed_fmt, sizeof(fixed_fmt), fmt); /* Check the current object */ curobj = NULL; if (current_object != NULL && current_object != &dummy_current_object_for_loads) curobj = current_object; if (curobj) assign_eval_cost(); /* Generate the warning message */ vsprintf(msg_buf, fmt, va); va_end(va); debug_message("%s ", ts); debug_message("%s", msg_buf); /* If we have a current_object, determine the program location * of the fault. */ if (curobj) { line_number = get_line_number_if_any(&file); debug_message("%s program: %s, object: %s line %ld\n" , ts, file, curobj->name, line_number); } fflush(stdout); } /* warnf() */ /*-------------------------------------------------------------------------*/ void parse_error (Bool warning, char *error_file, int line, char *what , char *context) /* The compiler found an error <what> (<warning> is FALSE) resp. * a warning <what> (<warning> is TRUE) while compiling <line> of * file <error_file>. The context of the error location is <context>. * * Log the error by calling master:log_error() (but do not reload * the master if not existing - the compiler is busy). */ { char buff[500]; if (error_file == NULL) 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: %s\n", error_file, line, context, what); /* Don't call the master if it isn't loaded! */ if (master_ob && !(master_ob->flags & O_DESTRUCTED) ) { push_volatile_string(error_file); push_volatile_string(buff); push_number(warning ? 1 : 0); apply_master(STR_LOG_ERROR, 3); } } /* parse_error() */ /*-------------------------------------------------------------------------*/ void throw_error() /* The second part of the efun throw(): the caller stored the message * into catch_value, now our job is to do the proper longjmp. */ { unroll_context_stack(); if (rt_context->type >= ERROR_RECOVERY_CATCH) { longjmp(((struct error_recovery_info *)rt_context)->con.text, 1); fatal("Throw_error failed!"); } free_svalue(&catch_value); catch_value.type = T_INVALID; error("Throw with no catch.\n"); } /* throw_error() */ /*-------------------------------------------------------------------------*/ void set_svalue_user (svalue_t *svp, object_t *owner) /* Set the owner of <svp> to object <owner>, if the svalue knows of * this concept. This may cause a recursive call to this function again. */ { switch(svp->type) { case T_POINTER: case T_QUOTED_ARRAY: set_vector_user(svp->u.vec, owner); break; case T_MAPPING: { set_mapping_user(svp->u.map, owner); break; } case T_CLOSURE: { set_closure_user(svp, owner); } } } /* set_svalue_user() */ /*-------------------------------------------------------------------------*/ static void give_uid_error_handler (svalue_t *arg) /* Error handler for give_uid_to_object(), called automatically when * the stack is cleant up during the error handling. * <arg> is a (struct give_uid_error_context*), the action is to destruct * the object. */ { struct give_uid_error_context *ecp; object_t *ob; ecp = (struct give_uid_error_context *)arg; ob = ecp->new_object; xfree(ecp); if (ob) { destruct(ob); } } /* give_uid_error_handler() */ /*-------------------------------------------------------------------------*/ static void push_give_uid_error_context (object_t *ob) /* Object <ob> will be given its uids. Push an error handler onto the * interpreter stack which will clean up <ob> in case of an error. */ { struct give_uid_error_context *ecp; ecp = xalloc(sizeof *ecp); if (!ecp) { destruct(ob); error("Out of memory (%lu bytes) for new object '%s' uids\n" , (unsigned long) sizeof(*ecp), ob->name); } ecp->head.type = T_ERROR_HANDLER; ecp->head.u.error_handler = give_uid_error_handler; ecp->new_object = ob; inter_sp++; inter_sp->type = T_LVALUE; inter_sp->u.lvalue = &ecp->head; } /* push_give_uid_error_context() */ /*-------------------------------------------------------------------------*/ static Bool give_uid_to_object (object_t *ob, int hook, int numarg) /* Object <ob> was just created - call the driver_hook <hook> with <numarg> * arguments to give it its uid and euid. * Return TRUE on success - on failure, destruct <ob>ject and raise * an error; return FALSE in the unlikely case that error() does return. */ { lambda_t *l; char *err, errtxt[1000]; svalue_t arg, *ret; ob->user = &default_wizlist_entry; /* Default uid */ if ( NULL != (l = driver_hook[hook].u.lambda) ) { if (driver_hook[hook].x.closure_type == CLOSURE_LAMBDA) l->ob = ob; call_lambda(&driver_hook[hook], numarg); ret = inter_sp; xfree(ret[-1].u.lvalue); /* free error context */ if (ret->type == T_STRING) { ob->user = add_name(ret->u.string); ob->eff_user = ob->user; pop_stack(); /* deallocate result */ inter_sp--; /* skip error context */ return MY_TRUE; } else if (ret->type == T_POINTER && VEC_SIZE(ret->u.vec) == 2 && ( ret->u.vec->item[0].type == T_STRING || (!strict_euids && ret->u.vec->item[0].u.number) ) ) { ret = ret->u.vec->item; ob->user = ret[0].type != T_STRING ? &default_wizlist_entry : add_name(ret[0].u.string); ob->eff_user = ret[1].type != T_STRING ? 0 : add_name(ret[1].u.string); pop_stack(); inter_sp--; return MY_TRUE; } else if (!strict_euids && ret->type == T_NUMBER && ret->u.number) { ob->user = &default_wizlist_entry; ob->eff_user = NULL; pop_stack(); inter_sp--; return MY_TRUE; } else { pop_stack(); /* deallocate result */ sprintf(errtxt, "Object '%.900s' illegal to load (no uid).\n" , ob->name); err = errtxt; } } else { do pop_stack(); while (--numarg); /* deallocate arguments */ xfree(inter_sp->u.lvalue); err = "Closure to set uid not initialized!\n"; } inter_sp--; /* skip error context */ if (master_ob == NULL) { /* Only for the master object. */ ob->user = add_name("NONAME"); ob->eff_user = NULL; return MY_TRUE; } ob->user = add_name("NONAME"); ob->eff_user = ob->user; put_object(&arg, ob); destruct_object(&arg); error(err); /* NOTREACHED */ return MY_FALSE; } /* give_uid_to_object() */ /*-------------------------------------------------------------------------*/ const char * make_name_sane (const char *pName, Bool addSlash) /* Make a given object name sane. * * The function removes leading '/' (if addSlash is true, all but one leading * '/' are removed), a trailing '.c', and folds consecutive * '/' into just one '/'. The '.c' removal does not work when given * clone object names (i.e. names ending in '#<number>'). * * The function returns a pointer to a static(!) buffer with the cleant * up name, or NULL if the given name already was sane. */ { static char buf[MAXPATHLEN+1]; const char *from = pName; char *to; short bDiffers = MY_FALSE; to = buf; /* Skip leading '/' */ if (!addSlash) { while (*from == '/') { bDiffers = MY_TRUE; from++; } } else { *to++ = '/'; if (*from != '/') bDiffers = MY_TRUE; else { from++; while (*from == '/') { bDiffers = MY_TRUE; from++; } } } /* addSlash or not: from now points to the first non-'/' */ /* Copy the name into buf, doing the other operations */ for (; '\0' != *from && (to - buf) < sizeof(buf) ; from++, to++) { if ('/' == *from) { *to = '/'; while ('/' == *from) { from++; bDiffers = MY_TRUE; } from--; } else if ('.' == *from && 'c' == *(from+1) && '\0' == *(from+2)) { bDiffers = MY_TRUE; break; } else *to = *from; } *to = '\0'; if (!bDiffers) return NULL; return (const char *)buf; } /* make_name_sane() */ /*-------------------------------------------------------------------------*/ Bool check_no_parentdirs (char *path) /* Check that there are no '/../' constructs in the path. * Return TRUE if there aren't. */ { char *p; if (path == NULL) return MY_FALSE; for (p = strchr(path, '.'); p; p = strchr(p+1, '.')) { if (p[1] != '.') continue; if ((p[2] == '\0' || p[2] == '/') && (p == path || p[-1] == '/') ) return MY_FALSE; /* Skip the next '.' as it's safe to do so */ p++; } return MY_TRUE; } /* check_no_parentdirs() */ /*-------------------------------------------------------------------------*/ Bool legal_path (char *path) /* Check that <path> is a legal relative path. This means no spaces * and no '/../' are allowed. * TODO: This should go into a 'files' module. */ { if (path == NULL || strchr(path, ' ') || path[0] == '/') return MY_FALSE; #ifdef MSDOS_FS { char *name; if (strchr(path,'\\')) return MY_FALSE; /* better save than sorry ... */ if (strchr(path,':')) return MY_FALSE; /* \B: is okay for DOS .. *sigh* */ name = strrchr(path,'/'); if (NULL != name) name++; else name = path; if (!strcasecmp(name,"NUL") || !strcasecmp(name,"CON") || !strcasecmp(name,"PRN") || !strcasecmp(name,"AUX") || !strcasecmp(name,"COM1") || !strcasecmp(name,"COM2") || !strcasecmp(name,"COM3") || !strcasecmp(name,"COM4") || !strcasecmp(name,"LPT1") || !strcasecmp(name,"LPT2") || !strcasecmp(name,"LPT3") || !strcasecmp(name,"LPT4") ) return MY_FALSE; } #endif return check_no_parentdirs(path); } /* legal_path() */ /*-------------------------------------------------------------------------*/ static void load_object_error(const char *msg, const char *name, namechain_t *chain) NORETURN; static void load_object_error(const char *msg, const char *name, namechain_t *chain) /* Generate a compilation error message <msg>. If <name> is not NULL, * ": '<name>'" is appended to the message. If <chain> is not NULL, * " (inherited by <chain...>)" is appended to the message. * The message is then printed to stderr and an error() with it is thrown. */ { strbuf_t sbuf; namechain_t *ptr; char * buf; strbuf_zero(&sbuf); strbuf_add(&sbuf, msg); if (name != NULL) { strbuf_add(&sbuf, ": '"); strbuf_add(&sbuf, name); strbuf_add(&sbuf, "'"); } if (chain != NULL) { strbuf_add(&sbuf, " (inherited"); for (ptr = chain; ptr != NULL; ptr = ptr->prev) { strbuf_add(&sbuf, " by '"); strbuf_add(&sbuf, ptr->name); strbuf_add(&sbuf, "'"); } strbuf_add(&sbuf, ")"); } strbuf_add(&sbuf, ".\n"); /* Make a local copy of the message so as not to leak memory */ buf = alloca(strlen(sbuf.buf)+1); if (!buf) error("Out of stack memory (%lu bytes)\n" , (unsigned long) strlen(sbuf.buf)+1); strcpy(buf, sbuf.buf); strbuf_free(&sbuf); fprintf(stderr, "%s %s", time_stamp(), buf); error("%.*s", (int)strlen(buf), buf); } /* load_object_error() */ /*-------------------------------------------------------------------------*/ #define MAX_LOAD_DEPTH 60 /* Make this a configurable constant */ static object_t * load_object (const char *lname, Bool create_super, int depth, namechain_t *chain) /* Load (compile) an object blueprint from the file <lname>. <create_super> * is true if the object has to be initialized with CREATE_SUPER, and false * if CREATE_OB is to be used. <depth> is the current recursive load depth * and is checked against MAX_LOAD_DEPTH. * * If the object can't be loaded because it inherits some other unloaded * object, call load_object() recursively to load the inherited object, then * try to load the original object again. This is done in a loop so that * eventually all missing inherits are loaded. * * The name <lname> must be sane object name, and can be a clone name. * * If there is no source file <lname>.c, the function calls * master:compile_object() in case it is a virtual object. * * <chain> is the internal list of inherits. * * Result is the pointer to the loaded object, or NULL on failure. */ { int fd; object_t *ob; object_t *save_command_giver = command_giver; int i; struct stat c_st; int name_length; char *name; /* Copy of <lname> */ char *fname; /* Filename for <name> */ program_t *prog; namechain_t nlink; #ifdef DEBUG if ('/' == lname[0]) fatal("Improper filename '%s' passed to load_object()\n", lname); #endif /* It could be that the passed filename is one of an already loaded * object. In that case, simply return that object. */ ob = lookup_object_hash((char *)lname); if (ob) { return ob; } /* We need two copies of <lname>: one to construct the filename in, * the second because lname might be a buffer which is deleted * during the compilation process. */ name_length = strlen(lname); name = alloca(name_length+2); fname = alloca(name_length+4); if (!name || !fname) fatal("Stack overflow in load_object()"); if (!compat_mode) *name++ = '/'; /* Add and hide a leading '/' */ strcpy(name, lname); strcpy(fname, lname); nlink.name = name; nlink.prev = chain; if (strict_euids && current_object && current_object->eff_user == 0 && current_object->name) error("Can't load objects when no effective user.\n"); if (master_ob && master_ob->flags & O_DESTRUCTED) { /* The master has been destructed, and it has not been noticed yet. * Reload it, because it can't be done inside of yyparse. * assert_master_ob_loaded() will clear master_ob while reloading is * in progress, thus preventing a fatal recursion. */ assert_master_ob_loaded(); /* has the object been loaded by assert_master_ob_loaded ? */ if ( NULL != (ob = find_object(name)) ) { if (ob->flags & O_SWAPPED && load_ob_from_swap(ob) < 0) /* The master has swapped this object and used up most * memory... strange, but thinkable */ error("Out of memory: unswap object '%s'\n", ob->name); return ob; } } /* Check if we were already trying to compile this object */ if (chain != NULL) { namechain_t * ptr; for (ptr = chain; ptr != NULL; ptr = ptr->prev) { if (!strcmp(name, ptr->name)) load_object_error("Recursive inherit", name, chain); } } /* Check if the name follows the "name#number" pattern */ { char c; char *p; i = name_length; p = name+name_length; while (--i > 0) { /* isdigit would need to check isascii first... */ if ( (c = *--p) < '0' || c > '9' ) { if (c == '#' && name_length - i > 1) { load_object_error("Illegal file to load", name, chain); } break; } } } /* Check that the c-file exists. */ (void)strcpy(fname+name_length, ".c"); if (ixstat(fname, &c_st) == -1) { /* The file does not exist - maybe it's a virtual object */ svalue_t *svp; push_volatile_string(fname); svp = apply_master(STR_COMP_OBJ, 1); if (svp && svp->type == T_OBJECT) { /* We got an object from the call, but is it what it * claims to be? */ if ( NULL != (ob = lookup_object_hash(name)) ) { /* An object for <name> magically appeared - is it * the one we received? */ if (ob == svp->u.ob) return ob; } else if (ob != master_ob) { /* Rename the object we got to the name it * is supposed to have. */ ob = svp->u.ob; remove_object_hash(ob); xfree(ob->name); ob->name = string_copy(name); enter_object_hash(ob); return ob; } fname[name_length] = '.'; } load_object_error("Failed to load file", name, chain); /* NOTREACHED */ return NULL; } /* Check if it's a legal name. */ if (!legal_path(fname)) { load_object_error("Illegal pathname", fname, chain); /* NOTREACHED */ return NULL; } /* The compilation loop. It will run until either <name> is loaded * or an error occurs. If the compilation is aborted because an * inherited object was not found, that object is loaded in a * recursive call, then the loop will try again on the original * object. */ while (MY_TRUE) { /* This can happen after loading an inherited object: */ ob = lookup_object_hash((char *)name); if (ob) { return ob; } if (comp_flag) fprintf(stderr, "%s compiling %s ...", time_stamp(), fname); if (current_file) { error("Can't load '%s': compiler is busy with '%s'.\n" , name, current_file); } fd = ixopen(fname, O_RDONLY | O_BINARY); if (fd <= 0) { perror(fname); error("Could not read the file.\n"); } FCOUNT_COMP(fname); current_file = fname; /* The file name is needed before compile_file(), in case there is * an initial 'line too long' error. */ compile_file(fd); if (comp_flag) { if (NULL == inherit_file) fprintf(stderr, " done\n"); else fprintf(stderr, " needs inherit\n"); } update_compile_av(total_lines); total_lines = 0; (void)close(fd); current_file = NULL; /* If there is no inherited file to compile, we can * end the loop here. */ if (NULL == inherit_file) break; /* This object wants to inherit an unloaded object. We discard * current object, load the object to be inherited and reload * the current object again. The global variable "inherit_file" * was set by lang.y to point to a file name. */ { char * pInherited; const char * tmp; tmp = make_name_sane(inherit_file, MY_FALSE); if (!tmp) { pInherited = inherit_file; } else { pInherited = alloca(strlen(tmp)+1); strcpy(pInherited, tmp); } push_referenced_shared_string(inherit_file); /* Automagic freeing in case of errors */ inherit_file = NULL; /* Now that the inherit_file-string will be freed in case * of an error, we can check if there were other errors * besides the missing inherit. */ if (num_parse_error > 0) { load_object_error("Error in loading object", name, chain); } if (strcmp(pInherited, name) == 0) { error("Illegal to inherit self.\n"); } if (depth >= MAX_LOAD_DEPTH) { load_object_error("Too deep inheritance", name, chain); } ob = load_object(pInherited, MY_TRUE, depth+1, &nlink); free_string(inter_sp->u.string); inter_sp--; if (!ob || ob->flags & O_DESTRUCTED) { load_object_error("Error in loading object " "(inheritance failed)\n", name, chain); } } /* handling of inherit_file */ } /* while() - compilation loop */ /* Did the compilation succeed? */ if (num_parse_error > 0) { load_object_error("Error in loading object", name, chain); } /* We got the program. Now create the blueprint to hold it. */ if (NULL != (ob = lookup_object_hash(name))) { /* The object magically appeared! * This can happen if rename_object() is used carelessly * in the mudlib handler for compiler warnings. */ #ifndef INITIALIZATION_BY___INIT for (i = compiled_prog->num_variables; --i >= 0; ) free_svalue(&prog_variable_values[i]); #endif free_prog(compiled_prog, MY_TRUE); load_object_error("Object appeared while it was compiled" , name, chain); /* NOTREACHED */ return NULL; } prog = compiled_prog; #ifdef INITIALIZATION_BY___INIT ob = get_empty_object(prog->num_variables); #else ob = get_empty_object( prog->num_variables, prog->variable_names , prog_variable_values); /* TODO: The initializers should be stored in the program. * TODO:: See clone_object() for the reason. * TODO:: To implement this efficiently, use special 'const' arrays * TODO:: and mappings with a copy-on-write strategy: value copies * TODO:: of such arrays are made on assignment (to catch m = ([...]); * TODO:: m_delete(m, ...)) and lvalue/ref computation. */ for (i = prog->num_variables; --i >= 0; ) free_svalue(&prog_variable_values[i]); xfree(prog_variable_values); #endif if (!ob) error("Out of memory for new object '%s'\n", name); ob->name = string_copy(name); tot_alloc_object_size += strlen(ob->name)+1; /* Tabling this unique string is of not much use. * Note that the string must be valid for the ref_object() * below to work in debugging mode. */ prog->blueprint = ref_object(ob, "load_object: blueprint reference"); if (!compat_mode) name--; /* Make the leading '/' visible again */ ob->load_name = make_shared_string(name); /* but here it is */ ob->prog = prog; ob->ticks = ob->gigaticks = 0; ob->next_all = obj_list; ob->prev_all = NULL; if (obj_list) obj_list->prev_all = ob; obj_list = ob; if (!obj_list_end) obj_list_end = ob; num_listed_objs++; enter_object_hash(ob); /* add name to fast object lookup table */ /* Give the object its uids */ push_give_uid_error_context(ob); push_string_shared(ob->name); if (give_uid_to_object(ob, H_LOAD_UIDS, 1)) { /* The object has an uid - now we can update the .user * of its initializers. */ svalue_t *svp; int j; object_t *save_current; save_current = current_object; current_object = ob; /* for lambda_ref_replace_program */ svp = ob->variables; for (j = ob->prog->num_variables; --j >= 0; svp++) { if (svp->type == T_NUMBER) continue; set_svalue_user(svp, ob); } if (save_current == &dummy_current_object_for_loads) { /* The master object is loaded with no current object */ current_object = NULL; reset_object(ob, create_super ? H_CREATE_SUPER : H_CREATE_OB); /* If the master inherits anything -Ugh- we have to have * some object to attribute initialized variables to. */ current_object = save_current; } else { current_object = save_current; reset_object(ob, create_super ? H_CREATE_SUPER : H_CREATE_OB); } } if ( !(ob->flags & O_DESTRUCTED)) ob->flags |= O_WILL_CLEAN_UP; /* Restore the command giver */ command_giver = check_object(save_command_giver); if (d_flag > 1 && ob) debug_message("%s --%s loaded\n", time_stamp(), ob->name); return ob; } /* load_object() */ /*-------------------------------------------------------------------------*/ static char * make_new_name (char *str) /* <str> is a basic object name - generate a clone name "<str>#<num>" * and return it. * * The number is guaranteed to be unique in combination with this name. */ { static unsigned long clone_id_number = 0; /* The next number to use for a clone name */ static int test_conflict = MY_FALSE; /* TRUE if the generated clone name has to be tested for uniqueness. * This is not the case before clone_id_number wraps around the * first time. */ char *p; size_t l; char buff[40]; if ('/' == *str) str++; for (;;) { /* Generate the clone name */ (void)sprintf(buff, "#%lu", clone_id_number); l = strlen(str); p = xalloc(l + strlen(buff) + 1); strcpy(p, str); strcpy(p+l, buff); clone_id_number++; if (clone_id_number == 0) /* Wrap around */ test_conflict = MY_TRUE; if (!test_conflict || !find_object(p)) return p; /* The name was already taken */ xfree(p); } } /* make_new_name() */ /*-------------------------------------------------------------------------*/ object_t * clone_object (char *str1) /* Create a clone of the object named <str1>, which may be a clone itself. * On success, return the new object, otherwise NULL. */ { object_t *ob, *new_ob; object_t *save_command_giver = command_giver; char *name; if (strict_euids && current_object && current_object->eff_user == NULL) error("Illegal to call clone_object() with effective user 0\n"); ob = get_object(str1); /* If the object self-destructed... */ if (ob == NULL) return NULL; /* If ob is a clone, try finding the blueprint via the load_name */ if (ob->flags & O_CLONE) { object_t *bp; bp = get_object(ob->load_name); if (bp) ob = bp; } if (ob->super) error("Cloning a bad object: '%s' is contained in '%s'.\n" , ob->name, ob->super->name); name = ob->name; /* If the ob is a clone, we have to test if its name is something * illegal like 'foobar#34'. In that case, we have to use the * load_name as template. */ if (ob->flags & O_CLONE) { char c; char *p; mp_int name_length, i; name_length = strlen(name); i = name_length; p = ob->name+name_length; while (--i > 0) { /* isdigit would need to check isascii first... */ if ( (c = *--p) < '0' || c > '9' ) { if (c == '#' && name_length - i > 1) { /* Well, unusable name format - use the load_name */ name = ob->load_name; } break; } } } if ((ob->flags & O_SWAPPED) && load_ob_from_swap(ob) < 0) error("Out of memory: unswap object '%s'\n", ob->name); if (ob->prog->flags & P_NO_CLONE) error("Cloning a bad object: '%s' sets '#pragma no_clone'.\n" , ob->name); ob->time_of_ref = current_time; /* We do not want the heart beat to be running for unused copied objects */ if (!(ob->flags & O_CLONE) && ob->flags & O_HEART_BEAT) set_heart_beat(ob, 0); /* Got the blueprint - now get a new object */ new_ob = get_empty_object(ob->prog->num_variables #ifndef INITIALIZATION_BY___INIT , ob->prog->variable_names , ob->variables #endif ); /* TODO: Yeech: the new objects variables are initialised from the * TODO:: template object variables. These values should be stored * TODO:: in the program. */ if (!new_ob) error("Out of memory for new clone '%s'\n", name); new_ob->name = make_new_name(name); tot_alloc_object_size += strlen(new_ob->name)+1; new_ob->load_name = ref_string(ob->load_name); new_ob->flags |= O_CLONE | O_WILL_CLEAN_UP; new_ob->prog = ob->prog; reference_prog (ob->prog, "clone_object"); new_ob->ticks = new_ob->gigaticks = 0; #ifdef DEBUG if (!current_object) fatal("clone_object() from no current_object !\n"); #endif new_ob->next_all = obj_list; new_ob->prev_all = NULL; if (obj_list) obj_list->prev_all = new_ob; obj_list = new_ob; if (!obj_list_end) obj_list_end = new_ob; num_listed_objs++; enter_object_hash(new_ob); /* Add name to fast object lookup table */ push_give_uid_error_context(new_ob); push_object(ob); push_volatile_string(new_ob->name); give_uid_to_object(new_ob, H_CLONE_UIDS, 2); reset_object(new_ob, H_CREATE_CLONE); command_giver = check_object(save_command_giver); /* Never know what can happen ! :-( */ if (new_ob->flags & O_DESTRUCTED) return NULL; return new_ob; } /* clone_object() */ /*-------------------------------------------------------------------------*/ object_t * lookfor_object(char * str, Bool bLoad) /* Look for a named object <str>, optionally loading it (<bLoad> is true). * Return a pointer to the object structure, or NULL. * * If <bLoad> is true, the function tries to load the object if it is * not already loaded. * If <bLoad> is false, the function just checks if the object is loaded. * * The object is not swapped in. * * For easier usage, the macros find_object() and get_object() expand * to the no-load- resp. load-call of this function. * * TODO: It would be nice if all loading uses of lookfor would go through * TODO:: the efun load_object() or a driver hook so that the mudlib * TODO:: has a chance to interfere with it. Dito for clone_object(), so * TODO:: that the euid-check can be done there? */ { object_t *ob; const char * pName; /* TODO: It would be more useful to check all callers of lookfor() * TODO:: and move the make_name_sane() into those where it can * TODO:: be dirty. */ pName = make_name_sane(str, MY_FALSE); if (!pName) pName = str; ob = lookup_object_hash((char *)pName); if (!bLoad) return ob; if (!ob) ob = load_object(pName, 0, 0, NULL); if (!ob || ob->flags & O_DESTRUCTED) return NULL; return ob; } /* lookfor_object() */ /*-------------------------------------------------------------------------*/ void destruct_object (svalue_t *v) /* Destruct the object named/passed in svalue <v>. * This is the full program: the master:prepare_destruct() is called * to clean the inventory of the object, and if it's an interactive, * it is given the chance to save a pending editor buffer. * * The actual destruction work is then done in destruct(). */ { object_t *ob; svalue_t *result; /* Get the object to destruct */ if (v->type == T_OBJECT) ob = v->u.ob; else { ob = find_object(v->u.string); if (ob == 0) error("destruct_object: Could not find %s\n", v->u.string); } if (ob->flags & O_DESTRUCTED) return; if (ob->flags & O_SWAPPED) if (load_ob_from_swap(ob) < 0) error("Out of memory: unswap object '%s'\n", ob->name); if (d_flag) { debug_message("%s destruct_object: %s (ref %ld)\n" , time_stamp(), ob->name, ob->ref); } push_object(ob); result = apply_master(STR_PREP_DEST, 1); if (!result) error("No prepare_destruct\n"); if (result->type == T_STRING) error(result->u.string); if (result->type != T_NUMBER || result->u.number != 0) return; if (ob->contains) { error("Master failed to clean inventory in prepare_destruct\n"); } if (ob->flags & O_SHADOW) { shadow_t *sh; object_t *save = command_giver; command_giver = ob; sh = O_GET_SHADOW(ob); if (sh->ip) trace_level |= sh->ip->trace_level; if (sh->ed_buffer) save_ed_buffer(); command_giver = save; } destruct(ob); } /* destruct_object() */ /*-------------------------------------------------------------------------*/ void deep_destruct (object_t *ob) /* Destruct an object <ob> and the blueprint objects of all inherited * programs. The actual destruction work is done by destruct(). * * The objects are still kept around until the end of the execution because * it might still hold a running program. The destruction will be completed * from the backend by a call to handle_newly_destructed_objects(). */ { program_t *prog; /* Destruct the object itself */ destruct(ob); /* Loop through all the inherits and destruct the blueprints * of the inherited programs. */ prog = ob->prog; if (prog != NULL) { int i; for (i = 0; i < prog->num_inherited; ++i) { program_t *iprog = prog->inherit[i].prog; if (iprog != NULL && iprog->blueprint != NULL) { destruct(iprog->blueprint); } } } } /* deep_destruct() */ /*-------------------------------------------------------------------------*/ void destruct (object_t *ob) /* Destruct an object <ob>. This function is called from * destruct_object() to do the actual work, and also directly in situations * where the master is out of order or the object not fully initialized. * * The function: * - marks the object as destructed * - moves it out of the global object list and the object able, into * the list of destructed objects * - changes all references on the interpreter stack to svalue-0 * - moves it out of its environment * - removes all shadows. * * The object is still kept around until the end of the execution because * it might still hold a running program. The destruction will be completed * from the backend by a call to handle_newly_destructed_objects(). */ { object_t **pp, *item, *next; if (ob->flags & O_DESTRUCTED) return; ob->time_reset = 0; /* We need the object in memory */ if (ob->flags & O_SWAPPED) { int save_privilege; save_privilege = malloc_privilege; malloc_privilege = MALLOC_SYSTEM; load_ob_from_swap(ob); malloc_privilege = save_privilege; } /* If there are shadows, remove them */ if (ob->flags & O_SHADOW) { shadow_t *shadow_sent; object_t *shadowing, *shadowed_by; shadow_sent = O_GET_SHADOW(ob); if (shadow_sent->ed_buffer) { object_t *save = command_giver; command_giver = ob; free_ed_buffer(); command_giver = save; } /* The chain of shadows is a double linked list. Take care to update * it correctly. */ if ( NULL != (shadowing = shadow_sent->shadowing) ) { shadow_t *shadowing_sent; /* Remove the shadow sent from the chain */ shadowing_sent = O_GET_SHADOW(shadowing); shadow_sent->shadowing = NULL; shadowing_sent->shadowed_by = shadow_sent->shadowed_by; check_shadow_sent(shadowing); /* This object, the shadow, may have added actions to * the shadowee, or it's vicinity. Take care to remove * them all. */ remove_shadow_actions(ob, shadowing); } if ( NULL != (shadowed_by = shadow_sent->shadowed_by) ) { shadow_t *shadowed_by_sent; /* Remove the shadow sent from the chain */ shadowed_by_sent = O_GET_SHADOW(shadowed_by); shadow_sent->shadowed_by = NULL; shadowed_by_sent->shadowing = shadowing; check_shadow_sent(shadowed_by); /* Our shadows may have added actions to us or to our * environment. Take care to remove them all. */ do { remove_shadow_actions(shadowed_by, ob); if (O_GET_SHADOW(shadowed_by) != NULL) shadowed_by = O_GET_SHADOW(shadowed_by)->shadowed_by; else shadowed_by = NULL; } while (shadowed_by != NULL); } check_shadow_sent(ob); } /* Move all objects in the inventory into the "void" */ for (item = ob->contains; item; item = next) { remove_action_sent(ob, item); item->super = NULL; next = item->next_inv; item->next_inv = NULL; } remove_object_from_stack(ob); if (ob == simul_efun_object) { simul_efun_object = NULL; invalidate_simul_efuns(); } set_heart_beat(ob, 0); /* 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->sent) remove_action_sent(ob, ob->super); # ifdef USE_SET_LIGHT add_light(ob->super, - ob->total_light); # endif for (pp = &ob->super->contains; *pp;) { if ((*pp)->sent) remove_action_sent(ob, *pp); if (*pp != ob) pp = &(*pp)->next_inv; else *pp = (*pp)->next_inv; } } /* 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. */ remove_object_hash(ob); if (ob->prev_all) ob->prev_all->next_all = ob->next_all; if (ob->next_all) ob->next_all->prev_all = ob->prev_all; if (ob == obj_list) obj_list = ob->next_all; if (ob == obj_list_end) obj_list_end = ob->prev_all; num_listed_objs--; ob->super = NULL; ob->next_inv = NULL; ob->contains = NULL; ob->flags &= ~O_ENABLE_COMMANDS; ob->flags |= O_DESTRUCTED; /* should come last! */ if (command_giver == ob) command_giver = NULL; /* Put the object into the list of newly destructed objects */ ob->prev_all = NULL; ob->next_all = newly_destructed_objs; newly_destructed_objs = ob; num_newly_destructed++; } /* destruct() */ /*-------------------------------------------------------------------------*/ static void remove_object (object_t *ob) /* This function is called from outside any execution thread to finally * remove object <ob>. <ob> must have been unlinked from all object lists * already (but the associated reference count must still exist). * * The function frees all variables and remaining sentences in the object. * If then only one reference (from the original object list) remains, the * object is freed immediately with a call to free_object(). If more * references exist, the object is linked into the destructed_objs list * for freeing at a future date. * * The object structure and the program will be freed as soon as there * are no further references to the object (the program will remain behind * in case it was inherited). * TODO: Distinguish data- and inheritance references? */ { sentence_t *sent; if (d_flag > 1) { debug_message("%s remove_object: object %s (ref %ld)\n" , time_stamp(), ob->name, ob->ref); } if (O_IS_INTERACTIVE(ob)) remove_interactive(ob, MY_FALSE); /* If this is a blueprint object, NULL out the pointer in the program * to remove the extraneous reference. */ if (ob->prog->blueprint == ob) { ob->prog->blueprint = NULL; remove_prog_swap(ob->prog, MY_TRUE); free_object(ob, "remove_object: blueprint reference"); } /* 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 > 0) { /* Deallocate variables in this object. */ int i; for (i = 0; i < ob->prog->num_variables; i++) { free_svalue(&ob->variables[i]); put_number(ob->variables+i, 0); } xfree(ob->variables); } #ifdef DEBUG else if (ob->variables != NULL) { debug_message("%s Warning: Object w/o variables, but variable block " "at %p\n", time_stamp(), ob->variables); } #endif /* This should be here to avoid using up memory as long as the object * isn't released. It must be here because gcollect doesn't expect * sentences in destructed objects. */ if ( NULL != (sent = ob->sent) ) { sentence_t *next; do { next = sent->next; if (sent->type == SENT_SHADOW) free_shadow_sent((shadow_t *)sent); else free_action_sent((action_t *)sent); } while ( NULL != (sent = next) ); ob->sent = NULL; } /* Either free the object, or link it up for future freeing. */ if (ob->ref <= 1) { free_object(ob, "destruct_object"); } else { if (destructed_objs != NULL) destructed_objs->prev_all = ob; ob->next_all = destructed_objs; destructed_objs = ob; ob->prev_all = NULL; num_destructed++; } } /* remove_object() */ /*-------------------------------------------------------------------------*/ void handle_newly_destructed_objects (void) /* Finish up all newly destructed objects kept in the newly_destructed_objs * list: deallocate as many associated resources and, if there are * more than one references to the object, put it into the destructed_objs list. */ { while (newly_destructed_objs) { object_t *ob = newly_destructed_objs; newly_destructed_objs = ob->next_all; #ifdef DEBUG if (!(ob->flags & O_DESTRUCTED)) fatal("Non-destructed object %p '%s' in list of newly destructed objects.\n" , ob, ob->name ? ob->name : "<null>" ); #endif remove_object(ob); num_newly_destructed--; } } /* handle_newly_destructed_objects() */ /*-------------------------------------------------------------------------*/ void remove_destructed_objects (void) /* Scan the list of destructed objects and free those with no references * remaining. */ { object_t *ob; for (ob = destructed_objs; ob != NULL; ) { object_t *victim; /* Check if only the list reference remains. * If not, go to the next object. */ if (ob->ref > 1) { ob = ob->next_all; continue; } /* This object can be freed - remove it from the list */ victim = ob; if (ob->prev_all != NULL) ob->prev_all->next_all = ob->next_all; if (ob->next_all != NULL) ob->next_all->prev_all = ob->prev_all; if (destructed_objs == ob) destructed_objs = ob->next_all; ob = ob->next_all; free_object(victim, "remove_destructed_objects"); num_destructed--; } } /* remove_destructed_objects() */ /*-------------------------------------------------------------------------*/ static INLINE shadow_t * new_shadow_sent(void) /* Allocate a new empty shadow sentence and return it. */ { shadow_t *p; if (free_sent == NULL) { xallocate(p, sizeof *p, "new shadow sentence"); alloc_shadow_sent++; } else { p = (shadow_t *)free_sent; free_sent = free_sent->next; } p->sent.type = SENT_SHADOW; p->shadowing = NULL; p->shadowed_by = NULL; p->ed_buffer = NULL; p->ip = NULL; return p; } /* new_shadow_sent() */ /*-------------------------------------------------------------------------*/ static void free_shadow_sent (shadow_t *p) /* Free the shadow sentence <p>. */ { #ifdef DEBUG if (SENT_SHADOW != p->sent.type) fatal("free_shadow_sent() received non-shadow sent type %d\n" , p->sent.type); #endif p->sent.next = free_sent; free_sent = (sentence_t *)p; } /* free_shadow_sent() */ /*-------------------------------------------------------------------------*/ void purge_shadow_sent(void) /* Actually deallocate all shadow sentences held in the free list. * Called during a GC and shutdown. */ { sentence_t *p; for (;free_sent; free_sent = p) { p = free_sent->next; xfree(free_sent); alloc_shadow_sent--; } } /* purge_shadow_sent() */ /*-------------------------------------------------------------------------*/ void check_shadow_sent (object_t *ob) /* Check if object <ob> has a shadow sentence and really needs it. * If yes and no, the sentence is removed. */ { if (ob->flags & O_SHADOW) { shadow_t *sh; sh = O_GET_SHADOW(ob); if (!sh->ip && !sh->ed_buffer && !sh->shadowing && !sh->shadowed_by ) { ob->sent = sh->sent.next; free_shadow_sent(sh); ob->flags &= ~O_SHADOW; } } } /* check_shadow_sent() */ /*-------------------------------------------------------------------------*/ void assert_shadow_sent (object_t *ob) /* Make sure that object <ob> has a shadow sentence. */ { if (!(ob->flags & O_SHADOW)) { shadow_t *sh; sh = new_shadow_sent(); sh->sent.next = ob->sent; ob->sent = (sentence_t *)sh; ob->flags |= O_SHADOW; } } /* assert_shadow_sent() */ /*-------------------------------------------------------------------------*/ Bool status_parse (strbuf_t * sbuf, char * buff) /* Parse the status request in <buff> and if recognized, dump the * data into the stringbuffer <sbuf>. * * Return TRUE if the request was recognised, and FALSE otherwise. * * The function is called from actions:special_parse() to implement * the hardcoded commands, and from the efun debug_info(). */ { if (sbuf) strbuf_zero(sbuf); if (!buff || *buff == 0 || strcmp(buff, "tables") == 0) { size_t tot, res; Bool verbose = MY_FALSE; if (strcmp(buff, "tables") == 0) verbose = MY_TRUE; res = 0; if (reserved_user_area) res = reserved_user_size; if (reserved_master_area) res += reserved_master_size; if (reserved_system_area) res += reserved_system_size; if (!verbose) { strbuf_addf(sbuf, "Actions:\t\t\t%8ld %9ld\n" , alloc_action_sent , alloc_action_sent * sizeof (action_t)); strbuf_addf(sbuf, "Shadows:\t\t\t%8ld %9ld\n" , alloc_shadow_sent , alloc_shadow_sent * sizeof (shadow_t)); strbuf_addf(sbuf, "Objects:\t\t\t%8ld %9ld (%ld swapped, %ld Kbytes)\n" , tot_alloc_object, tot_alloc_object_size , num_vb_swapped, total_vb_bytes_swapped / 1024); strbuf_addf(sbuf, "Arrays:\t\t\t\t%8ld %9ld\n" , (long)num_arrays, total_array_size() ); strbuf_addf(sbuf, "Mappings:\t\t\t%8ld %9ld\n" , num_mappings, total_mapping_size() ); strbuf_addf(sbuf, "Prog blocks:\t\t\t%8ld %9ld (%ld swapped, %ld Kbytes)\n" , total_num_prog_blocks + num_swapped - num_unswapped , total_prog_block_size + total_bytes_swapped - total_bytes_unswapped , num_swapped - num_unswapped , (total_bytes_swapped - total_bytes_unswapped) / 1024); strbuf_addf(sbuf, "Memory reserved:\t\t\t %9d\n", res); } if (verbose) { #ifdef COMM_STAT strbuf_addf(sbuf , "Calls to add_message: %d Packets: %d " "Average packet size: %.2f\n\n" , add_message_calls , inet_packets , inet_packets ? (float)inet_volume/(float)inet_packets : 0.0 ); #endif #ifdef APPLY_CACHE_STAT strbuf_addf(sbuf , "Calls to apply_low: %ld " "Cache hits: %ld (%.2f%%)\n\n" , (long)(apply_cache_hit+apply_cache_miss) , (long)apply_cache_hit , 100.*(float)apply_cache_hit/ (float)(apply_cache_hit+apply_cache_miss) ); #endif } tot = alloc_action_sent * sizeof(action_t); tot += alloc_shadow_sent * sizeof(shadow_t); tot += total_prog_block_size; tot += total_array_size(); tot += tot_alloc_object_size; if (verbose) { #ifdef DEBUG long count; object_t *ob; #endif strbuf_add(sbuf, "\nObject status:\n"); strbuf_add(sbuf, "--------------\n"); strbuf_addf(sbuf, "Objects total:\t\t\t %8ld\n" , (long)tot_alloc_object); #ifndef DEBUG strbuf_addf(sbuf, "Objects in list:\t\t %8ld\n" , (long)num_listed_objs); strbuf_addf(sbuf, "Objects newly destructed:\t\t %8ld\n" , (long)num_newly_destructed); strbuf_addf(sbuf, "Objects destructed:\t\t %8ld\n" , (long)num_destructed); #else for (count = 0, ob = obj_list; ob != NULL; ob = ob->next_all) count++; if (count != num_listed_objs) { debug_message("DEBUG: num_listed_objs mismatch: listed %ld, counted %ld\n" , (long)num_listed_objs, count); strbuf_addf(sbuf, "Objects in list:\t\t %8ld (counted %ld)\n" , (long)num_listed_objs, count); } else strbuf_addf(sbuf, "Objects in list:\t\t %8ld\n" , (long)num_listed_objs); for (count = 0, ob = newly_destructed_objs; ob != NULL; ob = ob->next_all) count++; if (count != num_newly_destructed) { debug_message("DEBUG: num_newly_destructed mismatch: listed %ld, counted %ld\n" , (long)num_newly_destructed, count); strbuf_addf(sbuf, "Objects newly destructed:\t\t %8ld (counted %ld)\n" , (long)num_newly_destructed, count); } else strbuf_addf(sbuf, "Objects newly destructed:\t %8ld\n" , (long)num_newly_destructed); for (count = 0, ob = destructed_objs; ob != NULL; ob = ob->next_all) count++; if (count != num_destructed) { debug_message("DEBUG: num_destructed mismatch: listed %ld, counted %ld\n" , (long)num_destructed, count); strbuf_addf(sbuf, "Objects destructed:\t\t %8ld (counted %ld)\n" , (long)num_destructed, count); } else strbuf_addf(sbuf, "Objects destructed:\t\t %8ld\n" , (long)num_destructed); #endif strbuf_addf(sbuf, "Objects processed in last cycle: " "%8ld (%5.1f%% - avg. %5.1f%%)\n" , (long)num_last_processed , (float)num_last_processed / (float)num_listed_objs * 100.0 , !avg_in_list ? 0.0 : ((avg_in_list || avg_last_processed > avg_in_list) ? 100.0 : 100.0 * (float)avg_last_processed / avg_in_list ) ); } tot += show_otable_status(sbuf, verbose); tot += heart_beat_status(sbuf, verbose); tot += add_string_status(sbuf, verbose); tot += call_out_status(sbuf, verbose); tot += total_mapping_size(); #ifdef RXCACHE_TABLE tot += rxcache_status(sbuf, verbose); #endif if (verbose) { strbuf_add(sbuf, "\nOther:\n"); strbuf_add(sbuf, "------\n"); } tot += show_lexer_status(sbuf, verbose); tot += show_comm_status(sbuf, verbose); if (!verbose) { size_t other; other = wiz_list_size(); other += swap_overhead(); other += num_simul_efun * sizeof(function_t); other += interpreter_overhead(); strbuf_addf(sbuf, "Other structures\t\t\t %9lu\n", other); tot += other; } tot += res; if (!verbose) { strbuf_add(sbuf, "\t\t\t\t\t ---------\n"); strbuf_addf(sbuf, "Total:\t\t\t\t\t %9d\n", tot); } return MY_TRUE; } if (strcmp(buff, "swap") == 0) { swap_status(sbuf); return MY_TRUE; } if (strcmp(buff, "malloc") == 0) { #if defined(MALLOC_smalloc) dump_malloc_data(sbuf); #endif #ifdef MALLOC_sysmalloc strbuf_add(sbuf, "Using system standard malloc.\n"); #endif return MY_TRUE; } return MY_FALSE; } /* status_parse() */ /*-------------------------------------------------------------------------*/ void dinfo_data_status (svalue_t *svp, int value) /* Fill in the "status" data for debug_info(DINFO_DATA, DID_STATUS) * into the svalue-block <svp>. * If <value> is -1, <svp> points indeed to a value block; other it is * the index of the desired value and <svp> points to a single svalue. */ { STORE_DOUBLE_USED; #define ST_NUMBER(which,code) \ if (value == -1) svp[which].u.number = code; \ else if (value == which) svp->u.number = code #define ST_DOUBLE(which,code) \ if (value == -1) { \ svp[which].type = T_FLOAT; \ STORE_DOUBLE(svp+which, code); \ } else if (value == which) { \ svp->type = T_FLOAT; \ STORE_DOUBLE(svp, code); \ } ST_NUMBER(DID_ST_ACTIONS, alloc_action_sent); ST_NUMBER(DID_ST_ACTIONS_SIZE, alloc_action_sent * sizeof (action_t)); ST_NUMBER(DID_ST_SHADOWS, alloc_shadow_sent); ST_NUMBER(DID_ST_SHADOWS_SIZE, alloc_shadow_sent * sizeof (shadow_t)); ST_NUMBER(DID_ST_OBJECTS, tot_alloc_object); ST_NUMBER(DID_ST_OBJECTS_SIZE, tot_alloc_object_size); ST_NUMBER(DID_ST_OBJECTS_SWAPPED, num_vb_swapped); ST_NUMBER(DID_ST_OBJECTS_SWAP_SIZE, total_vb_bytes_swapped); ST_NUMBER(DID_ST_OBJECTS_LIST, num_listed_objs); ST_NUMBER(DID_ST_OBJECTS_NEWLY_DEST, num_newly_destructed); ST_NUMBER(DID_ST_OBJECTS_DESTRUCTED, num_destructed); ST_NUMBER(DID_ST_OBJECTS_PROCESSED, num_last_processed); ST_DOUBLE(DID_ST_OBJECTS_AVG_PROC , !avg_in_list ? 0.0 : ((avg_in_list || avg_last_processed > avg_in_list) ? 1.0 : (double)avg_last_processed / avg_in_list ) ); ST_NUMBER(DID_ST_ARRAYS, num_arrays); ST_NUMBER(DID_ST_ARRAYS_SIZE, total_array_size()); ST_NUMBER(DID_ST_MAPPINGS, num_mappings); ST_NUMBER(DID_ST_MAPPINGS_SIZE, total_mapping_size()); ST_NUMBER(DID_ST_PROGS, total_num_prog_blocks + num_swapped - num_unswapped); ST_NUMBER(DID_ST_PROGS_SIZE, total_prog_block_size + total_bytes_swapped - total_bytes_unswapped); ST_NUMBER(DID_ST_PROGS_SWAPPED, num_swapped - num_unswapped); ST_NUMBER(DID_ST_PROGS_SWAP_SIZE, total_bytes_swapped - total_bytes_unswapped); ST_NUMBER(DID_ST_USER_RESERVE, reserved_user_size); ST_NUMBER(DID_ST_MASTER_RESERVE, reserved_master_size); ST_NUMBER(DID_ST_SYSTEM_RESERVE, reserved_system_size); #ifdef COMM_STAT ST_NUMBER(DID_ST_ADD_MESSAGE, add_message_calls); ST_NUMBER(DID_ST_PACKETS, inet_packets); ST_NUMBER(DID_ST_PACKET_SIZE, inet_volume); #else ST_NUMBER(DID_ST_ADD_MESSAGE, -1); ST_NUMBER(DID_ST_PACKETS, -1); ST_NUMBER(DID_ST_PACKET_SIZE, -1); #endif #ifdef APPLY_CACHE_STAT ST_NUMBER(DID_ST_APPLY, apply_cache_hit+apply_cache_miss); ST_NUMBER(DID_ST_APPLY_HITS, apply_cache_hit); #else ST_NUMBER(DID_ST_APPLY, -1); ST_NUMBER(DID_ST_APPLY_HITS, -1); #endif #undef ST_NUMBER #undef ST_DOUBLE } /* dinfo_data_status() */ /*-------------------------------------------------------------------------*/ char * check_valid_path (char *path, object_t *caller, char* call_fun, Bool writeflg) /* Object <caller> will read resp. write (<writeflg>) the file <path> * for the efun <call_fun>. * * Check the validity of the operation by calling master:valid_read() resp. * valid_write(). * * If the operation is valid, the path to use is returned (always without * leading '/', the path "/" will be returned as "."). This path is either * a pointer into the <path> argument, or a pointer to a static buffer in * apply(). * * If the operation is invalid, NULL is returned. */ { svalue_t *v; wiz_list_t *eff_user; if (path) push_string_malloced(path); else push_number(0); if ( NULL != (eff_user = caller->eff_user) ) push_shared_string(eff_user->name); else push_number(0); push_volatile_string(call_fun); push_valid_ob(caller); if (writeflg) v = apply_master(STR_VALID_WRITE, 4); else v = apply_master(STR_VALID_READ, 4); if (!v || (v->type == T_NUMBER && v->u.number == 0)) return NULL; if (v->type != T_STRING) { if (!path) { debug_message("%s master returned bogus filename\n", time_stamp()); return NULL; } } else { path = v->u.string; } if (path[0] == '/') path++; /* The string "/" will be converted to "." */ if (path[0] == '\0') path = "."; if (legal_path(path)) return path; error("Illegal path %s for %s() by %s\n", path, call_fun, caller->name); return NULL; } /* check_valid_path() */ /*-------------------------------------------------------------------------*/ void init_empty_callback (callback_t *cb) /* Initialize *<cb> to be an empty initialized callback. * Use this to initialize callback structures which might be freed before * completely filled in. */ { cb->num_arg = 0; cb->is_lambda = MY_FALSE; cb->function.named.ob = NULL; cb->function.named.name = NULL; } /* init_empty_callback() */ /*-------------------------------------------------------------------------*/ static INLINE void free_callback_args (callback_t *cb) /* Free the function arguments in the callback <cb>. */ { svalue_t *dest; int nargs; nargs = cb->num_arg; if (nargs == 1) { if (cb->arg.type != T_INVALID) free_svalue(&(cb->arg)); } else if (nargs > 1 && !cb->arg.x.extern_args) { dest = cb->arg.u.lvalue; while (--nargs >= 0) if (dest->type != T_INVALID) free_svalue(dest++); xfree(cb->arg.u.lvalue); } cb->arg.type = T_INVALID; cb->num_arg = 0; } /* free_callback_args() */ /*-------------------------------------------------------------------------*/ void free_callback (callback_t *cb) /* Free the data and references held by callback structure <cb>. * The structure itself remains because usually it is embedded within * another structure. * * Repeated calls for the same callback structure are legal. */ { if (cb->is_lambda && cb->function.lambda.type != T_INVALID) { free_svalue(&(cb->function.lambda)); cb->function.lambda.type = T_INVALID; } else if (!(cb->is_lambda)) { if (cb->function.named.ob) free_object(cb->function.named.ob, "free_callback"); if (cb->function.named.name) free_string(cb->function.named.name); cb->function.named.ob = NULL; cb->function.named.name = NULL; } free_callback_args(cb); } /* free_callback() */ /*-------------------------------------------------------------------------*/ static INLINE int setup_callback_args (callback_t *cb, int nargs, svalue_t * args , Bool allow_prot_lvalues) /* Setup the function arguments in the callback <cb> to hold the <nargs> * arguments starting from <args>. If <allow_prot_lvalues> is FALSE, no * argument may be a protected lvalue. The arguments are transferred into the * callback structure. * * Result is -1 on success, or, when encountering an illegal argument, * the index of the faulty argument (but even then all caller arguments * have been transferred or freed). * * TODO: It should be possible to accept protected lvalues by careful * TODO:: juggling of the protector structures. That, or rewriting the * TODO:: lvalue system. */ { svalue_t *dest; cb->num_arg = nargs; if (nargs < 1) { cb->arg.type = T_INVALID; cb->num_arg = 0; } else { /* Transfer the arguments into the callback structure */ if (nargs > 1) { xallocate(dest, sizeof(*dest) * nargs, "callback structure"); cb->arg.type = T_LVALUE; cb->arg.u.lvalue = dest; cb->arg.x.extern_args = MY_FALSE; } else dest = &(cb->arg); while (--nargs >= 0) { if (!allow_prot_lvalues && args->type == T_LVALUE && ( args->u.lvalue->type == T_PROTECTED_CHAR_LVALUE || args->u.lvalue->type == T_PROTECTED_STRING_RANGE_LVALUE || args->u.lvalue->type == T_PROTECTED_POINTER_RANGE_LVALUE || args->u.lvalue->type == T_PROTECTED_LVALUE ) ) { /* We don't handle protected lvalues - abort the process. * But to do that, we first have to free all * remaining arguments from the caller. */ int error_index = cb->num_arg - nargs - 1; do { free_svalue(args++); (dest++)->type = T_INVALID; } while (--nargs >= 0); free_callback_args(cb); return error_index; } transfer_svalue_no_free(dest++, args++); } } /* Success */ return -1; } /* setup_callback_args() */ /*-------------------------------------------------------------------------*/ int setup_function_callback ( callback_t *cb, object_t * ob, char * fun , int nargs, svalue_t * args, Bool allow_prot_lvalues) /* Setup the empty/uninitialized callback <cb> to hold a function * call to <ob>:<fun> with the <nargs> arguments starting from <args>. * If <allow_prot_lvalues> is FALSE, no argument may be a protected lvalue. * * Both <ob> and <fun> are copied from the caller, but the arguments are * adopted (taken away from the caller). * * Result is -1 on success, or, when encountering an illegal argument, * the index of the faulty argument (but even then all caller arguments * have been transferred or freed). */ { int error_index; cb->is_lambda = MY_FALSE; cb->function.named.name = make_shared_string(fun); cb->function.named.ob = ref_object(ob, "callback"); error_index = setup_callback_args(cb, nargs, args, allow_prot_lvalues); if (error_index >= 0) { free_object(cb->function.named.ob, "callback"); free_string(cb->function.named.name); cb->function.named.ob = NULL; cb->function.named.name = NULL; } return error_index; } /* setup_function_callback() */ /*-------------------------------------------------------------------------*/ int setup_closure_callback ( callback_t *cb, svalue_t *cl , int nargs, svalue_t * args, Bool allow_prot_lvalues) /* Setup the empty/uninitialized callback <cb> to hold a closure * call to <cl> with the <nargs> arguments starting from <args>. * If <allow_prot_lvalues> is FALSE, no argument may be a protected lvalue. * * Both <cl> and the arguments are adopted (taken away from the caller). * * Result is -1 on success, or, when encountering an illegal argument, * the index of the faulty argument (but even then all caller arguments * have been transferred or freed). */ { int error_index; cb->is_lambda = MY_TRUE; transfer_svalue_no_free(&(cb->function.lambda), cl); error_index = setup_callback_args(cb, nargs, args, allow_prot_lvalues); if (error_index >= 0) { free_svalue(&(cb->function.lambda)); cb->function.lambda.type = T_INVALID; } return error_index; } /* setup_closure_callback() */ /*-------------------------------------------------------------------------*/ int setup_efun_callback ( callback_t *cb, svalue_t *args, int nargs) /* Setup the empty/uninitialized callback <cb> with the <nargs> * values starting at <args>. This function is used to implement the * callbacks for efuns like map_array() and accepts these forms: * * (string fun) * (string fun, mixed extra, ...) TODO: This form is UGLY! * (string fun, string|object obj, mixed extra, ...) * (closure cl, mixed extra, ...) * * If the first argument is a string and the second neither an object * nor a string, this_object() is used as object specification. * * All arguments are adopted (taken away from the caller). Protected lvalues * like &(i[0]) are not allowed as 'extra' arguments. * * Result is -1 on success, or, when encountering an illegal argument, * the index of the faulty argument (but even then all caller arguments * have been transferred or freed). */ { int error_index; if (args[0].type == T_CLOSURE) { error_index = setup_closure_callback(cb, args, nargs-1, args+1, MY_FALSE); if (error_index >= 0) error_index++; } else if (args[0].type == T_STRING) { object_t *ob; int first_arg; first_arg = 1; if (nargs > 1) { if (args[1].type == T_OBJECT) { ob = args[1].u.ob; first_arg = 2; } else if (args[1].type == T_STRING) { ob = get_object(args[1].u.string); first_arg = 2; } else { /* TODO: It would be better to throw an error here */ ob = current_object; first_arg = 1; } } else ob = current_object; if (ob != NULL) { error_index = setup_function_callback(cb, ob, args[0].u.string , nargs-first_arg, args+first_arg , MY_FALSE); if (error_index >= 0) error_index += first_arg; } else { /* We couldn't find an object to call, so we have * to manually prepare the error condition. */ int i; for (i = first_arg; i < nargs; i++) free_svalue(args+i); error_index = 1; } /* Free the function spec */ free_svalue(args); if (first_arg > 1) free_svalue(args+1); } else error_index = 0; return error_index; } /* setup_efun_callback() */ /*-------------------------------------------------------------------------*/ object_t * callback_object (callback_t *cb) /* Return the object to call from the callback structure <cb>. * If the object is destructed, return NULL. */ { object_t *ob; if (cb->is_lambda) ob = !CLOSURE_MALLOCED(cb->function.lambda.x.closure_type) ? cb->function.lambda.u.ob : cb->function.lambda.u.lambda->ob; else ob = cb->function.named.ob; return check_object(ob); } /* callback_object() */ /*-------------------------------------------------------------------------*/ svalue_t * execute_callback (callback_t *cb, int nargs, Bool keep, Bool toplevel) /* Call the callback <cb> with the <nargs> arguments already pushed * onto the stack. Result is a pointer to a static area with the * result from the call. * * If an error occurs (the object to call has been destructed or can't * be swapped in), NULL is returned. * * If <keep> is TRUE, the callback structure will not be freed. * If <toplevel> is TRUE, the callback is called directly from * the backend (as opposed to from a running program) which makes * certain extra setups for current_object and current_prog necessary. * * This function is #defined to two macros: * * apply_callback(cb,nargs): call a callback from a running program, * the callback is kept. * backend_callback(cb,nargs): call a callback from the backend * and free it afterwards. */ { object_t *ob; int num_arg; ob = callback_object(cb); if (!ob || (O_PROG_SWAPPED(ob) && load_ob_from_swap(ob) < 0) ) { free_callback(cb); return NULL; } /* Push the arguments, if any, onto the stack */ num_arg = cb->num_arg; if (num_arg) { svalue_t * argp; int j; if (num_arg > 1) argp = cb->arg.u.lvalue; else argp = &(cb->arg); for (j = 0; j < num_arg; j++, argp++) { inter_sp++; if (destructed_object_ref(argp)) { *inter_sp = const0; assign_svalue(argp, &const0); } else if (keep) assign_svalue_no_free(inter_sp, argp); else transfer_svalue_no_free(inter_sp, argp); } } if (!keep) { /* The arguments are gone from the callback */ if (cb->num_arg > 1) xfree(cb->arg.u.lvalue); cb->num_arg = 0; cb->arg.type = T_INVALID; } /* Now call the function */ if (toplevel) current_object = ob; /* Need something valid here */ if (cb->is_lambda) { if (toplevel && cb->function.lambda.x.closure_type < CLOSURE_SIMUL_EFUN && cb->function.lambda.x.closure_type >= CLOSURE_EFUN) { /* efun, operator or sefun closure called from the backend: * we need the program for a proper traceback. We made sure * before that the program has been swapped in. */ current_prog = ob->prog; } call_lambda(&(cb->function.lambda), num_arg + nargs); transfer_svalue(&apply_return_value, inter_sp); inter_sp--; } else { if (!apply(cb->function.named.name, ob, num_arg + nargs)) transfer_svalue(&apply_return_value, &const0); } if (!keep) { /* Free the remaining information from the callback structure */ free_callback(cb); } /* Return the result */ return &apply_return_value; } /* execute_callback() */ /*-------------------------------------------------------------------------*/ #ifdef DEBUG void count_callback_extra_refs (callback_t *cb) /* Count all the refs in the callback to verify the normal refcounting. */ { if (!cb->is_lambda) count_extra_ref_in_object(cb->function.named.ob); else count_extra_ref_in_vector(&cb->function.lambda, 1); if (cb->num_arg == 1) count_extra_ref_in_vector(&(cb->arg), 1); else if (cb->num_arg > 1) count_extra_ref_in_vector(cb->arg.u.lvalue, (size_t)cb->num_arg); } /* count_callback_extra_refs() */ #endif /* DEBUG */ #ifdef GC_SUPPORT /*-------------------------------------------------------------------------*/ void clear_ref_in_callback (callback_t *cb) /* GC support: clear the refs in the memory held by the callback * structure (but not of the structure itself!) */ { if (cb->num_arg == 1) clear_ref_in_vector(&(cb->arg), 1); else if (cb->num_arg > 1) { clear_ref_in_vector(cb->arg.u.lvalue, (size_t)cb->num_arg); if (!cb->arg.x.extern_args) clear_memory_reference(cb->arg.u.lvalue); } if (cb->is_lambda) clear_ref_in_vector(&(cb->function.lambda), 1); else { #ifdef DEBUG if (!callback_object(cb)) fatal("GC run on callback with stale object.\n"); #endif clear_object_ref(cb->function.named.ob); } } /* clear_ref_in_callback() */ /*-------------------------------------------------------------------------*/ void count_ref_in_callback (callback_t *cb) /* GC support: count the refs in the memory held by the callback * structure (but not of the structure itself!) */ { if (cb->num_arg == 1) count_ref_in_vector(&(cb->arg), 1); else if (cb->num_arg > 1) { count_ref_in_vector(cb->arg.u.lvalue, (size_t)cb->num_arg); if (!cb->arg.x.extern_args) note_malloced_block_ref(cb->arg.u.lvalue); } #ifdef DEBUG if (!callback_object(cb)) fatal("GC run on callback with stale object.\n"); #endif if (cb->is_lambda) count_ref_in_vector(&(cb->function.lambda), 1); else { cb->function.named.ob->ref++; count_ref_from_string(cb->function.named.name); } } /* count_ref_in_callback() */ #endif /*-------------------------------------------------------------------------*/ void init_driver_hooks() /* Init the driver hooks. */ { int i; for (i = NUM_DRIVER_HOOKS; --i >= 0; ) { put_number(driver_hook + i, 0); } } /* init_driver_hooks() */ #ifdef USE_FREE_CLOSURE_HOOK /*-------------------------------------------------------------------------*/ void free_closure_hooks (svalue_t *svp, int count) /* "Free" the <count> closures in <svp>[], ie. store them for later * deletion by the backend. * * This is used for closures which are held by the gamedriver, ie. * have only one reference like the hooks or the prompt, and may be * freed while they are executed. */ { svalue_t *new; if (max_old_hooks < num_old_hooks + count) { int delta; delta = (count > NUM_CLOSURE_HOOKS) ? count : NUM_CLOSURE_HOOKS; if (old_hooks) new = rexalloc(old_hooks , (max_old_hooks + delta) * sizeof(*new)); else new = xalloc(delta * sizeof(*new)); if (!new) return; old_hooks = new; max_old_hooks += delta; } memcpy(old_hooks + num_old_hooks, svp, count * sizeof(*svp)); num_old_hooks += count; } /* free_closure_hooks() */ /*-------------------------------------------------------------------------*/ void free_old_driver_hooks (void) /* Free all closures queued in <old_hooks>, and the <old_hooks> array itself. * This function is called from the backend and from the garbage collector. */ { int i; if (!old_hooks) return; for (i = num_old_hooks; i--;) { if (old_hooks[i].type == T_CLOSURE && old_hooks[i].x.closure_type == CLOSURE_LAMBDA) { old_hooks[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA; } free_svalue(&old_hooks[i]); } xfree(old_hooks); old_hooks = NULL; num_old_hooks = max_old_hooks = 0; } /* free_old_driver_hooks() */ #endif /* USE_FREE_CLOSURE_HOOK */ /*-------------------------------------------------------------------------*/ #ifdef USE_SET_LIGHT void add_light (object_t *p, int n) /* The light emission of <p> and all surrounding objects is * changed by <n>. */ { if (n == 0) return; do { p->total_light += n; } while ( NULL != (p = p->super) ); } /* add_light() */ #endif /*-------------------------------------------------------------------------*/ Bool match_string (char * match, char * str, mp_int len) /* Test if the string <str> of length <len> matches the pattern <match>. * Allowed wildcards are * *: matches any sequence * ?: matches any single character * \: escapes the following wildcard * * The function is used by the compiler for inheritance specs, and by * e_get_dir(). * TODO: Another utils.c candidate. */ { /* Loop over match and str */ for (;;) { /* Act on the current match character */ switch(*match) { case '?': if (--len < 0) return MY_FALSE; str++; match++; continue; case '*': { char *str2; mp_int matchlen; for (;;) { switch (*++match) { case '\0': return len >= 0; case '?': --len; str++; case '*': continue; case '\\': match++; default: break; } break; } if (len <= 0) return MY_FALSE; str2 = strpbrk(match + 1, "?*\\"); if (!str2) { if ( (matchlen = strlen(match)) > len) return MY_FALSE; return strncmp(match, str + len - matchlen, matchlen) == 0; } else { matchlen = str2 - match; } /* matchlen >= 1 */ if ((len -= matchlen) >= 0) do { if ( !(str2 = memmem(match, matchlen, str, len + matchlen)) ) return MY_FALSE; len -= str2 - str; if (match_string(match + matchlen, str2 + matchlen, len)) return MY_TRUE; str = str2 + 1; } while (--len >= 0); return MY_FALSE; } case '\0': return len == 0; case '\\': match++; if (*match == '\0') return MY_FALSE; /* Fall through ! */ default: if (--len >= 0 && *match == *str) { match++; str++; continue; } return MY_FALSE; } /* switch(*match) */ } /* for(;;) */ } /* match_string() */ /*-------------------------------------------------------------------------*/ void print_svalue (svalue_t *arg) /* Print the value <arg> to the interactive user (exception: strings * are also written to non-interactive command_givers via tell_npc()). * The function is called for the efun write() and from * interpret:do_trace_call(). * * The function can only print scalar values - arrays, mappings and * closures are only hinted at. */ { if (arg == NULL) { add_message("<NULL>"); } else if (arg->type == T_STRING) { interactive_t *ip; /* Strings sent to monsters are now delivered */ if (command_giver && (command_giver->flags & O_ENABLE_COMMANDS) && !(O_SET_INTERACTIVE(ip, command_giver)) ) { tell_npc(command_giver, arg->u.string); } else { add_message("%s", arg->u.string); } } else if (arg->type == T_OBJECT) add_message("OBJ(%s)", arg->u.ob->name); else if (arg->type == T_NUMBER) add_message("%ld", arg->u.number); else if (arg->type == T_FLOAT) { char buff[120]; sprintf(buff, "%g", READ_DOUBLE( arg ) ); add_message(buff); } else if (arg->type == T_POINTER) add_message("<ARRAY>"); else if (arg->type == T_MAPPING) add_message("<MAPPING>"); else if (arg->type == T_CLOSURE) add_message("<CLOSURE>"); else add_message("<OTHER:%d>", arg->type); } /* print_svalue() */ /*=========================================================================*/ /* EFUNS */ /*-------------------------------------------------------------------------*/ static Bool validate_shadowing (object_t *ob) /* May current_object shadow object 'ob'? We perform a number of tests * including calling master:query_allow_shadow(). * TODO: Move all shadow functions into a separate file. */ { int i, j; object_t *cob; program_t *shadow, *victim; svalue_t *ret; cob = current_object; shadow = cob->prog; if (cob->flags & O_DESTRUCTED) return MY_FALSE; if (O_PROG_SWAPPED(ob)) if (load_ob_from_swap(ob) < 0) error("Out of memory: unswap object '%s'\n", ob->name); victim = ob->prog; if (victim->flags & P_NO_SHADOW) error("shadow '%s' on '%s': Can't shadow a 'no_shadow' program.\n" , cob->name, ob->name); if (cob->flags & O_SHADOW) { shadow_t *shadow_sent = O_GET_SHADOW(cob); if (shadow_sent->shadowing) error("shadow '%s' on '%s': Already shadowing.\n" , cob->name, ob->name); if (shadow_sent->shadowed_by) error("shadow '%s' on '%s': Can't shadow when shadowed.\n" , cob->name, ob->name); } if (cob->super) error("shadow '%s' on '%s': The shadow resides inside another object ('%s').\n" , cob->name, ob->name, cob->super->name); if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing) error("shadow '%s' on '%s': Can't shadow a shadow.\n" , cob->name, ob->name); if (ob == cob) error("shadow '%s' on '%s': Can't shadow self.\n" , cob->name, ob->name); /* Make sure that we don't shadow 'nomask' functions. */ for (i = shadow->num_function_names; --i >= 0; ) { funflag_t flags; char *name; program_t *progp; j = shadow->function_names[i]; flags = shadow->functions[j]; progp = shadow; while (flags & NAME_INHERITED) { inherit_t *inheritp; inheritp = &progp->inherit[flags & INHERIT_MASK]; j -= inheritp->function_index_offset; progp = inheritp->prog; flags = progp->functions[j]; } memcpy(&name, FUNCTION_NAMEP(progp->program + (flags & FUNSTART_MASK)) , sizeof name ); if ( (j = find_function(name, victim)) >= 0 && victim->functions[j] & TYPE_MOD_NO_MASK ) { error("shadow '%s' on '%s: Illegal to shadow 'nomask' function \"%s\".\n" , ob->name, cob->name, name); } } push_object(ob); ret = apply_master(STR_QUERY_SHADOW, 1); if (!((ob->flags|cob->flags) & O_DESTRUCTED) && ret && !(ret->type == T_NUMBER && ret->u.number == 0)) { return MY_TRUE; } return MY_FALSE; } /* validate_shadowing() */ /*-------------------------------------------------------------------------*/ svalue_t * f_shadow (svalue_t *sp) /* TEFUN shadow() * * object shadow(object ob, int flag) * * If flag is non-zero then the current object will shadow ob. If * flag is 0 then either 0 will be returned or the object that is * shadowing ob. * * The calling object must be permitted by the master object to * do the shadowing. In most installations, an object that * defines the function query_prevent_shadow() to return 1 * can't be shadowed, and the shadow() function will return 0 * instead of ob. * * shadow() also fails if the calling object tries to shadow * a function that was defined as ``nomask'', if the program was * compiled with the #pragma no_shadow, or if the calling * object is already shadowing, is being shadowed, or has an * environment. Also the target ob must not be shadowing * something else. * * If an object A shadows an object B then all call_other() to B * will be redirected to A. If object A has not defined the * function, then the call will be passed to B. There is only on * object that can call functions in B with call_other(), and * that is A. Not even object B can call_other() itself. All * normal (internal) function calls inside B will however remain * internal to B. */ { object_t *ob; /* Get the arguments */ if (sp[-1].type != T_OBJECT) bad_xefun_arg(1, sp); if (sp->type != T_NUMBER) bad_xefun_arg(2, sp); sp--; ob = sp->u.ob; deref_object(ob, "shadow"); if (sp[1].u.number == 0) { /* Just look for a possible shadow */ ob = (ob->flags & O_SHADOW) ? O_GET_SHADOW(ob)->shadowed_by : NULL; if (ob) sp->u.ob = ref_object(ob, "shadow"); else put_number(sp, 0); return sp; } sp->type = T_NUMBER; /* validate_shadowing might destruct ob */ assign_eval_cost(); inter_sp = sp; if (validate_shadowing(ob)) { /* Shadowing allowed */ shadow_t *shadow_sent, *co_shadow_sent; /* The shadow is entered first in the chain. */ assert_shadow_sent(ob); if (O_IS_INTERACTIVE(ob)) O_GET_INTERACTIVE(ob)->catch_tell_activ = MY_TRUE; shadow_sent = O_GET_SHADOW(ob); while (shadow_sent->shadowed_by) { ob = shadow_sent->shadowed_by; shadow_sent = O_GET_SHADOW(ob); } assert_shadow_sent(current_object); co_shadow_sent = O_GET_SHADOW(current_object); co_shadow_sent->shadowing = ob; shadow_sent->shadowed_by = current_object; put_ref_object(sp, ob, "shadow"); return sp; } /* Shadowing not allowed */ put_number(sp, 0); return sp; } /* f_shadow() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_shadowing (svalue_t *sp) /* TEFUN query_shadowing() * * object query_shadowing (object obj) * * The function returns the object which <obj> is currently * shadowing, or 0 if <obj> is not a shadow. */ { object_t *ob; if (sp->type != T_OBJECT) bad_xefun_arg(1, sp); ob = sp->u.ob; deref_object(ob, "shadow"); ob = (ob->flags & O_SHADOW) ? O_GET_SHADOW(ob)->shadowing : NULL; if (ob) sp->u.ob = ref_object(ob, "shadow"); else put_number(sp, 0); return sp; } /* f_query_shadowing() */ /*-------------------------------------------------------------------------*/ svalue_t * f_unshadow (svalue_t *sp) /* TEFUN unshadow() * * void unshadow(void) * * The calling object stops shadowing any other object. * If the calling object is being shadowed, that is also stopped. */ { shadow_t *shadow_sent, *shadowing_sent; object_t *shadowing, *shadowed_by; if (current_object->flags & O_SHADOW && NULL != (shadowing = (shadow_sent = O_GET_SHADOW(current_object))->shadowing) ) { shadowing_sent = O_GET_SHADOW(shadowing); /* Our victim is now shadowed by our shadow */ shadowed_by = shadow_sent->shadowed_by; shadowing_sent->shadowed_by = shadowed_by; if ( NULL != shadowed_by ) { /* Inform our shadow about its new victim */ O_GET_SHADOW(shadowed_by)->shadowing = shadow_sent->shadowing; } else { /* Our victim is no longer shadowed, so maybe it * doesn't need its shadow sentence anymore. */ remove_shadow_actions(current_object, shadowing); check_shadow_sent(shadowing); } shadow_sent->shadowed_by = NULL; shadow_sent->shadowing = NULL; check_shadow_sent(current_object); } return sp; } /* f_unshadow() */ /*-------------------------------------------------------------------------*/ svalue_t * f_set_driver_hook (svalue_t *sp) /* TEFUN set_driver_hook() * * void set_driver_hook(int what, closure arg) * void set_driver_hook(int what, string arg) * void set_driver_hook(int what, string * arg) * * This privileged efun sets the driver hook 'what' (values are * defined in /sys/driverhooks.h) to 'arg'. * The exact meanings and types of 'arg' depend of the hook set. * To remove a hook, set 'arg' to 0. * * Raises a privilege violation ("set_driver_hook", this_object, what). * * See hooks(C) for a detailed discussion. */ { p_int n; svalue_t old; /* Get the arguments */ if (sp[-1].type != T_NUMBER || (n = sp[-1].u.number) < 0 || n > NUM_DRIVER_HOOKS) { bad_xefun_arg(1, sp); } /* Legal call? */ if (!_privilege_violation("set_driver_hook", sp-1, sp)) { free_svalue(sp); return sp - 2; } old = driver_hook[n]; /* Remember this for freeing */ /* Check the type of the hook and set it if ok */ switch(sp->type) { case T_NUMBER: if (sp->u.number != 0) goto bad_arg_2; put_number(driver_hook + n, 0); break; case T_STRING: { char *str; if ( !((1 << T_STRING) & hook_type_map[n]) ) goto bad_arg_2; if ( NULL != (str = make_shared_string(sp->u.string)) ) { free_svalue(sp); put_string(driver_hook + n, str); if (n == H_NOECHO) mudlib_telopts(); } else { error("Out of memory (%lu bytes) for driver hook\n" , (unsigned long) strlen(sp->u.string)); } break; } case T_MAPPING: if (!sp->u.map->num_values || sp->u.map->ref != 1 /* add_to_mapping() could zero num_values */) { goto bad_arg_2; } goto default_test; case T_POINTER: { vector_t *v = sp->u.vec; if (v->ref > 1) { /* We need a genuine copy of the array */ deref_array(v); sp->u.vec = v = slice_array(v, 0, VEC_SIZE(v)-1); } if (n == H_INCLUDE_DIRS) { inter_sp = sp; set_inc_list(v); } goto default_test; } case T_CLOSURE: if (sp->x.closure_type == CLOSURE_UNBOUND_LAMBDA && sp->u.lambda->ref == 1) { driver_hook[n] = *sp; driver_hook[n].x.closure_type = CLOSURE_LAMBDA; driver_hook[n].u.lambda->ob = master_ob; if (n == H_NOECHO) { mudlib_telopts(); } break; } else if (!CLOSURE_IS_LFUN(sp->x.closure_type)) { goto bad_arg_2; } /* FALLTHROUGH */ default: default_test: if ( !((1 << sp->type) & hook_type_map[n]) ) { bad_arg_2: bad_xefun_arg(2, sp); break; /* flow control hint */ } driver_hook[n] = *sp; if (n == H_NOECHO) { mudlib_telopts(); } break; } #ifdef USE_FREE_CLOSURE_HOOK if (old.type != T_NUMBER) free_closure_hooks(&old, 1); /* free it in the backend */ #else /* The object reference in bound closures is not counted! */ if (old.type == T_CLOSURE && old.x.closure_type == CLOSURE_LAMBDA) { old.x.closure_type = CLOSURE_UNBOUND_LAMBDA; } free_svalue(&old); #endif return sp - 2; } /* f_set_driver_hook() */ /*-------------------------------------------------------------------------*/ #ifdef F_SET_AUTO_INCLUDE_STRING svalue_t * f_set_auto_include_string (svalue_t *sp) /* EFUN set_auto_include_string() * * void set_auto_include(string arg) * * If <arg> is a string, it will be automatically included into every * compiled LPC object. * * This is useful to enforce global definitions, e.g. * ``#pragma combine_strings'' or ``#pragma strict_types''. The * calling object needs to be privileged by the master object. * * Note that this efun is just a deprecated frontend for * set_driver_hook(H_AUTO_INCLUDE). */ { if (sp->type != T_STRING) bad_xefun_arg(1, sp); if (_privilege_violation("set_auto_include_string", sp, sp) > 0) { char *str; svalue_t old; old = driver_hook[H_AUTO_INCLUDE]; /* Remember this for freeing */ if ( NULL != (str = make_shared_string(sp->u.string)) ) { put_string(driver_hook + H_AUTO_INCLUDE, str); } else { error("Out of memory (%lu bytes) for driver hook\n" , (unsigned long) strlen(sp->u.string)); } #ifdef USE_FREE_CLOSURE_HOOK if (old.type != T_NUMBER) free_closure_hooks(&old, 1); /* free it in the backend */ #else /* The object reference in bound closures is not counted! */ if (old.type == T_CLOSURE && old.x.closure_type == CLOSURE_LAMBDA) { old.x.closure_type = CLOSURE_UNBOUND_LAMBDA; } free_svalue(&old); #endif } return sp - 1; } /* set_auto_include() */ #endif /* F_SET_AUTO_INCLUDE_STRING */ /*-------------------------------------------------------------------------*/ svalue_t * f_rename_object (svalue_t *sp) /* TEFUN rename_object() * * void rename_object (object ob, string new_name); * * Give the object <ob> a new object name <new_name>. Causes a privilege * violation. The new name must not contain a # character, except * at the end, to avoid confusion with clone numbers. * * Raises a privilege violation ("rename_object", this_object(), ob, name). */ { object_t *ob; char *name; mp_int length; inter_sp = sp; /* this is needed for assert_master_ob_loaded(), and for * the possible errors before. */ if (sp[-1].type != T_OBJECT) bad_xefun_arg(1, sp); if (sp[0].type != T_STRING) bad_xefun_arg(2, sp); ob = sp[-1].u.ob; name = sp[0].u.string; /* Remove leading '/' if any. */ while(name[0] == '/') name++; /* Truncate possible .c in the object name. */ length = strlen(name); if (name[length-2] == '.' && name[length-1] == 'c') { /* A new writeable copy of the name is needed. */ char *p; p = (char *)alloca(length+1); strcpy(p, name); name = p; name[length -= 2] = '\0'; } { char c; char *p; mp_int i; i = length; p = name + length; while (--i > 0) { /* isdigit would need to check isascii first... */ if ( (c = *--p) < '0' || c > '9' ) { if (c == '#' && length - i > 1) { error("Illegal name to rename_object: '%s'.\n", name); } break; } } } if (lookup_object_hash(name)) { error("Attempt to rename to object '%s'\n", name); } assert_master_ob_loaded(); if (master_ob == ob) error("Attempt to rename the master object\n"); if (privilege_violation4("rename_object", ob, name, 0, sp)) { remove_object_hash(ob); xfree(ob->name); ob->name = string_copy(name); enter_object_hash(ob); } free_svalue(sp--); free_svalue(sp--); return sp; } /* f_rename_object() */ /*-------------------------------------------------------------------------*/ void e_write (svalue_t *arg) /* EFUN write() * * void write (mixed msg) * * Write out something to the current user. What exactly will * be printed in the end depends of the type of msg. * * If it is a string or a number then just prints it out. * * If it is an object then the object will be printed in the * form: "OBJ("+file_name((object)mix)+")" * * If it is an array just "<ARRAY>" will be printed. * If it is a mapping just "<MAPPING>" will be printed. * If it is a closure just "<CLOSURE>" will be printed. * * If the write() function is invoked by a command of an living * but not interactive object and the given argument is a string * then the lfun catch_tell() of the living will be invoked with * the message as argument. */ { object_t *save_command_giver = command_giver; if (!command_giver && current_object->flags & O_SHADOW && O_GET_SHADOW(current_object)->shadowing) { command_giver = current_object; } if (command_giver) { /* Send the message to the first object in the shadow list */ if (command_giver->flags & O_SHADOW) while( O_GET_SHADOW(command_giver)->shadowing ) command_giver = O_GET_SHADOW(command_giver)->shadowing; } print_svalue(arg); command_giver = check_object(save_command_giver); } /* e_write() */ /*-------------------------------------------------------------------------*/ static void extract_limits ( struct limits_context_s * result , svalue_t *svp , int num , Bool tagged ) /* Extract the user-given runtime limits from <svp>... * and store them into <result>. If <tagged> is FALSE, <svp> points to an array * with the <num> values stored at the proper indices, otherwise <svp> points * to a series of <num>/2 (tag, value) pairs. * * If the function encounters illegal limit tags or values, it throws * an error. */ { char * limitnames[] = { "LIMIT_EVAL", "LIMIT_ARRAY", "LIMIT_MAPPING" , "LIMIT_BYTE", "LIMIT_FILE" }; /* Set the defaults (unchanged) limits */ result->max_eval = max_eval_cost; result->max_array = max_array_size; result->max_mapping = max_mapping_size; result->max_callouts = max_callouts; result->max_byte = max_byte_xfer; result->max_file = max_file_xfer; if (!tagged) { p_int val; int limit; for (limit = 0; limit < LIMIT_MAX && limit < num; limit++) { if (svp[limit].type != T_NUMBER) error("Illegal %s value: not a number\n", limitnames[limit]); /* TODO: Give type and value */ val = svp[limit].u.number; if (val >= 0) { switch(limit) { case LIMIT_EVAL: result->max_eval = val; break; case LIMIT_ARRAY: result->max_array = val; break; case LIMIT_MAPPING: result->max_mapping = val; break; case LIMIT_BYTE: result->max_byte = val; break; case LIMIT_FILE: result->max_file = val; break; case LIMIT_CALLOUTS: result->max_callouts = val; break; default: error("Unimplemented limit #%d\n", limit); } } else if (val == LIMIT_DEFAULT) { switch(limit) { case LIMIT_EVAL: result->max_eval = def_eval_cost; break; case LIMIT_ARRAY: result->max_array = def_array_size; break; case LIMIT_MAPPING: result->max_mapping = def_mapping_size; break; case LIMIT_BYTE: result->max_byte = def_byte_xfer; break; case LIMIT_FILE: result->max_file = def_file_xfer; break; case LIMIT_CALLOUTS: result->max_callouts = def_callouts; break; default: error("Unimplemented limit #%d\n", limit); } } else if (val != LIMIT_KEEP) error("Illegal %s value: %ld\n", limitnames[limit], val); } } else { int i; for (i = 0; i < num - 1; i += 2) { p_int val; int limit; if (svp[i].type != T_NUMBER) error("Illegal limit tag: not a number.\n"); /* TODO: Give type and value */ limit = (int)svp[i].u.number; if (limit < 0 || limit >= LIMIT_MAX) error("Illegal limit tag: %ld\n", (long)limit); if (svp[i+1].type != T_NUMBER) error("Illegal %s value: not a number\n", limitnames[limit]); /* TODO: Give type and value */ val = svp[i+1].u.number; if (val >= 0) { switch(limit) { case LIMIT_EVAL: result->max_eval = val; break; case LIMIT_ARRAY: result->max_array = val; break; case LIMIT_MAPPING: result->max_mapping = val; break; case LIMIT_BYTE: result->max_byte = val; break; case LIMIT_FILE: result->max_file = val; break; case LIMIT_CALLOUTS: result->max_callouts = val; break; default: error("Unimplemented limit #%d\n", limit); } } else if (val == LIMIT_DEFAULT) { switch(limit) { case LIMIT_EVAL: result->max_eval = def_eval_cost; break; case LIMIT_ARRAY: result->max_array = def_array_size; break; case LIMIT_MAPPING: result->max_mapping = def_mapping_size; break; case LIMIT_BYTE: result->max_byte = def_byte_xfer; break; case LIMIT_FILE: result->max_file = def_file_xfer; break; case LIMIT_CALLOUTS: result->max_callouts = def_callouts; break; default: error("Unimplemented limit #%d\n", limit); } } else if (val != LIMIT_KEEP) error("Illegal %s value: %ld\n", limitnames[limit], val); } } } /* extract_limits() */ /*-------------------------------------------------------------------------*/ svalue_t * f_limited (svalue_t * sp, int num_arg) /* VEFUN limited() * * mixed limited(closure fun) * mixed limited(closure fun, int tag, int value, ...) * mixed limited(closure fun, int * limits [, mixed args...] ) * * Call the function <fun> and execute it with the given runtime limits. * After the function exits, the currently active limits are restored. * Result of the efun is the result of the closure call. * * The arguments can be given in two ways: as an array (like the one * returned from query_limits(), or as a list of tagged values. * If the efun is used without any limit specification, all limits * are supposed to be 'unlimited'. * * The limit settings recognize two special values: * LIMIT_UNLIMITED: the limit is deactivated * LIMIT_KEEP: the former setting is kept * LIMIT_DEFAULT: the 'global' default setting is used. * * The efun causes a privilege violation ("limited", current_object, closure). */ { svalue_t *argp; struct limits_context_s limits; int cl_args; if (!num_arg) error("No arguments given.\n"); argp = sp - num_arg + 1; cl_args = 0; if (argp->type != T_CLOSURE) bad_xefun_vararg(1, sp); /* Get the limits */ if (num_arg == 1) { limits.max_eval = 0; limits.max_array = 0; limits.max_mapping = 0; limits.max_callouts = 0; limits.max_byte = 0; limits.max_file = 0; } else if (argp[1].type == T_POINTER) { extract_limits(&limits, argp[1].u.vec->item , (int)VEC_SIZE(argp[1].u.vec) , MY_FALSE); cl_args = num_arg - 2; } else if (num_arg % 2 == 1) { extract_limits(&limits, argp+1, num_arg-1, MY_TRUE); cl_args = 0; } else bad_xefun_vararg(num_arg, sp); /* If this object is destructed, no extern calls may be done */ if (current_object->flags & O_DESTRUCTED || !_privilege_violation("limited", argp, sp) ) { sp = pop_n_elems(num_arg, sp); sp++; put_number(sp, 0); } else { struct limits_context_s context; /* Save the current runtime limits and set the new ones */ save_limits_context(&context); context.rt.last = rt_context; rt_context = (rt_context_t *)&context; max_eval_cost = limits.max_eval ? limits.max_eval + eval_cost : 0; /* Make sure that we get the requested amount of ticks, but remember * that '0' means 'limitless' */ max_array_size = limits.max_array; max_mapping_size = limits.max_mapping; max_byte_xfer = limits.max_byte; max_file_xfer = limits.max_file; max_callouts = limits.max_callouts; assign_eval_cost(); inter_sp = sp; call_lambda(argp, cl_args); sp = inter_sp; /* Overwrite the closure with the result */ free_svalue(argp); /* The closure might have self-destructed */ *argp = *sp; sp--; /* Free the remaining arguments from the efun call */ sp = pop_n_elems(num_arg - cl_args - 1, sp); /* Restore the old limits */ max_eval_cost = limits.max_eval; /* the +eval_cost above was good for proper execution, * but might mislead the eval_cost evaluation in the * restore(). */ rt_context = context.rt.last; restore_limits_context(&context); } /* Stack is clean and sp points to the result */ return sp; } /* f_limited() */ /*-------------------------------------------------------------------------*/ svalue_t * f_set_limits (svalue_t * sp, int num_arg) /* VEFUN set_limits() * * void set_limits(int tag, int value, ...) * void set_limits(int * limits) * * Set the default runtime limits from the given arguments. The new limits * will be in effect for the next execution thread. * * The arguments can be given in two ways: as an array (like the one * returned from query_limits(), or as a list of tagged values. * The limit settings recognize two special values: * LIMIT_UNLIMITED: the limit is deactivated * LIMIT_KEEP: the former setting is kept * * The efun causes a privilege violation ("set_limits", current_object, first * arg). */ { svalue_t *argp; struct limits_context_s limits; if (!num_arg) error("No arguments given.\n"); argp = sp - num_arg + 1; if (num_arg == 1 && argp->type == T_POINTER) extract_limits(&limits, argp->u.vec->item, (int)VEC_SIZE(argp->u.vec) , MY_FALSE); else if (num_arg % 2 == 0) extract_limits(&limits, argp, num_arg, MY_TRUE); else bad_xefun_vararg(num_arg, sp); if (_privilege_violation("set_limits", argp, sp)) { /* Now store the parsed limits into the variables */ def_eval_cost = limits.max_eval; def_array_size = limits.max_array; def_mapping_size = limits.max_mapping; def_byte_xfer = limits.max_byte; def_file_xfer = limits.max_file; def_callouts = limits.max_callouts; } sp = pop_n_elems(num_arg, sp); return sp; } /* f_set_limits() */ /*-------------------------------------------------------------------------*/ svalue_t * f_query_limits (svalue_t * sp) /* TEFUN query_limits() * * int * query_limits(int defaults) * * Return an array with the current runtime limits, resp. if defaults * is true, the default runtime limits. The entries in the returned * array are: * * int[LIMIT_EVAL]: the max number of eval costs * int[LIMIT_ARRAY]: the max number of array entries * int[LIMIT_MAPPING]: the max number of mapping entries * int[LIMIT_BYTE]: the max number of bytes for one read/write_bytes() * int[LIMIT_FILE]: the max number of bytes for one read/write_file() * * A limit of '0' means 'no limit'. */ { vector_t *vec; Bool def; if (sp->type != T_NUMBER) bad_xefun_arg(1, sp); def = sp->u.number != 0; vec = allocate_uninit_array(LIMIT_MAX); if (!vec) error("(query_limits) Out of memory: array[%d] for result.\n" , LIMIT_MAX); put_number(vec->item+LIMIT_EVAL, def ? def_eval_cost : max_eval_cost); put_number(vec->item+LIMIT_ARRAY, def ? def_array_size : max_array_size); put_number(vec->item+LIMIT_MAPPING, def ? def_mapping_size : max_mapping_size); put_number(vec->item+LIMIT_BYTE, def ? def_byte_xfer : max_byte_xfer); put_number(vec->item+LIMIT_FILE, def ? def_file_xfer : max_file_xfer); put_number(vec->item+LIMIT_CALLOUTS, def ? def_callouts : max_callouts); /* No free_svalue: sp is a number */ put_array(sp, vec); return sp; } /* f_query_limits() */ /*=========================================================================*/ /* INVENTORY EFUNS */ /*-------------------------------------------------------------------------*/ void move_object (void) /* Move the object inter_sp[-1] into object inter_sp[0]; both objects * are removed from the stack. * * The actual move performed by the hooks H_MOVE_OBJECT0/1, this * function is called to implement the efuns move_object() and transfer(). */ { lambda_t *l; object_t *save_command = command_giver; if (NULL != ( l = driver_hook[H_MOVE_OBJECT1].u.lambda) ) { l->ob = inter_sp[-1].u.ob; call_lambda(&driver_hook[H_MOVE_OBJECT1], 2); } else if (NULL != ( l = driver_hook[H_MOVE_OBJECT0].u.lambda) ) { l->ob = current_object; call_lambda(&driver_hook[H_MOVE_OBJECT0], 2); } else error("Don't know how to move objects.\n"); command_giver = check_object(save_command); } /* move_object() */ /*-------------------------------------------------------------------------*/ svalue_t * f_set_environment (svalue_t *sp) /* TEFUN set_environment() * * void set_environment(object item, object env) * * The item is moved into its new environment env, which may be 0. * This efun is to be used in the H_MOVE_OBJECTx hook, as it does * nothing else than moving the item - no calls to init() or such. */ { object_t *item, *dest; object_t **pp, *ob; object_t *save_cmd = command_giver; /* Get and test the arguments */ if (sp[-1].type != T_OBJECT) bad_xefun_arg(1, sp); item = sp[-1].u.ob; if (item->flags & O_SHADOW && O_GET_SHADOW(item)->shadowing) error("Can't move an object that is shadowing.\n"); if (sp->type != T_OBJECT) { if (sp->type != T_NUMBER || sp->u.number) bad_xefun_arg(2, sp); dest = NULL; } else { dest = sp->u.ob; /* Recursive moves are not allowed. */ for (ob = dest; ob; ob = ob->super) if (ob == item) error("Can't move object inside itself.\n"); # ifdef USE_SET_LIGHT add_light(dest, item->total_light); # endif dest->flags &= ~O_RESET_STATE; } item->flags &= ~O_RESET_STATE; /* touch it */ if (item->super) { /* First remove the item out of its current environment */ Bool okey = MY_FALSE; if (item->sent) { remove_environment_sent(item); } if (item->super->sent) remove_action_sent(item, item->super); # ifdef USE_SET_LIGHT add_light(item->super, - item->total_light); # endif for (pp = &item->super->contains; *pp;) { if (*pp != item) { if ((*pp)->sent) remove_action_sent(item, *pp); pp = &(*pp)->next_inv; continue; } *pp = item->next_inv; okey = MY_TRUE; } if (!okey) fatal("Failed to find object %s in super list of %s.\n", item->name, item->super->name); } /* Now put it into its new environment (if any) */ item->super = dest; if (!dest) { item->next_inv = NULL; } else { item->next_inv = dest->contains; dest->contains = item; } command_giver = check_object(save_cmd); free_svalue(sp); sp--; free_svalue(sp); return sp - 1; } /* f_set_environment() */ /*=========================================================================*/ /* FILE EFUNS */ /*-------------------------------------------------------------------------*/ #ifdef atarist /* this code is provided to speed up ls() on an Atari ST/TT . */ #include <support.h> #include <limits.h> #include <osbind.h> extern long _unixtime PROT((unsigned, unsigned)); struct xdirect { /* inode and position in directory aren't usable in a portable way, * so why support them anyway? */ short d_namlen; char d_name[16]; int size; int time; }; typedef struct { _DTA dta; char *dirname; long status; } xdir; #define XDIR xdir static long olddta; /*-------------------------------------------------------------------------*/ static XDIR *xopendir(path) char *path; { char pattern[MAXPATHLEN+1]; XDIR *d; long status; d = (XDIR *)xalloc(sizeof(XDIR)); _unx2dos(path, pattern); strcat(pattern, "\\*.*"); olddta = Fgetdta(); Fsetdta(&d->dta); d->status = status = Fsfirst(pattern, 0xff); if (status && status != -ENOENT) { xfree(d); return 0; } d->dirname = string_copy(pattern); return d; } /*-------------------------------------------------------------------------*/ #define XOPENDIR(dest, path) ((dest) = xopendir(path)) static struct xdirect *xreaddir(d) XDIR *d; { static struct xdirect xde; if (d->status) return 0; _dos2unx(d->dta.dta_name, xde.d_name); xde.d_namlen = strlen(xde.d_name); if (FA_DIR & d->dta.dta_attribute) xde.size = -2; else xde.size = d->dta.dta_size; xde.time = _unixtime(d->dta.dta_time, d->dta.dta_date); d->status = Fsnext(); return &xde; } /*-------------------------------------------------------------------------*/ static void xclosedir(d) XDIR *d; { Fsetdta(olddta); xfree(d->dirname); xfree(d); } /*-------------------------------------------------------------------------*/ static void xrewinddir(d) XDIR *d; { long status; Fsetdta(&d->dta); d->status = status = Fsfirst(d->dirname, 0xff); } #endif /* atarist */ /*-------------------------------------------------------------------------*/ #ifndef XDIR struct xdirect { /* inode and position in directory aren't usable in a portable way, * so why support them anyway? */ short d_namlen; char *d_name; int size; int time; }; #define XOPENDIR(dest, path) (\ (!chdir(path) &&\ NULL != ((dest) = opendir("."))) ||\ (chdir(mud_lib),MY_FALSE)\ ) #define xclosedir(dir_ptr) (chdir(mud_lib),closedir(dir_ptr)) #define xrewinddir(dir_ptr) rewinddir(dir_ptr) #define XDIR DIR /*-------------------------------------------------------------------------*/ static struct xdirect * xreaddir (XDIR *dir_ptr, int mask) /* Read the next entry from <dir_ptr> and return it via a pointer * to a static xdirect structure. * <mask> is tested for GETDIR_SIZES and GETDIR_DATES - only the data * for requested items is returned. */ { static struct xdirect xde; struct generic_dirent *de; int namelen; struct stat st; de = readdir(dir_ptr); if (!de) return NULL; namelen = DIRENT_NLENGTH(de); xde.d_namlen = namelen; xde.d_name = de->d_name; if (mask & (GETDIR_SIZES|GETDIR_DATES) ) { if (ixstat(xde.d_name, &st) == -1) /* who knows... */ { xde.size = FSIZE_NOFILE; xde.time = 0; } else { if (S_IFDIR & st.st_mode) xde.size = FSIZE_DIR; else xde.size = st.st_size; xde.time = st.st_mtime; } } return &xde; } /* xreaddir() */ #endif /* XDIR */ /*-------------------------------------------------------------------------*/ static int pstrcmp (const void *p1, const void *p2) /* qsort() comparison function: strcmp() on two svalue-strings. */ { return strcmp(((svalue_t*)p1)->u.string, ((svalue_t*)p2)->u.string); } /* pstrcmp() */ /*-------------------------------------------------------------------------*/ struct get_dir_error_context { svalue_t head; XDIR *dirp; vector_t *v; }; /*-------------------------------------------------------------------------*/ static void get_dir_error_handler (svalue_t *arg) /* T_ERROR_HANDLER function: <arg> is a (struct get_dir_error_context*) * with the directory which needs to be closed. */ { struct get_dir_error_context *ecp; ecp = (struct get_dir_error_context *)arg; xclosedir(ecp->dirp); if (ecp->v) free_array(ecp->v); } /* get_dir_error_handler() */ /*-------------------------------------------------------------------------*/ vector_t * e_get_dir (char *path, int mask) /* EFUN get_dir() * * string *get_dir(string str) * string *get_dir(string str, int mask) * * This function takes a path as argument and returns an array of file * names and attributes in that directory. * * Returns 0 if the directory to search in does not exist. * * The filename part of the path may contain '*' or '?' as wildcards: * every '*' matches an arbitrary amount of characters (or just itself). * Thus get_dir("/path/ *") would return an alphabetically sorted array * of all files in directory "/path/", or just ({ "/path/ *" }) if this * file happens to exist. * * To query the content of a directory, use the directory name with a * trailing '/' or '/.', for example get_dir("/path/."). Use the * directory name as it is to get information about the directory itself. * * The optional second argument mask can be used to get * information about the specified files. * * GETDIR_EMPTY (0x00) get_dir returns an empty array (not very * useful). * GETDIR_NAMES (0x01) put the alphabetically sorted file names into * the returned array. * GETDIR_SIZES (0x02) put the file sizes unsorted into the returned * array. directories have size FSIZE_DIR (-2). * GETDIR_DATES (0x04) put the file modification dates unsorted into * the returned array. * GETDIR_PATH (0x10) if this mask bit is set, the filenames with * the full path will be returned * (GETDIR_NAMES is implied). * GETDIR_UNSORTED (0x20) if this mask bit is set, the result of will * _not_ be sorted. * GETDIR_ALL (0x07) GETDIR_NAMES|GETDIR_SIZES|GETDIR_DATES (see * examples). * * Note: You should use GETDIR_NAMES|GETDIR_UNSORTED to get the entries * in the same order as with GETDIR_SIZES and GETDIR_DATES. * * The values of mask can be added together. */ { static struct get_dir_error_context ec; /* must survive errors */ vector_t *v, *w; int i, j, count = 0; XDIR *dirp; int namelen; Bool do_match = MY_FALSE; struct xdirect *de; struct stat st; char *temppath; size_t templen; Bool in_top_dir = MY_FALSE; char *p; char *regexpr = 0; int nqueries; /* Adjust the mask for implied bits */ if (mask & GETDIR_PATH) mask |= GETDIR_NAMES; if (!path) return NULL; path = check_valid_path(path, current_object, "get_dir", MY_FALSE); if (path == NULL) return NULL; /* We need to modify the returned path, and thus to make a * writeable copy. * The path "" needs 2 bytes to store ".\0". */ temppath = alloca(strlen(path) + 2); if (strlen(path) < 2) { temppath[0] = path[0] ? path[0] : '.'; temppath[1] = '\000'; p = temppath; in_top_dir = MY_TRUE; } else { strcpy(temppath, path); /* If path ends with '/' or "/." remove it */ if ((p = strrchr(temppath, '/')) == NULL) p = temppath; if ((p[0] == '/' && p[1] == '.' && p[2] == '\0') || (p[0] == '/' && p[1] == '\0') ) *p = '\0'; in_top_dir = (p == temppath); } /* Number of data items per file */ nqueries = ((mask & GETDIR_NAMES) != 0) + ((mask & GETDIR_SIZES) != 0) + ((mask & GETDIR_DATES) != 0); if (strchr(p, '*') || ixstat(temppath, &st) < 0) { /* We got a wildcard and/or a directory: * prepare to match. */ if (*p == '\0') return NULL; regexpr = alloca(strlen(p)+2); if (p != temppath) { strcpy(regexpr, p + 1); *p = '\0'; } else { strcpy(regexpr, p); strcpy(temppath, "."); in_top_dir = MY_TRUE; } do_match = MY_TRUE; } else if (*p != '\0' && strcmp(temppath, ".")) { /* We matched a single file */ svalue_t *stmp; if (*p == '/' && *(p + 1) != '\0') p++; v = allocate_array(nqueries); stmp = v->item; if (mask & GETDIR_NAMES) { if (mask & GETDIR_PATH) { if (compat_mode) put_malloced_string(stmp, string_copy(temppath)); else put_malloced_string(stmp, add_slash(temppath)); } else { put_malloced_string(stmp, string_copy(p)); } stmp++; } if (mask & GETDIR_SIZES){ put_number(stmp, (S_IFDIR & st.st_mode) ? FSIZE_DIR : st.st_size); stmp++; } if (mask & GETDIR_DATES) { put_number(stmp, st.st_mtime); stmp++; } return v; } templen = strlen(temppath); if ( XOPENDIR(dirp, temppath) == 0) return NULL; /* Prepare the error handler to do clean up. */ ec.head.type = T_ERROR_HANDLER; ec.head.u.error_handler = get_dir_error_handler; ec.dirp = dirp; ec.v = NULL; inter_sp++; inter_sp->type = T_LVALUE; inter_sp->u.lvalue = &ec.head; /* Count files */ for (de = xreaddir(dirp, 1); de; de = xreaddir(dirp, 1)) { namelen = de->d_namlen; if (do_match) { if ( !match_string(regexpr, de->d_name, namelen) ) continue; } else { if (namelen <= 2 && *de->d_name == '.' && (namelen == 1 || de->d_name[1] == '.' ) ) continue; } count += nqueries; if (max_array_size && count >= max_array_size) break; } if (nqueries) count /= nqueries; /* Make array and put files on it. */ v = allocate_array(count * nqueries); if (count == 0) { /* This is the easy case :-) */ inter_sp--; xclosedir(dirp); return v; } ec.v = v; xrewinddir(dirp); w = v; j = 0; /* Taken into account that files might be added/deleted from outside. */ for(i = 0, de = xreaddir(dirp,mask); de; de = xreaddir(dirp,mask)) { namelen = de->d_namlen; if (do_match) { if ( !match_string(regexpr, de->d_name, namelen) ) continue; } else { if (namelen <= 2 && *de->d_name == '.' && (namelen == 1 || de->d_name[1] == '.' ) ) continue; } if (i >= count) { /* New file. Don't need efficience here, but consistence. */ vector_t *tmp, *new; count++; tmp = allocate_array(nqueries); new = add_array(v, tmp); free_array(v); free_array(tmp); ec.v = v = new; w = v; } if (mask & GETDIR_NAMES) { char *result; char *name; if ((mask & GETDIR_PATH) && !in_top_dir) { if (compat_mode) { xallocate(result, (size_t)namelen+templen+2, "getdir() names"); name = result; } else { xallocate(result, (size_t)namelen+templen+3, "getdir() names"); result[0] = '/'; name = result+1; } memcpy(name, temppath, templen); name[templen] = '/'; name += templen+1; } else { xallocate(result, (size_t)namelen+1, "getdir() names"); name = result; } if (namelen) memcpy(name, de->d_name, namelen); name[namelen] = '\0'; put_malloced_string(w->item+j, result); j++; } if (mask & GETDIR_SIZES) { put_number(w->item + j, de->size); j++; } if (mask & GETDIR_DATES) { put_number(w->item + j, de->time); j++; } i++; } xclosedir(dirp); inter_sp--; if ( !((mask ^ 1) & (GETDIR_NAMES|GETDIR_UNSORTED)) ) { /* Sort by names. */ qsort(v->item, i, sizeof v->item[0] * nqueries, pstrcmp); } return v; } /* e_get_dir() */ /*-------------------------------------------------------------------------*/ Bool e_tail (char *path) /* EFUN tail() * * void tail(string file) * * Print out the tail of a file. There is no specific amount of * lines given to the output. Only a maximum of 1000 bytes will * be printed. * * Return TRUE on success. */ { char buff[1000]; FILE *f; struct stat st; int offset; path = check_valid_path(path, current_object, "tail", MY_FALSE); if (path == NULL) return MY_FALSE; f = fopen(path, "r"); if (f == NULL) return MY_FALSE; FCOUNT_READ(path); if (fstat(fileno(f), &st) == -1) fatal("Could not stat an open file.\n"); if ( !S_ISREG(st.st_mode) ) { fclose(f); return MY_FALSE; } 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 MY_TRUE; } /* e_tail() */ /*-------------------------------------------------------------------------*/ int e_print_file (char *path, int start, int len) /* EFUN cat() * * int cat(string pathi [, int start [, int num]]) * * List the file found at path. * The optional arguments start and num are start line * number and number of lines. If they are not given the * file is printed from the beginning. * * Result is the number of lines printed, but never more than 50. */ { # define MAX_LINES 50 char buff[1000]; FILE *f; int i; if (len < 0) return 0; path = check_valid_path(path, current_object, "print_file", MY_FALSE); if (path == 0) return 0; if (start < 0) return 0; f = fopen(path, "r"); if (f == NULL) return 0; FCOUNT_READ(path); if (len == 0) len = MAX_LINES; if (len > MAX_LINES) len = MAX_LINES; if (start == 0) start = 1; for (i = 1; i < start + len; i++) { if (fgets(buff, sizeof buff, f) == 0) break; if (i >= start) add_message("%s", buff); } fclose(f); if (i <= start) return 0; if (i == MAX_LINES + start) add_message("*****TRUNCATED****\n"); return i-start; # undef MAX_LINES } /* e_print_file() */ /*-------------------------------------------------------------------------*/ Bool e_remove_file (char *path) /* EFUN rm() * * int rm(string file) * * Remove the file. Returns 0 for failure and 1 for success. */ { path = check_valid_path(path, current_object, "remove_file", MY_TRUE); if (path == 0) return MY_FALSE; if (unlink(path) == -1) return MY_FALSE; FCOUNT_DEL(path); return MY_TRUE; } /* e_remove_file() */ /*-------------------------------------------------------------------------*/ static Bool isdir (char *path) /* Helper function for copy and move: test if <path> is a directory. */ { struct stat stats; return ixstat (path, &stats) == 0 && S_ISDIR (stats.st_mode); } /* isdir() */ /*-------------------------------------------------------------------------*/ static void strip_trailing_slashes (char *path) /* Strip trailing slashed from <path>, which is modified in-place. */ { int last; last = strlen (path) - 1; while (last > 0 && path[last] == '/') path[last--] = '\0'; } /* strip_trailing_slashes() */ /*-------------------------------------------------------------------------*/ static int copy_file (char *from, char *to, int mode) /* Copy the file <from> to <to> with access <mode>. * Return 0 on success, 1 or errno on failure. */ { int ifd; int ofd; char buf[1024 * 8]; int len; /* Number of bytes read into `buf'. */ if (unlink(to) && errno != ENOENT) { debug_message("copy_file(): cannot remove '%s'\n", to); return 1; } ifd = ixopen3(from, O_RDONLY | O_BINARY, 0); if (ifd < 0) { debug_message("copy_file(): %s: open failed\n", from); return errno; } ofd = ixopen3(to, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600); if (ofd < 0) { debug_message("copy_file(): %s: open failed\n", to); close(ifd); return 1; } #ifdef HAVE_FCHMOD if (fchmod(ofd, mode)) { debug_message("copy_file(): %s: fchmod failed\n", to); close(ifd); close(ofd); unlink(to); return 1; } #endif FCOUNT_READ(from); FCOUNT_WRITE(to); while ((len = read(ifd, buf, sizeof (buf))) > 0) { int wrote = 0; char *bp = buf; do { wrote = write(ofd, bp, len); if (wrote < 0) { debug_message("copy_file(): %s: write failed\n", to); close(ifd); close(ofd); unlink(to); return 1; } bp += wrote; len -= wrote; } while (len > 0); } if (len < 0) { debug_message("copy_file(): %s: read failed\n", from); close(ifd); close(ofd); unlink(to); return 1; } if (close (ifd) < 0) { debug_message("copy_file(): %s: close failed\n", from); close(ofd); return 1; } if (close (ofd) < 0) { debug_message("copy_file(): %s: close failed\n", to); return 1; } #ifndef HAVE_FCHMOD if (chmod (to, mode)) { debug_message("copy_file(): %s: chmod failed\n", to); return 1; } #endif return 0; } /* copy_file() */ /*-------------------------------------------------------------------------*/ static int move_file (char *from, char *to) /* Move the file or directory <from> to <to>, copying it if necessary. * Result is 0 on success, 1 or errno on failure. */ { struct stat to_stats, from_stats; if (lstat(from, &from_stats) != 0) { debug_message("move_file(): %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) { debug_message("move_file(): '%s' and '%s' are the same file\n" , from, to); return 1; } if (S_ISDIR (to_stats.st_mode)) { debug_message("move_file(): %s: cannot overwrite directory\n", to); return 1; } } else if (errno != ENOENT) { perror("do_move"); debug_message("move_file(): %s: unknown error\n", to); return 1; } #ifndef RENAME_HANDLES_DIRECTORIES /* old SYSV */ if (isdir(from)) { char cmd_buf[100]; if (strchr(from, '\'') || strchr(to, '\'')) return 0; sprintf(cmd_buf, "/usr/lib/mv_dir '%s' '%s'", from, to); return system(cmd_buf); } else #endif /* RENAME_HANDLES_DIRECTORIES */ if (rename (from, to) == 0) { FCOUNT_DEL(from); return 0; } #if !defined(AMIGA) || defined(__GNUC__) if (errno != EXDEV) { debug_message("move_file(): cannot move '%s' to '%s'\n", from, to); return 1; } #endif /* rename failed on cross-filesystem link. Copy the file instead. */ if (!S_ISREG(from_stats.st_mode)) { debug_message("move_file(): cannot move '%s' across filesystems: " "Not a regular file\n", from); return 1; } if (copy_file(from, to, from_stats.st_mode & 0777)) return 1; if (unlink(from)) { debug_message("move_file(): cannot remove '%s'\n", from); return 1; } FCOUNT_DEL(from); return 0; } /* move_file() */ /*-------------------------------------------------------------------------*/ int e_rename (char *fr, char *t) /* EFUN rename() * * int rename(string from, string to) * * The efun rename() will move from to the new name to. If from * is a file, then to may be either a file or a directory. If * from is a directory, then to has to be a directory. If to * exists and is a directory, then from will be placed in that * directory and keep its original name. * * You must have write permission for from to rename the file. * * On successfull completion rename() will return 0. If any error * occurs 1 is returned. * TODO: Return useful error messages. */ { char *from, *to; int i; from = check_valid_path(fr, current_object, "rename_from", MY_TRUE); if (!from) return 1; push_apply_value(); to = check_valid_path(t, current_object, "rename_to", MY_TRUE); if (!to) { pop_apply_value(); return 1; } if (!strlen(to) && !strcmp(t, "/")) { to = 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 = alloca(strlen(to) + 1 + strlen(cp) + 1); sprintf(newto, "%s/%s", to, cp); pop_apply_value(); return move_file(from, newto); } /* File to file move */ i = move_file(from, to); pop_apply_value(); return i; } /* e_rename() */ /*-------------------------------------------------------------------------*/ svalue_t * f_copy_file (svalue_t *sp) /* TEFUN copy_file() * * int copy_file(string from, string to) * * The efun rename() will copy the file <from> to the new name <to>. * If <to> is a directory, then <from> will be placed in that * directory and keep its original name. * * You must have read permission for <from> and write permission * for the target name to copy the file. * * On successfull completion copy_file() will return 0. If any error * occurs, 1 is returned. * * TODO: Add two more args: start, length to implement slicing? * TODO:: See f-981229-10 "truncate_file()". * TODO: Return useful error messages. */ { struct stat to_stats, from_stats; char *from, *to, *cp; int result; /* Check the arguments */ if (sp[-1].type != T_STRING) bad_xefun_arg(1, sp); if (sp->type != T_STRING) bad_xefun_arg(2, sp); switch(0){default: result = 1; /* Default: failure */ from = check_valid_path(sp[-1].u.string, current_object, "copy_file" , MY_FALSE); if (!from || isdir(from)) break; /* We need our own copy of the result */ cp = alloca(strlen(from)+1); strcpy(cp, from); from = cp; to = check_valid_path(sp->u.string, current_object, "copy_file" , MY_TRUE); if (!to) break; if (!strlen(to) && !strcmp(sp->u.string, "/")) { to = alloca(3); strcpy(to, "./"); } strip_trailing_slashes(from); if (isdir(to)) { /* Target is a directory; build full target filename. */ char *newto; cp = strrchr(from, '/'); if (cp) cp++; else cp = from; newto = alloca(strlen(to) + 1 + strlen(cp) + 1); strcpy(newto, to); strcat(newto, "/"); strcat(newto, cp); to = newto; } /* Now copy the file */ if (lstat(from, &from_stats) != 0) { error("%s: lstat failed\n", from); break; } 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\n", from, to); break; } if (S_ISDIR(to_stats.st_mode)) { error("%s: cannot overwrite directory\n", to); break; } } else if (errno != ENOENT) { perror("copy_file"); error("%s: unknown error\n", to); break; } if (!S_ISREG(from_stats.st_mode)) { error("cannot copy `%s': Not a regular file\n", from); break; } result = copy_file(from, to, from_stats.st_mode & 0777); } /* switch(0) */ /* Clean up the stack and return the result */ free_svalue(sp); free_svalue(sp-1); put_number(sp-1, result); return sp-1; } /* f_copy_file() */ /***************************************************************************/