psyclpc/src/simulate.c

5574 lines
162 KiB
C
Raw Normal View History

2009-03-03 03:27:01 +00:00
/*---------------------------------------------------------------------------
* 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.
*
* The data structures, especially the runtime stack, are described where
* they are defined.
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include "typedefs.h"
#include "my-alloca.h"
#include <fcntl.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
/*-------------------------------------------------------------------------*/
#include "simulate.h"
#include "actions.h"
#include "array.h"
#include "backend.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "ed.h"
#include "filestat.h"
#include "gcollect.h"
#include "heartbeat.h"
#include "lex.h"
#include "main.h"
#include "mapping.h"
#include "mempools.h"
#include "mregex.h"
#include "mstrings.h"
#include "object.h"
#include "otable.h"
#ifdef USE_TLS
#include "pkg-tls.h"
#endif
#ifdef USE_SQLITE
#include "pkg-sqlite.h"
#endif
#include "prolang.h"
#include "sent.h"
#include "simul_efun.h"
#include "stdstrings.h"
#include "strfuns.h"
#ifdef USE_STRUCTS
#include "structs.h"
#endif
#include "swap.h"
#include "svalue.h"
#include "wiz_list.h"
#include "xalloc.h"
2009-05-21 22:41:07 +00:00
#include "i-eval_cost.h"
2009-03-03 03:27:01 +00:00
#include "../mudlib/sys/debug_info.h"
#include "../mudlib/sys/driver_hook.h"
#include "../mudlib/sys/files.h"
#include "../mudlib/sys/regexp.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 in values */
size_t max_map_keys; /* max mapping size in entries */
int32 max_eval; /* max eval cost */
int32 max_byte; /* max byte xfer */
int32 max_file; /* max file xfer */
int32 max_callouts; /* max callouts */
int32 use_cost; /* the desired cost of the evaluation */
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
* chain 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
2009-05-21 22:41:07 +00:00
= (rt_context_t *)&toplevel_context.rt;
2009-03-03 03:27:01 +00:00
/*-------------------------------------------------------------------------*/
static p_int alloc_shadow_sent = 0;
/* Statistic: how many shadow sentences have been allocated.
*/
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.
*/
#ifdef CHECK_OBJECT_REF
object_shadow_t * destructed_obj_shadows = NULL;
object_shadow_t * newly_destructed_obj_shadows = NULL;
#endif /* CHECK_OBJECT_REF */
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_objects() called from the backend.
#ifdef GC_SUPPORT
* 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 = NULL;
/* 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.
*/
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.
*/
static Bool in_fatal = MY_FALSE;
/* TRUE if fatal() is being processed.
*/
int num_error = 0;
/* Number of recursive calls to errorf().
*/
int num_warning = 0;
/* Number of recursive calls to warnf().
*/
static char emsg_buf[ERROR_BUF_LEN];
/* The buffer for the error message to be created.
*/
string_t *current_error;
string_t *current_error_file;
string_t *current_error_object_name;
mp_int current_error_line_number;
/* When an error occured during secure_apply(), these four
* variables receive allocated copies (resp. counted refs) 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.
*/
size_t def_mapping_keys = MAX_MAPPING_KEYS;
size_t max_mapping_keys = MAX_MAPPING_KEYS;
/* If != 0: the max. number of entries 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 use_eval_cost = DEF_USE_EVAL_COST;
/* How to account for the cost of the current evaluation.
* > 0: the cost to use regardless of actual cost.
* == 0: use the actual cost if the max_eval limit was less than the
* default; use 10 ticks if it was more.
* < 0: use -val% of the actual 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 ( int flags, uint offset
, volatile svalue_t ** volatile i_sp
, bytecode_p i_pc, svalue_t * i_fp
, int32 reserve_cost
#ifdef USE_NEW_INLINES
, svalue_t * i_context
#endif /* USE_NEW_INLINES */
)
/* Implement the F_CATCH 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 setjmp()
* to 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;
/* 'Fake' a subroutine call from <new_pc>
*/
#ifdef USE_NEW_INLINES
push_control_stack(INTER_SP, new_pc, i_fp, i_context);
#else
push_control_stack(INTER_SP, new_pc, i_fp);
#endif /* USE_NEW_INLINES */
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, flags)->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;
2009-05-21 22:41:07 +00:00
svalue_t catch_value;
2009-03-03 03:27:01 +00:00
/* Remove the catch context and get the old stackpointer setting */
2009-05-21 22:41:07 +00:00
sp = pull_error_context(INTER_SP, &catch_value);
2009-03-03 03:27:01 +00:00
/* 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;
*i_sp = (volatile svalue_t *)sp;
/* Restore the old eval costs */
eval_cost -= reserve_cost;
assigned_eval_cost -= reserve_cost;
/* If we ran out of memory, throw a new error */
if (!old_out_of_memory && out_of_memory)
{
errorf("(catch) Out of memory detected.\n");
}
rc = MY_TRUE;
}
else
{
/* Increase the eval_cost for the duration of the catch so that
* there is enough time left to handle an eval-too-big error.
* Do this before the check as the error handling will subtract
* the reserve again.
*/
eval_cost += reserve_cost;
assigned_eval_cost += reserve_cost;
if (max_eval_cost && eval_cost >= max_eval_cost)
{
2009-05-21 22:41:07 +00:00
errorf("Not enough eval time left for catch(): required %"PRId32
", available %"PRId32"\n", reserve_cost,
(max_eval_cost - eval_cost + reserve_cost)
2009-03-03 03:27:01 +00:00
);
/* NOTREACHED */
return MY_TRUE;
}
/* 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(inter_sp, 0);
}
eval_cost -= reserve_cost;
assigned_eval_cost -= reserve_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_map_keys = max_mapping_keys;
context->max_eval = max_eval_cost;
context->eval_cost = eval_cost;
context->max_byte = max_byte_xfer;
context->max_file = max_file_xfer;
context->use_cost = use_eval_cost;
} /* 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 (use_eval_cost == 0)
{
if (!max_eval_cost || max_eval_cost > context->max_eval)
{
assigned_eval_cost = eval_cost = context->eval_cost+10;
}
}
else if (use_eval_cost > 0)
{
int32 elapsed_cost = eval_cost - context->eval_cost;
if (elapsed_cost > use_eval_cost)
assigned_eval_cost = eval_cost = use_eval_cost + context->eval_cost;
assigned_eval_cost = eval_cost;
}
else /* (use_eval_cost < 0) */
{
int32 elapsed_cost = eval_cost - context->eval_cost;
int32 whole_fact = (-use_eval_cost) / 100;
int32 fract_fact = (-use_eval_cost) % 100;
eval_cost = context->eval_cost
+ elapsed_cost * whole_fact
+ elapsed_cost * fract_fact / 100;
assigned_eval_cost = eval_cost;
}
max_array_size = context->max_array;
max_mapping_size = context->max_mapping;
max_mapping_keys = context->max_map_keys;
max_callouts = context->max_callouts;
max_eval_cost = context->max_eval;
max_byte_xfer = context->max_byte;
max_file_xfer = context->max_file;
use_eval_cost = context->use_cost;
} /* 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)
{
#ifdef USE_ACTIONS
case COMMAND_CONTEXT:
restore_command_context(context);
break;
#endif
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(__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 (const 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;
/* Prevent double fatal. */
if (in_fatal)
{
dump_core();
}
in_fatal = MY_TRUE;
ts = time_stamp();
va_start(va, fmt);
#ifdef VERBOSE
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
? get_txt(current_object->name) : "<null>");
#endif
debug_message("%s ", ts);
vdebug_message(fmt, va);
if (current_object)
debug_message("%s Current object was %s\n"
, ts, current_object->name
? get_txt(current_object->name) : "<null>");
debug_message("%s Dump of the call chain:\n", ts);
(void)dump_trace(MY_TRUE, NULL);
#ifdef VERBOSE
printf("%s " PROGNAME " aborting on fatal error.\n", time_stamp());
fflush(stdout);
#else
debug_message("%s " PROGNAME " aborting on fatal error.\n", time_stamp());
#endif
sleep(1); /* let stdout settle down... abort can ignore the buffer... */
va_end(va);
/* Before shutting down, try to inform the game about it */
push_ref_string(inter_sp, STR_FATAL_ERROR);
callback_master(STR_SHUTDOWN, 1);
/* Mandatory cleanups */
#ifdef USE_TLS
tls_global_deinit();
#endif
/* Dump core and exit */
dump_core();
} /* fatal() */
/*-------------------------------------------------------------------------*/
char *
limit_error_format (char *fixed_fmt, size_t fixed_fmt_len, const 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
errorf (const 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 errorf()) 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 errorf(<prefmt><errmsg><postfmt>, ...).
*/
{
rt_context_t *rt;
string_t *object_name = NULL;
char *ts;
svalue_t *svp;
Bool error_caught;
/* TRUE: User catches this error.
*/
Bool published_catch;
/* TRUE: this is a catch which wants runtime_error to be called
*/
Bool do_save_error;
string_t *file; /* program name */
string_t *malloced_error; /* copy of emsg_buf+1 */
string_t *malloced_file = NULL; /* copy of program name */
string_t *malloced_name = NULL; /* copy of the object name */
object_t *curobj = NULL; /* Verified current object */
char fixed_fmt[ERROR_FMT_LEN];
/* Note: When changing this buffer, also change the HEAP_STACK_GAP
* limit in xalloc.c!
*/
mp_int line_number = 0;
va_list va;
/* Errors during the fatal() processing will abort the process
* immediately.
*/
if (in_fatal)
fatal("Error during fatal().");
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 */
error_caught = MY_FALSE;
published_catch = MY_FALSE;
if (rt->type >= ERROR_RECOVERY_CATCH)
{
/* User catches this error */
error_caught = MY_TRUE;
/* Try to copy the error message into the catch value.
* If we run out of memory here, we won't execute the catch.
*/
{
string_t * str = new_mstring(emsg_buf);
if (NULL != str)
2009-05-21 22:41:07 +00:00
{
svalue_t stmp;
put_string(&stmp, str);
transfer_error_message(&stmp, rt);
}
2009-03-03 03:27:01 +00:00
else
{
error_caught = MY_FALSE;
/* Unroll the context stack even further until the
* previous non-catch error recovery frame.
*/
for (
; !ERROR_RECOVERY_CONTEXT(rt->type)
&& rt->type >= ERROR_RECOVERY_CATCH
; rt = rt->last) NOOP;
}
}
}
if (error_caught)
{
struct error_recovery_info * eri = (struct error_recovery_info *)rt;
published_catch = (eri->flags & CATCH_FLAG_PUBLISH);
if (!out_of_memory)
{
if (!(eri->flags & CATCH_FLAG_NOLOG))
{
/* Even though caught, dump the backtrace - it makes mudlib
* debugging much easier. lynX 2007 would like to add:
*
* There are two different styles of catch backtrace:
* When the error happens at compilation time, then the
* details are printed first, then we get here and print
* out the backtrace. Further information is redundant
* but doesn't hurt.
*
* But there is also the kind of catch that happens at
* runtime. In that case there is no error ahead, or not
* a precise one. Then comes the backtrace, and to avoid
* confusion in that case it is important to output the
* emsg at the *end* of the backtrace so it is close to
* the file actually producing the error, not the one
* where the event loop started out from.
*
* In the past this was confusionary - Beginners would
* look for the bug at the wrong end of the backtrace
* where they only find an input_to callback or similar.
*/
#ifdef USE_LDMUD_COMPATIBILITY
debug_message("%s Caught error: %s", ts, emsg_buf + 1);
# ifdef VERBOSE
printf("%s Caught error: %s", ts, emsg_buf + 1);
# endif
#else
debug_message("%s ERROR caught. Backtrace:\n", ts);
#endif
if (current_error_trace)
{
free_array(current_error_trace);
current_error_trace = NULL;
}
object_name = dump_trace(MY_FALSE, &current_error_trace);
#ifdef USE_LDMUD_COMPATIBILITY
debug_message("%s ... execution continues.\n", ts);
# ifdef VERBOSE
printf("%s ... execution continues.\n", ts);
# endif
#else
debug_message("Recovering from: %s\n", emsg_buf + 1);
/* Intentional double newline */
#endif
}
else
{
/* No dump of the backtrace into the log, but we want it
* available for debug_info().
*/
if (current_error_trace)
{
free_array(current_error_trace);
current_error_trace = NULL;
}
object_name = collect_trace(NULL, &current_error_trace);
}
}
else /* We're running low on memory. */
{
if (current_error_trace)
{
free_array(current_error_trace);
current_error_trace = NULL;
}
object_name = STR_UNKNOWN_OBJECT;
}
if (!published_catch)
{
unroll_context_stack();
longjmp(((struct error_recovery_info *)rt_context)->con.text, 1);
fatal("Catch() longjump failed");
}
}
/* Error not caught by the program, or catch() requests the
* runtime_error() is to be called.
*/
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 */
malloced_error = new_mstring(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);
2009-05-21 22:41:07 +00:00
debug_message("%s program: %s, object: %s line %"PRIdMPINT"\n"
2009-03-03 03:27:01 +00:00
, ts, get_txt(file), get_txt(curobj->name)
, line_number);
if (current_prog && num_error < 3)
{
do_save_error = MY_TRUE;
}
malloced_file = file; /* Adopt reference */
malloced_name = ref_mstring(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)
{
2009-05-21 22:41:07 +00:00
printf("%s program: %s, object: %s line %"PRIdMPINT"\n"
2009-03-03 03:27:01 +00:00
, ts, get_txt(file), get_txt(curobj->name)
, line_number
);
}
}
/* Dump the backtrace (unless already done) */
if (!published_catch)
{
if (uncaught_error_trace)
{
free_array(uncaught_error_trace);
uncaught_error_trace = NULL;
}
if (current_error_trace)
{
free_array(current_error_trace);
current_error_trace = NULL;
}
object_name = dump_trace(num_error == 3, &current_error_trace);
if (!published_catch)
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)
{
free_mstring(malloced_error);
malloced_error = NULL;
}
if (malloced_file)
{
free_mstring(malloced_file);
malloced_file = NULL;
}
if (malloced_name)
{
free_mstring(malloced_name);
malloced_name = NULL;
}
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);
}
/* If the error is not caught at all, the stack must be brought in a
* usable state. After the call to reset_machine(), all arguments to
* errorf() 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.
*/
if (!published_catch)
reset_machine(MY_FALSE);
if (do_save_error)
{
save_error(emsg_buf, get_txt(file), line_number);
}
if (object_name)
{
/* Error occured in a heart_beat() function */
object_t *ob;
ob = find_object(object_name);
if (!ob)
{
#ifdef USE_LDMUD_COMPATIBILITY
if (command_giver && num_error < 2)
add_message("error when executing program in destroyed object %s\n",
get_txt(object_name));
#endif
debug_message("%s error when executing program in destroyed object %s\n"
, ts, get_txt(object_name));
}
}
if (num_error == 3)
{
debug_message("%s Master failure: %s", ts, emsg_buf+1);
#ifdef VERBOSE
printf("%s Master failure: %s", ts, emsg_buf+1);
#endif
}
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;
if (!published_catch)
{
CLEAR_EVAL_COST;
RESET_LIMITS;
}
push_ref_string(inter_sp, malloced_error);
a = 1;
if (curobj)
{
push_ref_string(inter_sp, malloced_file);
push_ref_string(inter_sp, malloced_name);
push_number(inter_sp, 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, MY_FALSE);
debug_message("%s Heart beat in %s turned off.\n"
, time_stamp(), get_txt(culprit->name));
push_ref_valid_object(inter_sp, culprit, "heartbeat error");
a++;
}
else
{
if (!curobj)
{
/* Push dummy values to keep the argument order correct */
push_number(inter_sp, 0);
push_number(inter_sp, 0);
push_number(inter_sp, 0);
a += 3;
}
/* Normal error: push -1 instead of a culprit. */
push_number(inter_sp, -1);
a++;
}
push_number(inter_sp, error_caught ? 1 : 0);
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_ref_valid_object(inter_sp, culprit, "runtime_error");
push_ref_string(inter_sp, malloced_error);
a = 2;
if (curobj)
{
push_ref_string(inter_sp, malloced_file);
push_ref_string(inter_sp, malloced_name);
push_number(inter_sp, line_number);
a += 3;
}
push_number(inter_sp, error_caught ? 1 : 0);
a++;
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(), get_txt(culprit->name));
set_heart_beat(culprit, MY_TRUE);
}
}
/* Handling errors is expensive! */
if (!published_catch)
assigned_eval_cost = eval_cost += MASTER_RESERVED_COST;
}
/* Clean up */
if (malloced_error)
{
free_mstring(malloced_error);
malloced_error = NULL;
}
if (malloced_file)
{
free_mstring(malloced_file);
malloced_file = NULL;
}
if (malloced_name)
{
free_mstring(malloced_name);
malloced_name = NULL;
}
num_error--;
if (current_interactive)
{
interactive_t *i;
if (O_SET_INTERACTIVE(i, current_interactive)
&& i->noecho & NOECHO_STALE)
{
2009-05-21 22:41:07 +00:00
set_noecho(i, 0, MY_FALSE, MY_FALSE);
2009-03-03 03:27:01 +00:00
}
}
/* Unroll the context stack and find the recovery context to jump to. */
if (published_catch)
{
unroll_context_stack();
longjmp(((struct error_recovery_info *)rt_context)->con.text, 1);
fatal("Catch() longjump failed");
}
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");
} /* errorf() */
/*-------------------------------------------------------------------------*/
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(). The message
* is also passed to master::runtime_warning().
*
* Note: Both 'warn' and 'warning' are already taken on some systems.
* TODO: Add a pwarnf(<prefmt>, <postfmt>,...) function which translates the
* TODO:: errno into a string and calls errorf(<prefmt><errmsg><postfmt>, ...).
*/
{
char *ts;
string_t *file = NULL; /* program name */
object_t *curobj = NULL; /* Verified current object */
char msg_buf[10000];
/* The buffer for the error message to be created.
*/
char fixed_fmt[2000];
/* Note: When changing this buffer, also change the HEAP_STACK_GAP
* limit in xalloc.c!
*/
mp_int line_number = 0;
Bool inside_catch;
/* TRUE: Code is executed inside a catch.
*/
va_list va;
num_warning++;
ts = time_stamp();
/* Check if this warning occurs inside a catch. */
inside_catch = MY_FALSE;
{
rt_context_t *rt;
for ( rt = rt_context
; !ERROR_RECOVERY_CONTEXT(rt->type)
; rt = rt->last) NOOP;
inside_catch = (rt->type >= ERROR_RECOVERY_CATCH);
}
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 error 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);
2009-05-21 22:41:07 +00:00
debug_message("%s program: %s, object: %s line %"PRIdMPINT"\n"
2009-03-03 03:27:01 +00:00
, ts, get_txt(file), get_txt(curobj->name)
, line_number);
}
fflush(stdout);
if (num_warning < 3)
{
/* Call master::runtime_warning().
*/
object_t * save_cmd = command_giver;
put_c_string(++inter_sp, msg_buf);
if (curobj)
{
if (compat_mode)
push_ref_string(inter_sp, curobj->name);
else
push_string(inter_sp, add_slash(curobj->name));
}
else
push_number(inter_sp, 0);
if (file)
push_ref_string(inter_sp, file);
else
push_number(inter_sp, 0);
push_number(inter_sp, line_number);
push_number(inter_sp, inside_catch ? 1 : 0);
apply_master(STR_WARNING, 5);
command_giver = save_cmd;
}
else
{
if (file)
free_mstring(file);
errorf("Too many nested warnings.\n");
}
if (file)
free_mstring(file);
num_warning--;
} /* warnf() */
/*-------------------------------------------------------------------------*/
void
parse_error (Bool warning, const char *error_file, int line, const char *what
, const 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_c_string(inter_sp, error_file);
push_c_string(inter_sp, buff);
push_number(inter_sp, warning ? 1 : 0);
apply_master(STR_LOG_ERROR, 3);
}
} /* parse_error() */
/*-------------------------------------------------------------------------*/
void
2009-05-21 22:41:07 +00:00
throw_error (svalue_t *v)
2009-03-03 03:27:01 +00:00
2009-05-21 22:41:07 +00:00
/* The efun throw(). We have to save the message <v> in the
* error context and then do the proper longjmp. <v> is freed.
2009-03-03 03:27:01 +00:00
*/
{
unroll_context_stack();
if (rt_context->type >= ERROR_RECOVERY_CATCH)
{
2009-05-21 22:41:07 +00:00
transfer_error_message(v, rt_context);
2009-03-03 03:27:01 +00:00
longjmp(((struct error_recovery_info *)rt_context)->con.text, 1);
fatal("Throw_error failed!");
}
2009-05-21 22:41:07 +00:00
free_svalue(v);
2009-03-03 03:27:01 +00:00
errorf("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);
2009-05-21 22:41:07 +00:00
errorf("Out of memory (%zu bytes) for new object '%s' uids\n"
, sizeof(*ecp), get_txt(ob->name));
2009-03-03 03:27:01 +00:00
}
ecp->new_object = ob;
push_error_handler(give_uid_error_handler, &(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 errorf() does return.
*/
{
lambda_t *l;
char *err, errtxt[1024];
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)
{
free_object(l->ob, "give_uid_to_object");
l->ob = ref_object(ob, "give_uid_to_object");
}
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.str);
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.str);
ob->eff_user = ret[1].type != T_STRING
? 0
: add_name(ret[1].u.str);
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"
, get_txt(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(STR_NONAME);
ob->eff_user = NULL;
return MY_TRUE;
}
ob->user = add_name(STR_NONAME);
ob->eff_user = ob->user;
put_object(&arg, ob);
destruct_object(&arg);
errorf(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 && (size_t)(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 (const 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 (const 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
|| (!allow_filename_spaces && strchr(path, ' '))
|| path[0] == '/')
return MY_FALSE;
#ifdef MSDOS_FS
{
const char *name;
if (strchr(path,'\\'))
return MY_FALSE; /* better safe 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 errorf() 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(strbuf_length(&sbuf)+1);
if (!buf)
2009-05-21 22:41:07 +00:00
errorf("Out of stack memory (%zu bytes)\n"
, strlen(sbuf.buf)+1);
2009-03-03 03:27:01 +00:00
strbuf_copy(&sbuf, buf);
strbuf_free(&sbuf);
fprintf(stderr, "%s %s", time_stamp(), buf);
errorf("%.*s", MIN(ERROR_BUF_LEN - 200, (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
, Bool isMasterObj, 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.
* <isMasterObj> is TRUE if the top-level object to be compiled is the master
* object.
* <chain> is the pointer to the calling frame's namechain structure.
*
* 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.
*
* Result is the pointer to the loaded object, or NULL on failure.
*/
{
int fd;
object_t *ob;
object_t *save_command_giver = command_giver;
2009-05-21 22:41:07 +00:00
long i;
2009-03-03 03:27:01 +00:00
struct stat c_st;
2009-05-21 22:41:07 +00:00
size_t name_length;
2009-03-03 03:27:01 +00:00
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
2009-05-21 22:41:07 +00:00
/* Empty lnames may lead to a driver crash in enter_object_hash() if there
* exists a file '.c' in the root of the mudlib.
*/
name_length = strlen(lname);
if (name_length < 1) {
load_object_error("Illegal file to load (empty filename)", lname, chain);
/* NOTREACHED */
}
2009-03-03 03:27:01 +00:00
/* 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_str((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.
2009-05-21 22:41:07 +00:00
* The memory is allocated in one chunk for both strings and an error
* handler is pushed on the stack (additionally is needed: memory for '/'
* and '\0 (sizeof("/")) and '/', '\0', '.' and 'c' (sizeof("/.c"))).
2009-03-03 03:27:01 +00:00
*/
2009-05-21 22:41:07 +00:00
name = xalloc_with_error_handler(2 * name_length + sizeof("/") +
sizeof("/.c"));
fname = name + name_length + sizeof("/") + 1;
if (!name)
errorf("Out of memory (%zu bytes) in load_object() for temporary name "
"buffers.\n", 2*name_length + sizeof("/") + sizeof("/.c"));
2009-03-03 03:27:01 +00:00
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)
errorf("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_str(name)) )
{
#ifdef USE_SWAP
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
*/
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
#endif
2009-05-21 22:41:07 +00:00
pop_stack(); /* free error handler */
2009-03-03 03:27:01 +00:00
return ob;
}
}
/* 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);
/* NOTREACHED */
}
break;
}
}
}
/* 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 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_c_string(inter_sp, fname);
svp = apply_master(STR_COMPILE_OBJECT, 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_str(name)) )
{
/* An object for <name> magically appeared - is it
* the one we received?
*/
if (ob == svp->u.ob)
{
/* If this object is a clone, clear the clone flag
* but mark it as replaced.
*/
if (ob->flags & O_CLONE)
{
ob->flags &= ~O_CLONE;
ob->flags |= O_REPLACED;
}
2009-05-21 22:41:07 +00:00
pop_stack(); /* free error handler */
2009-03-03 03:27:01 +00:00
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);
free_mstring(ob->name);
ob->name = new_mstring(name);
enter_object_hash(ob);
/* If this object is a clone, clear the clone flag
* but mark it as replaced.
*/
if (ob->flags & O_CLONE)
{
ob->flags &= ~O_CLONE;
ob->flags |= O_REPLACED;
}
2009-05-21 22:41:07 +00:00
pop_stack(); /* free error handler */
2009-03-03 03:27:01 +00:00
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_str((char *)name);
if (ob)
{
2009-05-21 22:41:07 +00:00
pop_stack(); /* free error handler */
2009-03-03 03:27:01 +00:00
return ob;
}
if (comp_flag)
fprintf(stderr, "%s compiling %s ...", time_stamp(), fname);
if (current_loc.file)
{
errorf("Can't load '%s': compiler is busy with '%s'.\n"
, name, current_loc.file->name);
}
fd = ixopen(fname, O_RDONLY | O_BINARY);
if (fd <= 0)
{
perror(fname);
errorf("Could not read the file.\n");
}
FCOUNT_COMP(fname);
/* The file name is needed before compile_file(), in case there is
* an initial 'line too long' error.
*/
compile_file(fd, fname, isMasterObj);
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);
/* 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(get_txt(inherit_file), MY_FALSE);
if (!tmp)
{
pInherited = get_txt(inherit_file);
}
else
{
pInherited = alloca(strlen(tmp)+1);
strcpy(pInherited, tmp);
}
push_string(inter_sp, 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)
{
errorf("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, isMasterObj, &nlink);
free_mstring(inter_sp->u.str);
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_str(name)))
{
/* The object magically appeared!
* This can happen if rename_object() is used carelessly
* in the mudlib handler for compiler warnings.
*/
free_prog(compiled_prog, MY_TRUE);
load_object_error("Object appeared while it was compiled"
, name, chain);
/* NOTREACHED */
return NULL;
}
prog = compiled_prog;
ob = get_empty_object(prog->num_variables);
if (!ob)
errorf("Out of memory for new object '%s'\n", name);
ob->name = new_mstring(name);
#ifdef CHECK_OBJECT_STAT
if (check_object_stat)
{
2009-05-21 22:41:07 +00:00
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) load( %p '%s') name: %zu -> (%ld:%ld)\n"
2009-03-03 03:27:01 +00:00
, tot_alloc_object, tot_alloc_object_size, ob, ob->name ? get_txt(ob->name) : "<null>"
, mstrsize(ob->name)
, tot_alloc_object
, tot_alloc_object_size + (mstrsize(ob->name))
);
}
#endif
tot_alloc_object_size += mstrsize(ob->name);
/* 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 = new_tabled(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_ref_string(inter_sp, 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; /* just in case */
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;
2009-05-21 22:41:07 +00:00
init_object_variables(ob, NULL);
2009-03-03 03:27:01 +00:00
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;
2009-05-21 22:41:07 +00:00
init_object_variables(ob, NULL);
2009-03-03 03:27:01 +00:00
reset_object(ob, create_super ? H_CREATE_SUPER : H_CREATE_OB);
}
}
if ( !(ob->flags & O_DESTRUCTED))
ob->flags |= O_WILL_CLEAN_UP;
2009-05-21 22:41:07 +00:00
/* free the error handler with the buffer for name and fname. */
pop_stack();
2009-03-03 03:27:01 +00:00
/* Restore the command giver */
command_giver = check_object(save_command_giver);
#ifdef USE_LDMUD_COMPATIBILITY
if (d_flag > 1 && ob)
{
/* Isn't this redundant with the -c flag!? */
debug_message("%s --%s loaded\n", time_stamp(), get_txt(ob->name));
}
#endif
#if 0 && defined(CHECK_OBJECT_REF)
if (strchr(get_txt(ob->name), '#') == NULL)
2009-05-21 22:41:07 +00:00
printf("DEBUG: new_object(%p '%s') ref %"PRIdPINT" flags %x\n"
, ob, get_txt(ob->name), ob->ref, ob->flags);
2009-03-03 03:27:01 +00:00
#endif
return ob;
} /* load_object() */
/*-------------------------------------------------------------------------*/
static string_t *
make_new_name (string_t *str)
/* <str> is a basic object name - generate a clone name "<str>#<num>"
* and return it. The result will be an untabled string with one reference.
*
* 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.
*/
string_t *p;
char buff[40];
str = del_slash(str);
for (;;)
{
/* Generate the clone name */
(void)sprintf(buff, "#%lu", clone_id_number);
p = mstr_add_txt(str, buff, strlen(buff));
clone_id_number++;
if (clone_id_number == 0) /* Wrap around */
test_conflict = MY_TRUE;
if (!test_conflict || !find_object(p))
{
free_mstring(str);
return p;
}
/* The name was already taken */
free_mstring(p);
}
} /* make_new_name() */
/*-------------------------------------------------------------------------*/
static object_t *
clone_object (string_t *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;
string_t *name;
if (strict_euids && current_object && current_object->eff_user == NULL)
errorf("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 first via the object's
* program, then via the load_name.
*/
if (ob->flags & O_CLONE)
{
object_t *bp = NULL;
/* If the object's program hasn't been replaced, it most likely
* contains a pointer to the blueprint we're looking for.
*/
if (!(ob->flags & O_REPLACED))
{
bp = ob->prog->blueprint;
if (bp && (bp->flags & O_DESTRUCTED))
{
free_object(bp, "clone_object");
bp = ob->prog->blueprint = NULL;
}
}
/* Fallback: find/load the blueprint by the load_name */
if (!bp)
bp = get_object(ob->load_name);
if (bp)
ob = bp;
}
#ifdef USE_INVENTORIES
if (ob->super)
errorf("Cloning a bad object: '%s' is contained in '%s'.\n"
, get_txt(ob->name), get_txt(ob->super->name));
#endif
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 = mstrsize(name);
i = name_length;
p = get_txt(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;
}
}
}
#ifdef USE_SWAP
if ((ob->flags & O_SWAPPED) && load_ob_from_swap(ob) < 0)
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
#endif
if (ob->prog->flags & P_NO_CLONE)
errorf("Cloning a bad object: '%s' sets '#pragma no_clone'.\n"
, get_txt(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, MY_FALSE);
/* Got the blueprint - now get a new object */
new_ob = get_empty_object(ob->prog->num_variables);
if (!new_ob)
errorf("Out of memory for new clone '%s'\n", get_txt(name));
new_ob->name = make_new_name(name);
#ifdef CHECK_OBJECT_STAT
if (check_object_stat)
{
2009-05-21 22:41:07 +00:00
fprintf(stderr, "DEBUG: OSTAT: (%ld:%ld) clone( %p '%s') name: %zu -> (%ld:%ld)\n"
2009-03-03 03:27:01 +00:00
, tot_alloc_object, tot_alloc_object_size, new_ob, new_ob->name ? get_txt(new_ob->name) : "<null>"
, mstrsize(new_ob->name)
, tot_alloc_object
, tot_alloc_object_size + (mstrsize(new_ob->name))
);
}
#endif
tot_alloc_object_size += mstrsize(new_ob->name);
new_ob->load_name = ref_mstring(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_ref_object(inter_sp, ob, "clone_object");
push_ref_string(inter_sp, new_ob->name);
give_uid_to_object(new_ob, H_CLONE_UIDS, 2);
2009-05-21 22:41:07 +00:00
init_object_variables(new_ob, ob);
2009-03-03 03:27:01 +00:00
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 (string_t * 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;
Bool isMasterObj = MY_FALSE;
if (mstreq(str, master_name_str))
isMasterObj = MY_TRUE;
/* 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(get_txt(str), MY_FALSE);
if (!pName)
pName = get_txt(str);
if (!isMasterObj && !strcmp(pName, get_txt(master_name_str)))
isMasterObj = MY_TRUE;
ob = lookup_object_hash_str(pName);
if (!bLoad)
return ob;
if (!ob)
{
ob = load_object(pName, 0, 0, isMasterObj, NULL);
}
if (!ob || ob->flags & O_DESTRUCTED)
return NULL;
return ob;
} /* lookfor_object() */
/*-------------------------------------------------------------------------*/
object_t *
find_object_str (const char * str)
/* Look for a named object <str>.
* Return a pointer to the object structure, or NULL.
*
* The object is not swapped in.
*/
{
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;
return lookup_object_hash_str(pName);
} /* find_object_str() */
/*-------------------------------------------------------------------------*/
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.str);
if (ob == 0)
errorf("destruct_object: Could not find %s\n", get_txt(v->u.str));
}
if (ob->flags & O_DESTRUCTED)
return;
#ifdef USE_SWAP
if (ob->flags & O_SWAPPED)
if (load_ob_from_swap(ob) < 0)
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
#endif
if (d_flag)
{
2009-05-21 22:41:07 +00:00
debug_message("%s destruct_object: %s (ref %"PRIdPINT")\n"
2009-03-03 03:27:01 +00:00
, time_stamp(), get_txt(ob->name), ob->ref);
}
push_ref_object(inter_sp, ob, "destruct");
result = apply_master(STR_PREP_DEST, 1);
if (!result)
errorf("No prepare_destruct\n");
if (result->type == T_STRING)
errorf(get_txt(result->u.str));
if (result->type != T_NUMBER || result->u.number != 0)
return;
#ifdef USE_INVENTORIES
if (ob->contains)
{
errorf("Master failed to clean inventory in prepare_destruct\n");
}
#endif
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;
#ifdef USE_BUILTIN_EDITOR
if (sh->ed_buffer)
save_ed_buffer();
#endif
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)
/* Really 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().
*/
{
#ifdef USE_INVENTORIES
# ifdef USE_SHADOWING
object_t **pp;
# endif
object_t *item, *next;
#endif
#ifdef CHECK_OBJECT_REF
object_shadow_t *shadow;
#endif /* CHECK_OBJECT_REF */
if (ob->flags & O_DESTRUCTED)
return;
#ifdef CHECK_OBJECT_REF
xallocate(shadow, sizeof(*shadow), "destructed object shadow");
#endif /* CHECK_OBJECT_REF */
#ifdef USE_SQLITE
if (ob->open_sqlite_db)
sl_close(ob);
#endif
ob->time_reset = 0;
#ifdef USE_SWAP
/* 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;
}
#endif
/* If there are shadows, remove them */
if (ob->flags & O_SHADOW)
{
shadow_t *shadow_sent;
#ifdef USE_SHADOWING
object_t *shadowing, *shadowed_by;
#endif
shadow_sent = O_GET_SHADOW(ob);
#ifdef USE_BUILTIN_EDITOR
if (shadow_sent->ed_buffer)
{
object_t *save = command_giver;
command_giver = ob;
free_ed_buffer();
command_giver = save;
}
#endif
#ifdef USE_SHADOWING
/* 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);
#ifdef USE_ACTIONS
/* 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);
#endif
}
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 {
#ifdef USE_ACTIONS
remove_shadow_actions(shadowed_by, ob);
#endif
if (O_GET_SHADOW(shadowed_by) != NULL)
shadowed_by = O_GET_SHADOW(shadowed_by)->shadowed_by;
else
shadowed_by = NULL;
} while (shadowed_by != NULL);
}
#endif
check_shadow_sent(ob);
}
#ifdef USE_INVENTORIES
/* Move all objects in the inventory into the "void" */
for (item = ob->contains; item; item = next)
{
#ifdef USE_SHADOWING
remove_action_sent(ob, item);
#endif
item->super = NULL;
next = item->next_inv;
item->next_inv = NULL;
}
#endif
remove_object_from_stack(ob);
if (ob == simul_efun_object)
{
simul_efun_object = NULL;
invalidate_simul_efuns();
}
set_heart_beat(ob, MY_FALSE);
#ifdef USE_INVENTORIES
/* Remove us out of this current room (if any).
* Remove all sentences defined by this object from all objects here.
*/
if (ob->super)
{
#ifdef USE_SHADOWING
if (ob->super->sent)
remove_action_sent(ob, ob->super);
#endif
# ifdef USE_SET_LIGHT
add_light(ob->super, - ob->total_light);
# endif
#ifdef USE_SHADOWING
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;
}
#endif
}
#endif
/* 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--;
#ifdef USE_INVENTORIES
ob->super = NULL;
ob->next_inv = NULL;
ob->contains = NULL;
#endif
ob->flags &= ~O_ENABLE_COMMANDS;
ob->flags |= O_DESTRUCTED; /* must come last! */
if (command_giver == ob)
command_giver = NULL;
#ifdef USE_EXPAT
if (ob->xml_parser != NULL) { /* free parser */
XML_ParserFree(ob->xml_parser);
ob->xml_parser = NULL;
}
#endif
/* 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++;
#ifdef CHECK_OBJECT_REF
shadow->obj = ob;
shadow->ref = ob->ref;
shadow->flags = ob->flags;
shadow->sent = ob->sent;
shadow->next = newly_destructed_obj_shadows;
newly_destructed_obj_shadows = shadow;
#endif /* CHECK_OBJECT_REF */
} /* destruct() */
#ifdef CHECK_OBJECT_REF
/*-------------------------------------------------------------------------*/
void
check_object_shadow (object_t *ob, object_shadow_t *sh)
{
if (sh->obj != ob)
fatal("DEBUG: Obj %p '%s', shadow %p -> obj %p '%s'\n"
, ob, get_txt(ob->name), sh, sh->obj, get_txt(sh->obj->name));
if ((sh->flags & O_DESTRUCTED) != (ob->flags & O_DESTRUCTED)
|| sh->sent != ob->sent
)
2009-05-21 22:41:07 +00:00
fatal("DEBUG: Obj %p '%s': ref %"PRIdPINT", flags %x, sent %p;"
"shadow ref %"PRIdPINT", flags %x, sent %p\n"
2009-03-03 03:27:01 +00:00
, ob, get_txt(ob->name), ob->ref, ob->flags, ob->sent
, sh->ref, sh->flags, sh->sent
);
} /* check_object_shadow() */
void
check_all_object_shadows (void)
{
object_shadow_t *sh;
object_t * ob;
for (ob = newly_destructed_objs, sh = newly_destructed_obj_shadows
; ob != NULL
; ob = ob->next_all, sh = sh->next
)
check_object_shadow(ob, sh);
for (ob = destructed_objs, sh = destructed_obj_shadows
; ob != NULL
; ob = ob->next_all, sh = sh->next
)
check_object_shadow(ob, sh);
} /* check_object_shadows() */
void
update_object_sent(object_t *obj, sentence_t *new_sent)
{
object_shadow_t *sh;
if (!(obj->flags & O_DESTRUCTED))
{
obj->sent = new_sent;
return;
}
for (sh = newly_destructed_obj_shadows; sh != NULL; sh = sh->next)
if (sh->obj == obj)
break;
if (sh == NULL)
for (sh = newly_destructed_obj_shadows; sh != NULL; sh = sh->next)
if (sh->obj == obj)
break;
if (sh == NULL)
{
2009-05-21 22:41:07 +00:00
fatal("DEBUG: Obj %p '%s': ref %"PRIdPINT", flags %x, sent %p; no shadow found\n"
2009-03-03 03:27:01 +00:00
, obj, get_txt(obj->name), obj->ref, obj->flags, obj->sent
);
}
check_object_shadow(obj, sh);
obj->sent = new_sent;
sh->sent = new_sent;
}
#endif /* CHECK_OBJECT_REF */
/*-------------------------------------------------------------------------*/
static void
remove_object (object_t *ob
#ifdef CHECK_OBJECT_REF
, object_shadow_t *sh
#endif /* CHECK_OBJECT_REF */
)
/* 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;
#ifdef CHECK_OBJECT_REF
check_object_shadow(ob, sh);
#endif
if (d_flag > 1)
{
2009-05-21 22:41:07 +00:00
debug_message("%s remove_object: object %s (ref %"PRIdPINT")\n"
2009-03-03 03:27:01 +00:00
, time_stamp(), get_txt(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;
#ifdef USE_SWAP
remove_prog_swap(ob->prog, MY_TRUE);
#endif
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);
#ifdef USE_ACTIONS
else
free_action_sent((action_t *)sent);
#endif
} while ( NULL != (sent = next) );
ob->sent = NULL;
#ifdef CHECK_OBJECT_REF
sh->sent = NULL;
#endif /* CHECK_OBJECT_REF */
}
/* Either free the object, or link it up for future freeing. */
if (ob->ref <= 1)
{
free_object(ob, "destruct_object");
#ifdef CHECK_OBJECT_REF
xfree(sh);
#endif /* CHECK_OBJECT_REF */
}
else
{
if (destructed_objs != NULL)
destructed_objs->prev_all = ob;
ob->next_all = destructed_objs;
destructed_objs = ob;
ob->prev_all = NULL;
num_destructed++;
#ifdef CHECK_OBJECT_REF
sh->next = destructed_obj_shadows;
destructed_obj_shadows = sh;
#endif /* CHECK_OBJECT_REF */
}
} /* 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;
#ifdef CHECK_OBJECT_REF
object_t *next_ob = ob->next_all;
object_shadow_t *sh = newly_destructed_obj_shadows;
object_shadow_t *next_sh = sh->next;
#else
newly_destructed_objs = ob->next_all;
#endif /* CHECK_OBJECT_REF */
#ifdef DEBUG
if (!(ob->flags & O_DESTRUCTED))
fatal("Non-destructed object %p '%s' in list of destructed objects.\n"
, ob, ob->name ? get_txt(ob->name) : "<null>"
);
#endif
#ifdef CHECK_OBJECT_REF
remove_object(ob, sh);
newly_destructed_objs = next_ob;
newly_destructed_obj_shadows = next_sh;
#else
remove_object(ob);
#endif /* CHECK_OBJECT_REF */
num_newly_destructed--;
}
} /* handle_newly_destructed_objects() */
/*-------------------------------------------------------------------------*/
void
remove_destructed_objects (Bool force)
/* Scan the list of destructed objects and free those with no references
* remaining.
* If <force> is FALSE, the call immediately returns if the flag
* <dest_last_ref_gone> (in object.c) is FALSE - this flag is set by
* free_object() if all but one reference to a destructed object is gone.
* If <force> is TRUE, the scan takes place unconditionally (this is used by
* the GC).
*/
{
object_t *ob;
#ifdef CHECK_OBJECT_REF
object_shadow_t *sh = destructed_obj_shadows;
object_shadow_t *prev = NULL;
#endif /* CHECK_OBJECT_REF */
if (!force && !dest_last_ref_gone)
return;
dest_last_ref_gone = MY_FALSE;
for (ob = destructed_objs; ob != NULL; )
{
object_t *victim;
#ifdef CHECK_OBJECT_REF
check_object_shadow(ob, sh);
#endif /* CHECK_OBJECT_REF */
/* Check if only the list reference remains.
* If not, go to the next object.
*/
if (ob->ref > 1)
{
ob = ob->next_all;
#ifdef CHECK_OBJECT_REF
prev = sh;
sh = sh->next;
#endif /* CHECK_OBJECT_REF */
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--;
#ifdef CHECK_OBJECT_REF
{
object_shadow_t * next = sh->next;
if (prev == NULL)
{
destructed_obj_shadows = next;
}
else
{
prev->next = next;
}
xfree(sh);
sh = next;
}
#endif /* CHECK_OBJECT_REF */
}
} /* remove_destructed_objects() */
/*-------------------------------------------------------------------------*/
static INLINE shadow_t *
new_shadow_sent(void)
/* Allocate a new empty shadow sentence and return it.
*/
{
shadow_t *p;
xallocate(p, sizeof *p, "new shadow sentence");
alloc_shadow_sent++;
p->sent.type = SENT_SHADOW;
#ifdef USE_SHADOWING
p->shadowing = NULL;
p->shadowed_by = NULL;
#endif
#ifdef USE_BUILTIN_EDITOR
p->ed_buffer = NULL;
#endif
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
xfree(p);
alloc_shadow_sent--;
} /* free_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
#ifdef USE_BUILTIN_EDITOR
&& !sh->ed_buffer
#endif
#ifdef USE_SHADOWING
&& !sh->shadowing
&& !sh->shadowed_by
#endif
)
{
#ifdef CHECK_OBJECT_REF
update_object_sent(ob, sh->sent.next);
#else
ob->sent = sh->sent.next;
#endif /* CHECK_OBJECT_REF */
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;
#ifdef CHECK_OBJECT_REF
update_object_sent(ob, (sentence_t *)sh);
#else
ob->sent = (sentence_t *)sh;
#endif /* CHECK_OBJECT_REF */
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)
{
#ifdef USE_ACTIONS
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Actions:\t\t\t%8"PRIdPINT" %9"PRIdPINT"\n"
2009-03-03 03:27:01 +00:00
, alloc_action_sent
, alloc_action_sent * sizeof (action_t));
#endif
#ifdef USE_SHADOWS
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Shadows:\t\t\t%8"PRIdPINT" %9"PRIdPINT"\n"
2009-03-03 03:27:01 +00:00
, alloc_shadow_sent
, alloc_shadow_sent * sizeof (shadow_t));
#endif
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Objects:\t\t\t%8ld %9ld (%ld destructed;"
" %"PRIdMPINT" swapped: %"PRIdMPINT" Kbytes)\n"
2009-03-03 03:27:01 +00:00
, tot_alloc_object, tot_alloc_object_size
2009-05-21 22:41:07 +00:00
, num_destructed
2009-03-03 03:27:01 +00:00
, num_vb_swapped, total_vb_bytes_swapped / 1024);
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Prog blocks:\t\t\t%8"PRIdMPINT" %9"PRIdMPINT
" (%"PRIdMPINT" swapped: %"PRIdMPINT" Kbytes)\n"
2009-03-03 03:27:01 +00:00
, 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, "Arrays:\t\t\t\t%8ld %9ld\n"
, (long)num_arrays, total_array_size() );
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Mappings:\t\t\t%8"PRIdMPINT" %9"PRIdMPINT
" (%"PRIdMPINT" hybrid, %"PRIdMPINT" hash)\n"
2009-03-03 03:27:01 +00:00
, num_mappings, total_mapping_size()
, num_dirty_mappings, num_hash_mappings
);
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Memory reserved:\t\t\t %9zu\n", res);
2009-03-03 03:27:01 +00:00
}
if (verbose) {
/* TODO: Add these numbers to the debug_info statistics. */
strbuf_add(sbuf, "\nVM Execution:\n");
strbuf_add(sbuf, "-------------\n");
strbuf_addf(sbuf
2009-05-21 22:41:07 +00:00
, "Last: %10lu ticks, %3ld.%06ld s\n"
"Average: %10.0lf ticks, %10.6lf s\n"
2009-03-03 03:27:01 +00:00
, last_total_evalcost
2009-05-21 22:41:07 +00:00
, last_eval_duration.tv_sec, (long)last_eval_duration.tv_usec
2009-03-03 03:27:01 +00:00
, stat_total_evalcost.weighted_avg
, stat_eval_duration.weighted_avg / 1000000.0
);
strbuf_addf(sbuf
, "Load: %.2lf cmds/s, %.2lf comp lines/s\n"
, stat_load.weighted_avg
, stat_compile.weighted_avg
);
#ifdef COMM_STAT
strbuf_add(sbuf, "\nNetwork IO:\n");
strbuf_add(sbuf, "-----------\n");
strbuf_addf(sbuf
2009-05-21 22:41:07 +00:00
, "In: Packets: %10lu - Sum: %10lu - "
2009-03-03 03:27:01 +00:00
"Average packet size: %7.2f\n"
, inet_packets_in
, inet_volume_in
, inet_packets_in ? (float)inet_volume_in/(float)inet_packets_in : 0.0
);
strbuf_addf(sbuf
2009-05-21 22:41:07 +00:00
, "Out: Packets: %10lu - Sum: %10lu - "
2009-03-03 03:27:01 +00:00
"Average packet size: %7.2f\n"
" Calls to add_message: %lu\n"
, inet_packets
, inet_volume
, inet_packets ? (float)inet_volume/(float)inet_packets : 0.0
, add_message_calls
);
#endif
#ifdef APPLY_CACHE_STAT
strbuf_add(sbuf, "\nApply Cache:\n");
strbuf_add(sbuf, "------------\n");
strbuf_addf(sbuf
2009-05-21 22:41:07 +00:00
, "Calls to apply_low: %10"PRIuPINT"\n"
"Cache hits: %10"PRIuPINT" (%.2f%%)\n"
, (apply_cache_hit+apply_cache_miss)
, apply_cache_hit
2009-03-03 03:27:01 +00:00
, 100.*(float)apply_cache_hit/
(float)(apply_cache_hit+apply_cache_miss) );
#endif
}
tot = total_prog_block_size;
tot += total_array_size();
tot += tot_alloc_object_size;
#ifdef USE_ACTIONS
tot += alloc_action_sent * sizeof(action_t);
#endif
#ifdef USE_SHADOWS
tot += alloc_shadow_sent * sizeof(shadow_t);
#endif
if (verbose)
{
#ifdef DEBUG
2009-05-21 22:41:07 +00:00
unsigned long count;
2009-03-03 03:27:01 +00:00
object_t *ob;
#endif
strbuf_add(sbuf, "\nObject status:\n");
strbuf_add(sbuf, "--------------\n");
strbuf_addf(sbuf, "Objects total:\t\t\t %8ld\n"
2009-05-21 22:41:07 +00:00
, tot_alloc_object);
2009-03-03 03:27:01 +00:00
#ifndef DEBUG
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Objects in list:\t\t %8lu\n"
, (unsigned long)num_listed_objs);
2009-03-03 03:27:01 +00:00
strbuf_addf(sbuf, "Objects newly destructed:\t\t %8ld\n"
2009-05-21 22:41:07 +00:00
, num_newly_destructed);
2009-03-03 03:27:01 +00:00
strbuf_addf(sbuf, "Objects destructed:\t\t %8ld\n"
2009-05-21 22:41:07 +00:00
, num_destructed);
2009-03-03 03:27:01 +00:00
#else
for (count = 0, ob = obj_list; ob != NULL; ob = ob->next_all)
count++;
if (count != (long)num_listed_objs)
{
2009-05-21 22:41:07 +00:00
debug_message("DEBUG: num_listed_objs mismatch: listed %lu, counted %lu\n"
, (unsigned long)num_listed_objs, count);
strbuf_addf(sbuf, "Objects in list:\t\t %8lu (counted %lu)\n"
, (unsigned long)num_listed_objs, count);
2009-03-03 03:27:01 +00:00
}
else
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Objects in list:\t\t %8lu\n"
, (unsigned long)num_listed_objs);
2009-03-03 03:27:01 +00:00
for (count = 0, ob = newly_destructed_objs; ob != NULL; ob = ob->next_all)
count++;
if (count != num_newly_destructed)
{
2009-05-21 22:41:07 +00:00
debug_message("DEBUG: num_newly_destructed mismatch: listed %ld, counted %lu\n"
, num_newly_destructed, count);
strbuf_addf(sbuf, "Objects newly destructed:\t\t %8ld (counted %lu)\n"
, num_newly_destructed, count);
2009-03-03 03:27:01 +00:00
}
else
strbuf_addf(sbuf, "Objects newly destructed:\t %8ld\n"
2009-05-21 22:41:07 +00:00
, num_newly_destructed);
2009-03-03 03:27:01 +00:00
for (count = 0, ob = destructed_objs; ob != NULL; ob = ob->next_all)
count++;
if (count != num_destructed)
{
2009-05-21 22:41:07 +00:00
debug_message("DEBUG: num_destructed mismatch: listed %ld, counted %lu\n"
, num_destructed, count);
strbuf_addf(sbuf, "Objects destructed:\t\t %8ld (counted %lu)\n"
, num_destructed, count);
2009-03-03 03:27:01 +00:00
}
else
strbuf_addf(sbuf, "Objects destructed:\t\t %8ld\n"
2009-05-21 22:41:07 +00:00
, num_destructed);
2009-03-03 03:27:01 +00:00
#endif
strbuf_addf(sbuf, "Objects processed in last cycle: "
2009-05-21 22:41:07 +00:00
"%8lu (%5.1lf%% - avg. %5.1lf%%)\n"
, (unsigned long)num_last_processed
2009-03-03 03:27:01 +00:00
, (float)num_last_processed / (float)num_listed_objs * 100.0
, 100.0 * relate_statistics(stat_last_processed, stat_in_list)
);
#ifdef NEW_CLEANUP
strbuf_addf(sbuf, "Objects data-cleaned in last cycle: "
2009-05-21 22:41:07 +00:00
"%5lu (%5.1lf%% - avg. %5.1lf : %5.1lf%%)\n"
, (unsigned long)num_last_data_cleaned
2009-03-03 03:27:01 +00:00
, (double)num_last_data_cleaned / (double)num_listed_objs * 100.0
, stat_last_data_cleaned.weighted_avg
, 100.0 * relate_statistics(stat_last_data_cleaned, stat_in_list)
);
#endif
}
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 USE_STRUCTS
tot += total_struct_size(sbuf, verbose);
#endif
tot += rxcache_status(sbuf, verbose);
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();
#ifdef USE_SWAP
other += swap_overhead();
#endif
other += num_simul_efun * sizeof(function_t);
other += interpreter_overhead();
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "Other structures\t\t\t %9zu\n", other);
2009-03-03 03:27:01 +00:00
tot += other;
}
tot += mb_status(sbuf, verbose);
tot += res;
if (!verbose) {
strbuf_add(sbuf, "\t\t\t\t\t ---------\n");
strbuf_add(sbuf, "Total:\t\t\t\t\t ");
2009-05-21 22:41:07 +00:00
strbuf_addf(sbuf, "%9zu\n", tot);
2009-03-03 03:27:01 +00:00
}
return MY_TRUE;
}
#ifdef USE_SWAP
if (strcmp(buff, "swap") == 0)
{
swap_status(sbuf);
return MY_TRUE;
}
#endif
if (strcmp(buff, "malloc") == 0) {
mem_dump_data(sbuf);
return MY_TRUE;
}
if (strcmp(buff, "malloc extstats") == 0) {
mem_dump_extdata(sbuf);
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); \
}
#ifdef USE_ACTIONS
ST_NUMBER(DID_ST_ACTIONS, alloc_action_sent);
ST_NUMBER(DID_ST_ACTIONS_SIZE, alloc_action_sent * sizeof (action_t));
#endif
#ifdef USE_SHADOWS
ST_NUMBER(DID_ST_SHADOWS, alloc_shadow_sent);
ST_NUMBER(DID_ST_SHADOWS_SIZE, alloc_shadow_sent * sizeof (shadow_t));
#endif
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, relate_statistics(stat_last_processed, stat_in_list));
/* TODO: Maybe add number of objects data cleaned here as well. */
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_HYBRID_MAPPINGS, num_dirty_mappings);
ST_NUMBER(DID_ST_HASH_MAPPINGS, num_hash_mappings);
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);
ST_NUMBER(DID_ST_PACKETS_IN, inet_packets_in);
ST_NUMBER(DID_ST_PACKET_SIZE_IN, inet_volume_in);
#else
ST_NUMBER(DID_ST_ADD_MESSAGE, -1);
ST_NUMBER(DID_ST_PACKETS, -1);
ST_NUMBER(DID_ST_PACKET_SIZE, -1);
ST_NUMBER(DID_ST_PACKETS_IN, -1);
ST_NUMBER(DID_ST_PACKET_SIZE_IN, -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() */
/*-------------------------------------------------------------------------*/
string_t *
check_valid_path (string_t *path, object_t *caller, string_t* 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 ".").
*
* The result string has its own reference, but may be <path> again.
*
* If the operation is invalid, NULL is returned.
*/
{
svalue_t *v;
wiz_list_t *eff_user;
if (path)
push_ref_string(inter_sp, path);
else
push_number(inter_sp, 0);
if ( NULL != (eff_user = caller->eff_user) && NULL != eff_user->name)
push_ref_string(inter_sp, eff_user->name);
else
push_number(inter_sp, 0);
push_ref_string(inter_sp, call_fun);
push_ref_valid_object(inter_sp, caller, "check_valid_path");
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;
}
(void)ref_mstring(path);
}
else if (v->u.str == path)
{
(void)ref_mstring(path);
}
else
{
path = ref_mstring(v->u.str);
}
if (get_txt(path)[0] == '/')
{
string_t *npath;
memsafe(npath = del_slash(path), mstrsize(path)-1
, "path for file operation");
free_mstring(path);
path = npath;
}
/* The string "/" will be converted to "." */
if (mstreq(path, STR_EMPTY))
{
free_mstring(path);
path = ref_mstring(STR_PERIOD);
}
if (legal_path(get_txt(path)))
{
return path;
}
/* Push the path onto the VM stack so that errorf() can free it */
push_string(inter_sp, path);
errorf("Illegal path '%s' for %s() by %s\n", get_txt(path), get_txt(call_fun)
, get_txt(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_mstring(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 delayed_callback)
/* Setup the function arguments in the callback <cb> to hold the <nargs>
* arguments starting from <args>. If <delayed_callback> is FALSE,
* the callback will happen within the current LPC cycle: no argument may be
* a protected lvalue, but normal lvalues are ok. If TRUE, the callback
* will happen at a later time: protected lvalues are ok, but not normal ones.
*
* 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)
{
Bool dontHandle = MY_FALSE;
if (args->type == T_LVALUE)
{
/* Check if we are allowed to handle the lvalues. */
Bool isProtected
= ( 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
);
dontHandle = ( delayed_callback && !isProtected)
|| (!delayed_callback && isProtected)
;
}
if (dontHandle)
{
/* We don't handle the lvalue - 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, string_t * fun
, int nargs, svalue_t * args, Bool delayed_callback)
/* Setup the empty/uninitialized callback <cb> to hold a function
* call to <ob>:<fun> with the <nargs> arguments starting from <args>.
* If <delayed_callback> is FALSE, the callback will happen within the current
* LPC cycle: no argument may be a protected lvalue, but normal lvalues are
* ok. If TRUE, the callback will happen at a later time: protected lvalues
* are ok, but not normal ones.
*
* 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_tabled_from(fun); /* for faster apply()s */
cb->function.named.ob = ref_object(ob, "callback");
error_index = setup_callback_args(cb, nargs, args, delayed_callback);
if (error_index >= 0)
{
free_object(cb->function.named.ob, "callback");
free_mstring(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 delayed_callback)
/* Setup the empty/uninitialized callback <cb> to hold a closure
* call to <cl> with the <nargs> arguments starting from <args>.
* If <delayed_callback> is FALSE, the callback will happen within the current
* LPC cycle: no argument may be a protected lvalue, but normal lvalues are
* ok. If TRUE, the callback will happen at a later time: protected lvalues
* are ok, but not normal ones.
*
* 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 = -1;
cb->is_lambda = MY_TRUE;
transfer_svalue_no_free(&(cb->function.lambda), cl);
if (cb->function.lambda.x.closure_type == CLOSURE_UNBOUND_LAMBDA
|| cb->function.lambda.x.closure_type == CLOSURE_PRELIMINARY
)
{
/* Uncalleable closure */
error_index = 0;
free_svalue(&(cb->function.lambda));
cb->function.lambda.type = T_INVALID;
}
else
{
error_index = setup_callback_args(cb, nargs, args, delayed_callback);
if (error_index >= 0)
{
free_svalue(&(cb->function.lambda));
cb->function.lambda.type = T_INVALID;
error_index++;
}
}
return error_index;
} /* setup_closure_callback() */
/*-------------------------------------------------------------------------*/
int
setup_efun_callback_base ( callback_t *cb, svalue_t *args, int nargs
, Bool bNoObj)
/* 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!
* (closure cl, mixed extra, ...)
*
* If bNoObj is FALSE (the usual case), this form is also allowed:
*
* (string fun, string|object obj, 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. Ditto
* if bNoObj is used.
*
* 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).
*
* This function is #defined to two macros:
*
* setup_efun_callback(cb,args,nargs) -> bNoObj == FALSE
* setup_efun_callback_noobj(cb,args,nargs) -> bNoObj == TRUE
*
* The no-object feature is to support old-fashioned efun
* unique_array().
*/
{
int error_index;
if (args[0].type == T_CLOSURE)
{
error_index = setup_closure_callback(cb, args, nargs-1, args+1, MY_FALSE);
}
else if (args[0].type == T_STRING)
{
object_t *ob;
int first_arg;
first_arg = 1;
if (nargs > 1)
{
if (bNoObj)
{
ob = current_object;
first_arg = 1;
}
else
{
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.str);
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.str
, 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
{
/* We couldn't find anything to call, so we have
* to manually prepare the error condition.
*/
int i;
for (i = 0; i < nargs; i++)
free_svalue(args+i);
error_index = 0;
}
return error_index;
} /* setup_efun_callback_base() */
/*-------------------------------------------------------------------------*/
void
callback_change_object (callback_t *cb, object_t *obj)
/* Change the object the callback is bound to, if it is a function callback.
* A new reference is added to <obj>.
*/
{
object_t *old;
if (cb->is_lambda)
{
fatal("callback_change_object(): Must not be called with a closure callback.");
/* NOTREACHED */
return;
}
old = cb->function.named.ob;
cb->function.named.ob = ref_object(obj, "change callback");
if (old)
free_object(old, "change_callback");
} /* callback_change_object() */
/*-------------------------------------------------------------------------*/
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
#ifdef USE_SWAP
|| (O_PROG_SWAPPED(ob) && load_ob_from_swap(ob) < 0)
#endif
)
{
while (nargs-- > 0)
free_svalue(inter_sp--);
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 (toplevel)
tracedepth = 0;
if (!sapply(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 USE_PARANOIA
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 /* USE_PARANOIA */
#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() */
/*-------------------------------------------------------------------------*/
Bool
match_string (const char * match, const 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
* f_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 = xmemmem(str, len + matchlen, match, 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.str);
}
else
{
add_message(FMT_STRING, arg->u.str);
}
}
else if (arg->type == T_OBJECT)
add_message("OBJ(%s)", get_txt(arg->u.ob->name));
else if (arg->type == T_NUMBER)
2009-05-21 22:41:07 +00:00
add_message("%"PRIdPINT, arg->u.number);
2009-03-03 03:27:01 +00:00
else if (arg->type == T_FLOAT)
{
char buff[120];
2009-05-21 22:41:07 +00:00
snprintf(buff, sizeof(buff), "%g", READ_DOUBLE( arg ) );
2009-03-03 03:27:01 +00:00
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
2009-05-21 22:41:07 +00:00
add_message("<OTHER:%"PRIdPHINT">", arg->type);
2009-03-03 03:27:01 +00:00
} /* print_svalue() */
/*=========================================================================*/
/* EFUNS */
/*-------------------------------------------------------------------------*/
svalue_t *
f_clone_object (svalue_t * sp)
/* EFUN clone_object()
*
* object clone_object(string name)
* object clone_object(object template)
*
* Clone a new object from definition <name>, or alternatively from
* the object <template>. In both cases, the new object is given an
* unique name and returned.
*/
{
object_t *ob;
/* Get the argument and clone the object */
if (sp->type == T_STRING)
{
ob = clone_object(sp->u.str);
}
else
{
ob = clone_object(sp->u.ob->load_name);
}
free_svalue(sp);
if (ob)
{
put_ref_object(sp, ob, "F_CLONE_OBJECT");
}
else
put_number(sp, 0);
return sp;
} /* f_clone_object() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_destruct (svalue_t * sp)
/* EFUN destruct()
*
* void destruct(object ob)
*
* Completely destroy and remove object ob (if not already done so).
* After the call to destruct(), no global variables will exist any
* longer, only local ones, and arguments.
*
* If an object self-destructs, it will not immediately terminate
* execution. If the efun this_object() will be called by the
* destructed object, the result will be 0.
*
* The efun accepts destructed objects as argument (which appear
* as the number 0) and the simply acts as a no-op in that case.
*
* Internally, the object is not destructed immediately, but
* instead put into a list and finally destructed after the
* current execution has ended.
*/
{
if (T_NUMBER != sp->type || sp->u.number)
{
if (sp->type != T_OBJECT)
efun_arg_error(1, T_OBJECT, sp->type, sp);
destruct_object(sp);
}
free_svalue(sp);
sp--;
return sp;
} /* f_destruct() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_find_object (svalue_t *sp)
/* EFUN find_object()
*
* object find_object(string str)
*
* Find an object with the file_name str. If the object isn't loaded,
* it will not be found.
*/
{
object_t *ob;
ob = find_object(sp->u.str);
free_svalue(sp);
if (ob)
put_ref_object(sp, ob, "find_object");
else
put_number(sp, 0);
return sp;
} /* f_find_object() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_load_object (svalue_t *sp)
/* EFUN load_object()
*
* object load_object(string name)
*
* Load the object from the file <name> and return it. If the
* object already exists, just return it.
*
* This efun can be used only to load blueprints - for clones, use
* the efun clone_object().
*/
{
object_t *ob;
ob = get_object(sp->u.str);
free_svalue(sp);
if (ob)
put_ref_object(sp, ob, "F_LOAD_OBJECT");
else
put_number(sp, 0);
return sp;
} /* f_load_object() */
/*-------------------------------------------------------------------------*/
#ifdef USE_SHADOWING
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;
#ifdef USE_SWAP
if (O_PROG_SWAPPED(ob))
if (load_ob_from_swap(ob) < 0)
errorf("Out of memory: unswap object '%s'\n", get_txt(ob->name));
#endif
victim = ob->prog;
if (victim->flags & P_NO_SHADOW)
errorf("shadow '%s' on '%s': Can't shadow a 'no_shadow' program.\n"
, get_txt(cob->name), get_txt(ob->name));
if (cob->flags & O_SHADOW)
{
shadow_t *shadow_sent = O_GET_SHADOW(cob);
if (shadow_sent->shadowing)
errorf("shadow '%s' on '%s': Already shadowing.\n"
, get_txt(cob->name), get_txt(ob->name));
if (shadow_sent->shadowed_by)
errorf("shadow '%s' on '%s': Can't shadow when shadowed.\n"
, get_txt(cob->name), get_txt(ob->name));
}
#ifdef USE_INVENTORIES
if (cob->super)
errorf("shadow '%s' on '%s': The shadow resides inside another object ('%s').\n"
, get_txt(cob->name), get_txt(ob->name)
, get_txt(cob->super->name));
#endif
if (ob->flags & O_SHADOW && O_GET_SHADOW(ob)->shadowing)
errorf("shadow '%s' on '%s': Can't shadow a shadow.\n"
, get_txt(cob->name), get_txt(ob->name));
if (ob == cob)
errorf("shadow '%s' on '%s': Can't shadow self.\n"
, get_txt(cob->name), get_txt(ob->name));
/* Make sure that we don't shadow 'nomask' functions.
*/
for (i = shadow->num_function_names; --i >= 0; )
{
funflag_t flags;
string_t *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 )
{
errorf("shadow '%s' on '%s': Illegal to shadow 'nomask' function '%s'.\n"
, get_txt(ob->name), get_txt(cob->name), get_txt(name));
}
}
push_ref_object(inter_sp, ob, "shadow");
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)
/* EFUN 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 */
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)
/* EFUN 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;
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)
/* EFUN 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.
*/
#ifdef USE_ACTIONS
remove_shadow_actions(current_object, shadowing);
#endif
check_shadow_sent(shadowing);
}
shadow_sent->shadowed_by = NULL;
shadow_sent->shadowing = NULL;
check_shadow_sent(current_object);
}
return sp;
} /* f_unshadow() */
#endif
/*-------------------------------------------------------------------------*/
svalue_t *
f_set_driver_hook (svalue_t *sp)
/* EFUN 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 */
n = sp[-1].u.number;
if (n < 0 || n >= NUM_DRIVER_HOOKS)
{
2009-05-21 22:41:07 +00:00
errorf("Bad hook number: %"PRIdPINT", expected 0..%ld\n"
2009-03-03 03:27:01 +00:00
, n, (long)NUM_DRIVER_HOOKS-1);
/* NOTREACHED */
return sp;
}
/* Legal call? */
if (!privilege_violation(STR_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)
{
put_number(driver_hook + n, 0);
break;
}
else if (n == H_REGEXP_PACKAGE)
{
if (sp->u.number != RE_PCRE
&& sp->u.number != RE_TRADITIONAL
)
{
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": got 0x%"PRIxPINT
", expected RE_PCRE (0x%lx) or RE_TRADITIONAL (0x%lx).\n"
, n, sp->u.number
2009-03-03 03:27:01 +00:00
, (long)RE_PCRE, (long)RE_TRADITIONAL
);
break;
}
goto default_test;
}
else
{
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": got number, expected %s or 0.\n"
2009-03-03 03:27:01 +00:00
, n
, efun_arg_typename(hook_type_map[n]));
break;
}
break;
case T_STRING:
{
string_t *str;
if ( !((1 << T_STRING) & hook_type_map[n]) )
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": got string, expected %s.\n"
2009-03-03 03:27:01 +00:00
, n
, efun_arg_typename(hook_type_map[n]));
str = make_tabled_from(sp->u.str); /* for faster apply()s */
put_string(driver_hook + n, str);
free_svalue(sp);
if (n == H_NOECHO)
mudlib_telopts();
break;
}
case T_MAPPING:
if (!sp->u.map->num_values
|| sp->u.map->ref != 1 /* add_to_mapping() could zero num_values */)
{
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": mapping is empty "
2009-03-03 03:27:01 +00:00
"or has other references.\n", n);
return sp;
}
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 = ref_object(master_ob, "hook closure");
if (n == H_NOECHO)
{
mudlib_telopts();
}
break;
}
else if (!CLOSURE_IS_LFUN(sp->x.closure_type))
{
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": unbound lambda or "
2009-03-03 03:27:01 +00:00
"lfun closure expected.\n", n);
}
/* FALLTHROUGH */
default:
default_test:
if ( !((1 << sp->type) & hook_type_map[n]) )
{
2009-05-21 22:41:07 +00:00
errorf("Bad value for hook %"PRIdPINT": got %s, expected %s.\n"
2009-03-03 03:27:01 +00:00
, n, typename(sp->type), efun_arg_typename(hook_type_map[n]));
break; /* flow control hint */
}
driver_hook[n] = *sp;
if (n == H_NOECHO)
{
mudlib_telopts();
}
break;
}
if (old.type != T_NUMBER)
free_svalue(&old);
return sp - 2;
} /* f_set_driver_hook() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_write (svalue_t *sp)
/* 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.
*/
{
#ifdef USE_SHADOWING
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;
}
#endif
print_svalue(sp);
#ifdef USE_SHADOWING
command_giver = check_object(save_command_giver);
#endif
free_svalue(sp); sp--;
return sp;
} /* f_write() */
/*-------------------------------------------------------------------------*/
static void
set_single_limit ( struct limits_context_s * result
, int limit
, svalue_t *svp
)
/* Set the limit #<limit> in *<result> to the value in <svp>.
*
* If the function encounters illegal limit tags or values, it throws
* an error.
*/
{
static char * limitnames[] = { "LIMIT_EVAL", "LIMIT_ARRAY", "LIMIT_MAPPING"
, "LIMIT_BYTE", "LIMIT_FILE", "LIMIT_COST" };
p_int val;
if (svp->type != T_NUMBER)
errorf("Illegal %s value: got a %s, expected a number\n"
, limitnames[limit], typename(svp[limit].type));
val = svp->u.number;
if (limit == LIMIT_COST)
{
if (val == LIMIT_DEFAULT)
result->use_cost = DEF_USE_EVAL_COST;
else if (val != LIMIT_KEEP)
result->use_cost = val;
}
else
{
if (val >= 0)
{
switch(limit)
{
case LIMIT_EVAL: result->max_eval = val; break;
case LIMIT_ARRAY: result->max_array = val; break;
case LIMIT_MAPPING_KEYS: result->max_map_keys = val; break;
case LIMIT_MAPPING_SIZE: 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: errorf("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_KEYS:
result->max_map_keys = def_mapping_keys;
break;
case LIMIT_MAPPING_SIZE:
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: errorf("Unimplemented limit #%d\n", limit);
}
}
else if (val != LIMIT_KEEP)
2009-05-21 22:41:07 +00:00
errorf("Illegal %s value: %"PRIdPINT"\n", limitnames[limit], val);
2009-03-03 03:27:01 +00:00
}
} /* set_single_limit() */
/*-------------------------------------------------------------------------*/
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.
*/
{
/* 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_map_keys = max_mapping_keys;
result->max_callouts = max_callouts;
result->max_byte = max_byte_xfer;
result->max_file = max_file_xfer;
result->use_cost = 0;
if (!tagged)
{
int limit;
for (limit = 0; limit < LIMIT_MAX && limit < num; limit++)
{
set_single_limit(result, limit, svp+limit);
}
}
else
{
int i;
for (i = 0; i < num - 1; i += 2)
{
2009-05-21 22:41:07 +00:00
p_int limit;
2009-03-03 03:27:01 +00:00
if (svp[i].type != T_NUMBER)
errorf("Illegal limit value: got a %s, expected a number\n"
, typename(svp[i].type));
2009-05-21 22:41:07 +00:00
limit = svp[i].u.number;
2009-03-03 03:27:01 +00:00
if (limit < 0 || limit >= LIMIT_MAX)
2009-05-21 22:41:07 +00:00
errorf("Illegal limit tag: %"PRIdPINT"\n", limit);
2009-03-03 03:27:01 +00:00
set_single_limit(result, limit, svp+i+1);
}
}
} /* extract_limits() */
/*-------------------------------------------------------------------------*/
static vector_t *
create_limits_array (struct limits_context_s * rtlimits)
/* Create an array with the values from <rtlimits> and return it.
* Return NULL if out of memory.
*/
{
vector_t *vec;
vec = allocate_uninit_array(LIMIT_MAX);
if (vec)
{
put_number(vec->item+LIMIT_EVAL, rtlimits->max_eval);
put_number(vec->item+LIMIT_ARRAY, rtlimits->max_array);
put_number(vec->item+LIMIT_MAPPING_KEYS, rtlimits->max_map_keys);
put_number(vec->item+LIMIT_MAPPING_SIZE, rtlimits->max_mapping);
put_number(vec->item+LIMIT_BYTE, rtlimits->max_byte);
put_number(vec->item+LIMIT_FILE, rtlimits->max_file);
put_number(vec->item+LIMIT_CALLOUTS, rtlimits->max_callouts);
put_number(vec->item+LIMIT_COST, rtlimits->use_cost);
}
return vec;
} /* create_limits_array() */
/*-------------------------------------------------------------------------*/
svalue_t *
v_limited (svalue_t * sp, int num_arg)
/* EFUN 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 three 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;
vector_t *vec;
struct limits_context_s limits;
int cl_args;
if (!num_arg)
errorf("No arguments given.\n");
argp = sp - num_arg + 1;
cl_args = 0;
/* Get the limits */
if (num_arg == 1)
{
limits.max_eval = 0;
limits.max_array = 0;
limits.max_mapping = 0;
limits.max_map_keys = 0;
limits.max_callouts = 0;
limits.max_byte = 0;
limits.max_file = 0;
limits.use_cost = 1; /* smallest we can do */
}
2009-05-21 22:41:07 +00:00
else if (argp[1].type == T_POINTER && VEC_SIZE(argp[1].u.vec) < INT_MAX)
2009-03-03 03:27:01 +00:00
{
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
{
errorf("limited(): Invalid limit specification.\n");
/* NOTREACHED */
return sp;
}
/* Create an array with the parsed limits to pass
* to privilege violation and store it in argp[1] so that
* it can be cleared in case of an error.
*/
if (num_arg > 1)
free_svalue(argp+1);
else
{
push_number(sp, 0);
num_arg++;
}
vec = create_limits_array(&limits);
if (!vec)
{
inter_sp = sp;
errorf("(set_limits) Out of memory: array[%d] for call.\n"
, LIMIT_MAX);
/* NOTREACHED */
return sp;
}
put_array(argp+1, vec);
/* If this object is destructed, no extern calls may be done */
if (current_object->flags & O_DESTRUCTED
|| !privilege_violation2(STR_LIMITED, argp, argp+1, 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;
2009-05-21 22:41:07 +00:00
rt_context = (rt_context_t *)&context.rt;
2009-03-03 03:27:01 +00:00
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_mapping_keys = limits.max_map_keys;
max_byte_xfer = limits.max_byte;
max_file_xfer = limits.max_file;
max_callouts = limits.max_callouts;
use_eval_cost = limits.use_cost;
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;
} /* v_limited() */
/*-------------------------------------------------------------------------*/
svalue_t *
v_set_limits (svalue_t * sp, int num_arg)
/* EFUN 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 three special values:
* LIMIT_UNLIMITED: the limit is deactivated
* LIMIT_DEFAULT: the global setting is used.
* 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;
vector_t *vec;
if (!num_arg)
errorf("No arguments given.\n");
argp = sp - num_arg + 1;
2009-05-21 22:41:07 +00:00
if (num_arg == 1 && argp->type == T_POINTER && VEC_SIZE(argp->u.vec) < INT_MAX)
2009-03-03 03:27:01 +00:00
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
{
errorf("set_limits(): Invalid limit specification.\n");
/* NOTREACHED */
return sp;
}
/* On the stack, create an array with the parsed limits to pass
* to privilege violation.
*/
sp = pop_n_elems(num_arg, sp); /* sp == argp now */
vec = create_limits_array(&limits);
if (!vec)
{
inter_sp = sp;
errorf("(set_limits) Out of memory: array[%d] for call.\n"
, LIMIT_MAX);
/* NOTREACHED */
return sp;
}
push_array(sp, vec);
num_arg = 1;
if (privilege_violation(STR_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_mapping_keys = limits.max_map_keys;
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;
} /* v_set_limits() */
/*-------------------------------------------------------------------------*/
svalue_t *
f_query_limits (svalue_t * sp)
/* EFUN 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_SIZE]: the max number of mapping values
* int[LIMIT_MAPPING_KEYS]: the max number of mapping entries
* (LIMIT_MAPPING is an alias for LIMIT_MAPPING_KEYS)
* 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()
* int[LIMIT_COST]: how to account for the evaluation cost
*
* A limit of '0' means 'no limit', except for LIMIT_COST.
*/
{
vector_t *vec;
Bool def;
def = sp->u.number != 0;
vec = allocate_uninit_array(LIMIT_MAX);
if (!vec)
{
errorf("(query_limits) Out of memory: array[%d] for result.\n"
, LIMIT_MAX);
/* NOTREACHED */
return sp;
}
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_KEYS
, def ? def_mapping_keys : max_mapping_keys);
put_number(vec->item+LIMIT_MAPPING_SIZE
, 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);
put_number(vec->item+LIMIT_COST, def ? DEF_USE_EVAL_COST : use_eval_cost);
/* No free_svalue: sp is a number */
put_array(sp, vec);
return sp;
} /* f_query_limits() */
/***************************************************************************/